从文档:
将某些数据库行为设置为与指定版本的SQL Server兼容。
...
兼容性级别仅提供与SQL Server早期版本的部分向后兼容性。使用兼容性级别作为临时迁移帮助,以解决由相关兼容性级别设置控制的行为中的版本差异。
在我的解释中,兼容模式是关于行为和语法的解析,而不是解析器说“嘿,您不能使用ROW_NUMBER()
!”之类的东西。有时较低的兼容性级别使您可以继续摆脱不再受支持的语法,有时使您无法使用新的语法结构。该文档列出了几个明确的示例,但是这里有一些演示:
将内置函数作为函数参数传递
该代码在兼容级别90+下工作:
SELECT *
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL);
但在80中它产生:
消息102,级别15,状态1
'('附近的语法不正确。
这里的具体问题是在80中,您不允许将内置函数传递给函数。如果要保持在80兼容模式下,可以通过以下方法解决此问题:
DECLARE @db_id INT = DB_ID();
SELECT *
FROM sys.dm_db_index_physical_stats(@db_id, NULL, NULL, NULL, NULL);
将表类型传递给表值函数
与上述类似,使用TVP并将其传递给表值函数时,可能会出现语法错误。这适用于现代兼容级别:
CREATE TYPE dbo.foo AS TABLE(bar INT);
GO
CREATE FUNCTION dbo.whatever
(
@foo dbo.foo READONLY
)
RETURNS TABLE
AS
RETURN (SELECT bar FROM @foo);
GO
DECLARE @foo dbo.foo;
INSERT @foo(bar) SELECT 1;
SELECT * FROM dbo.whatever(@foo);
但是,将兼容性级别更改为80,然后再次运行最后三行;您收到此错误信息:
消息137,级别16,状态1,第19行
必须声明标量变量“ @foo”。
除了升级compat级别或以其他方式获得结果之外,没有什么好的解决方法。
在APPLY中使用合格的列名
在90兼容模式及更高版本中,您可以毫无问题地做到这一点:
SELECT * FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t;
但是,在80兼容模式下,传递给该函数的限定列会引发通用语法错误:
消息102,级别15,状态1
“。”附近的语法不正确。
ORDER BY碰巧与列名匹配的别名
考虑以下查询:
SELECT name = REVERSE(name), realname = name
FROM sys.all_objects AS o
ORDER BY o.name;
在80兼容模式下,结果如下:
001_ofni_epytatad_ps sp_datatype_info_100
001_scitsitats_ps sp_statistics_100
001_snmuloc_corps_ps sp_sproc_columns_100
...
在90兼容模式下,结果是完全不同的:
snmuloc_lla all_columns
stcejbo_lla all_objects
sretemarap_lla all_parameters
...
原因?在80兼容模式下,表前缀将被完全忽略,因此按SELECT
列表中别名定义的表达式进行排序。在较新的兼容性级别中,将考虑使用表前缀,因此SQL Server将实际使用表中的该列(如果找到)。如果ORDER BY
在表中未找到别名,则较新的兼容性级别不会太含糊。考虑以下示例:
SELECT myname = REVERSE(name), realname = name
FROM sys.all_objects AS o
ORDER BY o.myname;
结果由myname
80中的表达式排序,因为表前缀再次被忽略,但是在90中它生成此错误消息:
消息207,级别16,状态1,第3行
无效的列名“ myname”。
文档中也对此进行了说明:
将ORDER BY
列表中的列引用绑定到列表中定义的SELECT
列时,将忽略列歧义,有时会忽略列前缀。这可能导致结果集以意外的顺序返回。
例如,接受一个ORDER BY
只有两部分的列(<table_alias>.<column>
)的子句用作SELECT列表中列的引用,但是表别名将被忽略。考虑以下查询。
SELECT c1 = -c1 FROM t_table AS x ORDER BY x.c1
执行时,列前缀将在中被忽略ORDER BY
。排序操作未按x.c1
预期在指定的源列()上发生;相反,它发生在派生c1
查询中定义的列。该查询的执行计划表明,首先计算派生列的值,然后对计算值进行排序。
排序不在SELECT列表中
在90兼容模式下,您不能执行以下操作:
SELECT name = COALESCE(a.name, '') FROM sys.objects AS a
UNION ALL
SELECT name = COALESCE(a.name, '') FROM sys.objects AS a
ORDER BY a.name;
结果:
如果语句包含UNION,INTERSECT或EXCEPT运算符,则消息16,级别16,状态1的
ORDER BY项目必须出现在选择列表中。
但是,在80中,您仍然可以使用此语法。
旧的,危险的外部连接
80模式还允许您使用旧的,已弃用的外部联接语法(*=/=*
):
SELECT o.name, c.name
FROM sys.objects AS o, sys.columns AS c
WHERE o.[object_id] *= c.[object_id];
在SQL Server 2008/2008 R2中,如果您年满90岁或更高,则会收到以下详细消息:
消息4147,级别15,状态1
查询使用非ANSI外部联接运算符(“ *=
”或“ =*
”)。要不加修改地运行此查询,请使用ALTER DATABASE的SET COMPATIBILITY_LEVEL选项将当前数据库的兼容性级别设置为80。强烈建议使用ANSI外部联接运算符(LEFT OUTER JOIN,RIGHT OUTER JOIN)重写查询。在SQL Server的将来版本中,即使在向后兼容模式下也将不支持非ANSI连接运算符。
在SQL Server 2012中,这根本不再是有效的语法,并且产生以下内容:
消息102,级别15,状态1,第3行
'* ='附近的语法不正确。
当然,在SQL Server 2012中,由于不再支持80,因此无法再使用兼容性级别来解决此问题。如果您以80兼容模式升级数据库(通过就地升级,分离/附加,备份/还原,日志传送,镜像等),它将自动为您升级到90。
不带WITH的表提示
在80兼容模式下,您可以使用以下内容,并会看到表提示:
SELECT * FROM dbo.whatever NOLOCK;
在90以后,它NOLOCK
不再是表提示,而是别名。否则,这将起作用:
SELECT * FROM dbo.whatever AS w NOLOCK;
但事实并非如此:
消息1018,级别15,状态
1'NOLOCK'附近的语法不正确。如果要将其用作表提示的一部分,则现在需要A WITH关键字和括号。有关正确的语法,请参见SQL Server联机丛书。
现在,为了证明在第一个示例中在90兼容模式下未观察到该行为,请使用AdventureWorks(确保它处于更高的兼容级别)并运行以下命令:
BEGIN TRANSACTION;
SELECT TOP (1) * FROM Sales.SalesOrderHeader UPDLOCK;
SELECT * FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
AND resource_type IN ('KEY', 'OBJECT'); -- how many rows here? 0
COMMIT TRANSACTION;
BEGIN TRANSACTION;
SELECT TOP (1) * FROM Sales.SalesOrderHeader WITH (UPDLOCK);
SELECT * FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
AND resource_type IN ('KEY', 'OBJECT'); -- how many rows here? 2
COMMIT TRANSACTION;
这一行为特别有问题,因为行为发生了变化,而没有错误消息甚至没有错误。而且,升级顾问和其他工具可能甚至找不到,因为就其所知,这就是表别名。
涉及新日期/时间类型的转换
SQL Server 2008中引入的新的日期/时间类型(例如date
和datetime2
)支持的范围比原始的日期datetime
和时间更大smalldatetime
。无论兼容级别如何,显式转换所支持范围之外的值都将失败,例如:
SELECT CONVERT(SMALLDATETIME, '00010101');
产量:
消息242,级别16,状态3
将varchar数据类型转换为smalldatetime数据类型导致值超出范围。
但是,隐式转换将在较新的兼容性级别中起作用。例如,这将在100多个版本中起作用:
SELECT DATEDIFF(DAY, CONVERT(SMALLDATETIME, SYSDATETIME()), '00010101');
但是在80(以及90)中,它会产生与上述类似的错误:
消息242,级别16,状态3
将varchar数据类型转换为日期时间数据类型导致超出范围的值。
触发器中的冗余FOR子句
这是一个晦涩的场景。在80兼容模式下,这将成功:
CREATE TABLE dbo.x(y INT);
GO
CREATE TRIGGER tx ON dbo.x
FOR UPDATE, UPDATE
------------^^^^^^ notice the redundant UPDATE
AS PRINT 1;
在90兼容性及更高版本中,它不再解析,而是出现以下错误消息:
消息1034,级别15,状态1,过程tx
语法错误:在触发器声明中重复指定了操作“ UPDATE”。
PIVOT / UNPIVOT
某些形式的语法在80岁以下无法正常工作(但在90多个版本中仍然可以正常工作):
SELECT col1, col2
FROM dbo.t1
UNPIVOT (value FOR col3 IN ([x],[y])) AS p;
这样产生:
消息156,级别15,状态1
关键字'for'附近的语法不正确。
有关某些变通办法,包括CROSS APPLY
,请参阅以下答案。
新的内置功能
尝试使用新功能,例如TRY_CONVERT()
在兼容级别<110的数据库中。根本无法识别它们。
SELECT TRY_CONVERT(INT, 1);
结果:
消息195,级别15,状态10
“ TRY_CONVERT”不是公认的内置函数名称。
建议
仅在实际需要时才使用80兼容模式。由于在2008 R2之后的下一版本中将不再提供该功能,因此您要做的最后一件事是在此兼容级别下编写代码,依赖于您看到的行为,然后在无法再使用时遇到很多麻烦使用该兼容级别。前瞻性思考,不要试图通过花时间继续使用旧的,过时的语法将自己描绘在一个角落。