Behavior changes in SQL Server 2000 regarding the comparison of columns and constants have several implications on the expected results as compared to previous releases, such as:
- Query Results
- Query Execution Plans, Index Selection, and Performance
- Error Conditions
Query results
Consider the following scenario:
create table T1 (col1 int NOT NULL)
go
insert T1 values (1)
insert T1 values (2)
insert T1 values (3)
go
If you run the following query on SQL Server version 7.0 (or earlier) or on SQL Server 2000 with database compatibility level set to 70, you receive the following results:
select * from T1 where col1=2.3
col1
-----------
2
(1 row(s) affected)
If you run the same query on SQL Server 2000 using the default compatibility level (80), you receive the following results:
col1
-----------
(0 row(s) affected)
In the first case, 2.3 is converted to an
int value (yielding 2 as a result) and the query is executed as "...where col1=2," returning one row because constants are always converted to the data type column.
In the second case, the constant 2.3 is identified as an approximate number that is higher in the data type hierarchy than the column declared as
int. Therefore, the query is evaluated as "convert(decimal(2,1), col1)=2.3" and no rows are returned. If you want SQL Server 2000 to behave as earlier versions did, then rewrite the query as:
select * from T1 where col1=convert(int,2.3)
The same situation applies to other data types as well. For example, a comparison between a column of
char data type and a Unicode constant might generate different results on SQL Server 7.0 and SQL Server 2000.
Query execution plans, index selection, and performance
As stated earlier, in some cases a column may be converted to another data type to perform the comparison depending on the data types being compared. This means that a query with a predicate such as "<column> = <literal>" is treated as "...convert<(other_data_type>, <column)> = <literal>" and the execution plan for such a variation might change significantly.
Consider the following scenario:
create table T3 (col1 char(10) NOT NULL)
go
insert T3 values ('a')
insert T3 values ('b')
insert T3 values ('c')
go
create clustered index CIT3 on T3(col1)
The following query on SQL Server version 7.0 or on SQL Server 2000 with a 70 database compatibility level is run by using an index seek for the particular value:
select * from T3 where col1=N'a'
|--Clustered Index Seek(OBJECT:([Northwind].[dbo].[T3].[CIT3]), SEEK:([T3].[col1]=Convert([@1])) ORDERED FORWARD)
Under compatibility level 80, SQL Server 2000 uses an index scan, which results in higher I/O, CPU usage, and run time.
|--Clustered Index Scan(OBJECT:([Northwind].[dbo].[T3].[CIT3]), WHERE:(Convert([T3].[col1])=[@1]))
Consider another scenario:
create table T1
(col1 int NOT NULL)
go
insert T1 values (1)
insert T1 values (2)
insert T1 values (3)
go
create clustered index clustind on T1(col1)
The following query on SQL Server 7.0 or on SQL Server 2000 with a 70 database compatibility level gives us the query plan below.
Select * from T1 where col1 = 1
|--Clustered Index Seek(OBJECT:([master].[dbo].[T1].[clustind]), SEEK:([T1].[col1]=[@1]) ORDERED)
Under compatibility level 80, SQL Server 2000 converts the literal 1 to the same data type as the
Col1 column. Therefore, it is converted to an
int.
The assumed base type of the literal 1 is
tinyint. The
tinyint data type is the lowest in the data type precedence in the integer family.
This allows you to convert the literal to the data type of the column without a loss in precision.
Select * from T1 where col1 = 1
|--Clustered Index Seek(OBJECT:([pubs].[dbo].[T1].[clustind]), SEEK:([T1].[col1]=Convert([@1])) ORDERED FORWARD)
If the conversion happens between numeric data types, equality comparison is executed as a range seek followed by a comparison (of the returned range) with the constant. This has the negative consequence of disallowing the use of additional columns on composite indexes, as in the following scenario:
create table T4 (col1 int NOT NULL, col2 int NOT NULL)
insert T4 values (1,1)
insert T4 values (1,2)
insert T4 values (1,3)
create clustered index CIT4 on T4(col1, col2)
Running the following query with previous behavior yields an index seek on the two columns:
select * from T4 where col1=1.1 and col2=2
|--Clustered Index Seek(OBJECT:([Northwind].[dbo].[T4].[CIT4]),
SEEK:([T4].[col1]=Convert([@1]) AND [T4].[col2]=Convert([@2])) ORDERED FORWARD)
Under SQL Server 2000 with an 80 compatibility level, the plan includes an index range seek on the first column only, followed by a nested loops join with the constant to find the matching rows. (Note that the complete plan is not shown.)
|--Clustered Index Seek(OBJECT:([Northwind].[dbo].[T4].[CIT4]),
SEEK:([T4].[col1] > [Expr1004] AND [T4].[col1] < [Expr1005]),
WHERE:([T4].[col2]=Convert([@2]) AND Convert([T4].[col1])=Convert([@1]))
ORDERED FORWARD)
Error conditions
The new comparison behavior may also allow queries that used to fail on previous versions. Consider the following scenario:
create table T5 (col1 tinyint NOT NULL)
go
insert T5 values (1)
insert T5 values (2)
go
If you run the following query, an error is raised under the previous behavior because SQL Server attempts to convert the constant 300 into a
tinyint value:
select * from T5 where col1= 300
Result:
Server: Msg 220, Level 16, State 2, Line 1
Arithmetic overflow error for data type tinyint, value = 300.
Under SQL Server 2000 (set to a compatibility level 80), the query runs because the
tinyint column is converted to a larger
integer data type:
col1
----
(0 row(s) affected)
Additional resources
To change the compatibility level of a database, use the
sp_dbcmptlevel stored procedure. For more information, refer to the "sp_dbcmptlevel" and "Database Compatibility Level Option" topics in SQL Server Books Online.
For more information on data type precedence hierarchy, see the "Data Type Precedence" topic in SQL Server Books Online.
For more information on using literals on SQL Server, see the "Constants" topic in SQL Server Books Online.