web-dev-qa-db-ja.com

互換性レベル80の実際の動作は何ですか?

誰かが互換性モード機能についてより良い洞察を私に提供できますか?予想とは異なる動作をしています。

私が互換モードを理解している限り、SQL Serverのさまざまなバージョン間での特定の言語構造の可用性とサポートについてです。

データベースエンジンバージョンの内部動作には影響しません。以前のバージョンではまだ利用できなかった機能や構造の使用を防止しようとします。

SQL Server 2008 R2で互換レベル80の新しいデータベースを作成しました。単一のint列を持つテーブルを作成し、それに数行を追加しました。

次に、row_number()関数を使用してselectステートメントを実行しました。

私の考えでは、row_number関数は2005年に導入されただけなので、これはcompat 80モードでエラーをスローします。

しかし、驚いたことにこれはうまくいきました。そして、確かに、互換性ルールは「何かを保存」したときにのみ評価されます。そこで、row_numberステートメント用のストアドプロシージャを作成しました。

ストアドプロシージャの作成はうまくいき、完全に実行して結果を得ることができます。

誰かが互換モードの動作をよりよく理解するのを手伝ってくれませんか?私の理解には明らかに欠陥があります。

47
souplex

the docs から:

特定のデータベースの動作を、指定されたバージョンの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に変更し、最後の3行を再度実行します。あなたはこのエラーメッセージを受け取ります:

メッセージ137、レベル16、状態1、行19
スカラー変数「@foo」を宣言する必要があります。

互換性レベルをアップグレードしたり、結果を別の方法で取得したりする以外は、本当に頭の上の良い回避策はありません。


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;
_

結果は、80ではmyname式によって並べ替えられます。これも、テーブルプレフィックスが無視されるためですが、90ではこのエラーメッセージが生成されます。

メッセージ207、レベル16、状態1、行3
無効な列名 'myname'。

これはすべて ドキュメント でも説明されています:

_ORDER BY_リストの列参照をSELECTリストで定義された列にバインドすると、列のあいまいさが無視され、列のプレフィックスが無視されることがあります。これにより、結果セットが予期しない順序で返される可能性があります。

たとえば、SELECTリストの列への参照として使用される単一の2部構成の列(_ORDER BY_)を含む_<table_alias>.<column>_句は受け入れられますが、テーブルのエイリアスは無視されました。次のクエリを考えてみましょう。

_SELECT c1 = -c1 FROM t_table AS x ORDER BY x.c1_

実行すると、_ORDER BY_の列プレフィックスは無視されます。指定されたソース列(_x.c1_)でソート操作が期待どおりに行われません。代わりに、クエリで定義されている派生_c1_列で発生します。このクエリの実行プランは、派生列の値が最初に計算され、次に計算された値がソートされることを示しています。


SELECTリストにないORDER BY

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;
_

結果:

メッセージ104、レベル16、状態1
ステートメントにUNION、INTERSECT、またはEXCEPT演算子が含まれている場合、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」付近の構文が正しくありません。これがテーブルヒントの一部として意図されている場合、WITHキーワードと括弧が必要になりました。適切な構文については、SQL Server Books Onlineを参照してください。

ここで、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データ型をdatetimeデータ型に変換すると、範囲外の値が発生しました。


トリガー内の冗長な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_を含むいくつかの回避策については、 これらの回答 を参照してください。


新しい組み込み関数

互換性レベルが110未満のデータベースでTRY_CONVERT()などの新しい関数を使用してみてください。これらの関数はまったく認識されません。

_SELECT TRY_CONVERT(INT, 1);
_

結果:

メッセージ195、レベル15、状態10
「TRY_CONVERT」は、認識されている組み込み関数名ではありません。


推奨事項

実際に必要な場合にのみ80互換モードを使用します。2008 R2以降の次のバージョンでは使用できなくなるため、最後にこれにコードを記述します互換性レベルを確認し、表示される動作に依存し、その互換性レベルを使用できなくなったときに、かなりの破損が発生します。前向きに考え、古い非推奨の構文を使い続ける時間を稼ぐことによって、自分を隅に追いやろうとしないでください。

67
Aaron Bertrand

互換性レベルは、SQL Serverの以前のバージョンからの制御された移行を可能にするためにのみ存在します。互換性レベル90は、新機能の使用を排除するものではなく、単にデータベースの特定の側面がSQL Server 2005の動作と互換性のある方法で保持されることを意味します。

詳細は http://msdn.Microsoft.com/en-us/library/bb510680.aspx を参照してください。

9
Max Vernon