web-dev-qa-db-fra.com

Quel est le comportement réel du niveau de compatibilité 80?

Quelqu'un pourrait-il me donner une meilleure idée de la fonctionnalité du mode de compatibilité? Il se comporte différemment de ce à quoi je m'attendais.

Pour autant que je comprenne les modes de compatibilité, il s'agit de la disponibilité et de la prise en charge de certaines structures linguistiques entre les différentes versions de SQL Server.

Il n'affecte pas le fonctionnement interne de la version du moteur de base de données. Il tenterait d'empêcher l'utilisation de fonctionnalités et de constructions qui n'étaient pas encore disponibles dans les versions antérieures.

Je viens de créer une nouvelle base de données avec un niveau de compatibilité 80 dans SQL Server 2008 R2. A créé une table avec une seule colonne int et l'a remplie avec quelques lignes.

Puis exécuté une instruction select avec une fonction row_number().

Ma pensée était, puisque la fonction row_number n'a été introduite qu'en 2005, cela provoquerait une erreur en mode compat 80.

Mais à ma grande surprise, cela a bien fonctionné. Alors, sûrement, les règles de compatibilité ne sont évaluées qu'une fois que vous "enregistrez quelque chose". J'ai donc créé un proc stocké pour mon instruction row_number.

La création de proc stockée s'est bien passée et je peux parfaitement l'exécuter et obtenir des résultats.

Quelqu'un pourrait-il m'aider à mieux comprendre le fonctionnement du mode de compatibilité? Ma compréhension est évidemment imparfaite.

47
souplex

De les docs :

Définit certains comportements de base de données pour qu'ils soient compatibles avec la version spécifiée de SQL Server.
...
Le niveau de compatibilité n'offre qu'une compatibilité descendante partielle avec les versions antérieures de SQL Server. Utilisez le niveau de compatibilité comme aide à la migration provisoire pour contourner les différences de version dans les comportements contrôlés par le paramètre de niveau de compatibilité approprié.

Dans mon interprétation, le mode de compatibilité concerne le comportement et l'analyse syntaxique, pas pour des choses comme l'analyseur qui dit: "Hé, vous ne pouvez pas utiliser ROW_NUMBER()!" Parfois, le niveau de compatibilité inférieur vous permet de continuer à vous en sortir avec la syntaxe qui n'est plus prise en charge, et parfois il vous empêche d'utiliser de nouvelles constructions de syntaxe. La documentation répertorie plusieurs exemples explicites, mais voici quelques démonstrations:


Passage de fonctions intégrées comme arguments de fonction

Ce code fonctionne au niveau de compatibilité 90+:

SELECT *
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL);

Mais en 80 cela donne:

Msg 102, niveau 15, état 1
Syntaxe incorrecte près de '('.

Le problème spécifique ici est qu'en 80, vous n'êtes pas autorisé à passer une fonction intégrée dans une fonction. Si vous souhaitez rester en mode de compatibilité 80, vous pouvez contourner ce problème en disant:

DECLARE @db_id INT = DB_ID();

SELECT * 
FROM sys.dm_db_index_physical_stats(@db_id, NULL, NULL, NULL, NULL);

Passage d'un type de table à une fonction table

Similaire à ce qui précède, vous pouvez obtenir une erreur de syntaxe lorsque vous utilisez un TVP et essayez de le transmettre à une fonction table. Cela fonctionne dans les niveaux de compatibilité modernes:

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

Toutefois, modifiez le niveau de compatibilité à 80 et exécutez à nouveau les trois dernières lignes; vous obtenez ce message d'erreur:

Msg 137, niveau 16, état 1, ligne 19
Doit déclarer la variable scalaire "@foo".

Pas vraiment une bonne solution de contournement, à part la mise à niveau du niveau de compatibilité ou l'obtention des résultats d'une manière différente.


Utilisation de noms de colonnes qualifiés dans APPLY

En mode de compatibilité 90 et plus, vous pouvez le faire sans problème:

SELECT * FROM sys.dm_exec_cached_plans AS p
  CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t;

Cependant, en mode de compatibilité 80, la colonne qualifiée remise à la fonction déclenche une erreur de syntaxe générique:

Msg 102, niveau 15, état 1
Syntaxe incorrecte près de '.'.


ORDER BY un alias qui correspond au nom d'une colonne

Considérez cette requête:

SELECT name = REVERSE(name), realname = name 
FROM sys.all_objects AS o
ORDER BY o.name;

En mode de compatibilité 80, les résultats sont les suivants:

001_ofni_epytatad_ps   sp_datatype_info_100
001_scitsitats_ps      sp_statistics_100
001_snmuloc_corps_ps   sp_sproc_columns_100
...

En mode de compatibilité 90, les résultats sont assez différents:

snmuloc_lla      all_columns
stcejbo_lla      all_objects
sretemarap_lla   all_parameters
...

La raison? En mode de compatibilité 80, le préfixe de table est entièrement ignoré, il est donc trié par l'expression définie par l'alias dans la liste SELECT. Dans les niveaux de compatibilité plus récents, le préfixe de table est pris en compte, donc SQL Server utilisera réellement cette colonne dans la table (si elle est trouvée). Si l'alias ORDER BY N'est pas trouvé dans le tableau, les nouveaux niveaux de compatibilité ne pardonnent pas trop l'ambiguïté. Considérez cet exemple:

SELECT myname = REVERSE(name), realname = name 
FROM sys.all_objects AS o
ORDER BY o.myname;

Le résultat est ordonné par l'expression myname en 80, car là encore le préfixe de table est ignoré, mais en 90 il génère ce message d'erreur:

Msg 207, niveau 16, état 1, ligne 3
Nom de colonne non valide 'myname'.

Tout cela est également expliqué dans la documentation :

Lors de la liaison des références de colonne de la liste ORDER BY Aux colonnes définies dans la liste SELECT, les ambiguïtés de colonne sont ignorées et les préfixes de colonne sont parfois ignorés. Cela peut entraîner le retour du jeu de résultats dans un ordre inattendu.

Par exemple, une clause ORDER BY Avec une seule colonne en deux parties (<table_alias>.<column>) Qui est utilisée comme référence à une colonne dans une liste SELECT est acceptée, mais la table l'alias est ignoré. Considérez la requête suivante.

SELECT c1 = -c1 FROM t_table AS x ORDER BY x.c1

Une fois exécuté, le préfixe de colonne est ignoré dans le ORDER BY. L'opération de tri ne se produit pas sur la colonne source spécifiée (x.c1) Comme prévu; au lieu de cela, il se produit sur la colonne dérivée c1 définie dans la requête. Le plan d'exécution de cette requête montre que les valeurs de la colonne dérivée sont d'abord calculées, puis les valeurs calculées sont triées.


COMMANDER PAR quelque chose qui n'est pas dans la liste SELECT

En mode de compatibilité 90, vous ne pouvez pas faire ceci:

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;

Résultat:

Msg 104, niveau 16, état 1
Les éléments ORDER BY doivent apparaître dans la liste de sélection si l'instruction contient un opérateur UNION, INTERSECT ou EXCEPT.

En 80, cependant, vous pouvez toujours utiliser cette syntaxe.


Anciennes jointures externes icky

Le mode 80 vous permet également d'utiliser l'ancienne syntaxe de jointure externe obsolète (*=/=*):

SELECT o.name, c.name
FROM sys.objects AS o, sys.columns AS c
WHERE o.[object_id] *= c.[object_id];

Dans SQL Server 2008/2008 R2, si vous avez 90 ans ou plus, vous obtenez ce message détaillé:

Msg 4147, niveau 15, état 1
La requête utilise des opérateurs de jointure externes non ANSI ("*=" Ou "=*"). Pour exécuter cette requête sans modification, veuillez définir le niveau de compatibilité de la base de données actuelle sur 80, à l'aide de l'option SET COMPATIBILITY_LEVEL de ALTER DATABASE. Il est fortement recommandé de réécrire la requête à l'aide des opérateurs de jointure externe ANSI (LEFT OUTER JOIN, RIGHT OUTER JOIN). Dans les futures versions de SQL Server, les opérateurs de jointure non ANSI ne seront pas pris en charge, même dans les modes de compatibilité descendante.

Dans SQL Server 2012, ce n'est plus du tout une syntaxe valide et donne ce qui suit:

Msg 102, niveau 15, état 1, ligne 3
Syntaxe incorrecte près de '* ='.

Bien sûr, dans SQL Server 2012, vous ne pouvez plus contourner ce problème en utilisant le niveau de compatibilité, car 80 n'est plus pris en charge. Si vous mettez à niveau une base de données en mode 80 compat (par mise à niveau sur place, détacher/attacher, sauvegarder/restaurer, envoi de journaux, mise en miroir, etc.), elle sera automatiquement mise à niveau à 90 pour vous.


Conseils de tableau sans WITH

En mode 80 compat, vous pouvez utiliser ce qui suit et l'indice du tableau sera observé:

SELECT * FROM dbo.whatever NOLOCK; 

Dans 90+, ce NOLOCK n'est plus un indice de tableau, c'est un alias. Sinon, cela fonctionnerait:

SELECT * FROM dbo.whatever AS w NOLOCK;

Mais ce n'est pas le cas:

Msg 1018, niveau 15, état 1
Syntaxe incorrecte près de 'NOLOCK'. Si cela est prévu dans le cadre d'une indication de tableau, un mot clé A WITH et des parenthèses sont désormais requis. Consultez la documentation en ligne de SQL Server pour la syntaxe appropriée.

Maintenant, pour prouver que le comportement n'est pas observé dans le premier exemple en mode 90 compat, utilisez AdventureWorks (en vous assurant qu'il est à un niveau de compatibilité supérieur) et exécutez ce qui suit:

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;

Celui-ci est particulièrement problématique car le comportement change sans message d'erreur ni même erreur. Et c'est aussi quelque chose que le conseiller de mise à niveau et d'autres outils pourraient même ne pas repérer, car pour autant qu'il sache, c'est un alias de table.


Conversions impliquant de nouveaux types de date/heure

Les nouveaux types de date/heure introduits dans SQL Server 2008 (par exemple date et datetime2) Prennent en charge une plage beaucoup plus grande que les datetime et smalldatetime d'origine). Les conversions explicites de valeurs en dehors de la plage prise en charge échoueront quel que soit le niveau de compatibilité, par exemple:

SELECT CONVERT(SMALLDATETIME, '00010101');

Rendements:

Msg 242, niveau 16, état 3
La conversion d'un type de données varchar en un type de données smalldatetime a entraîné une valeur hors plage.

Cependant, les conversions implicites fonctionneront elles-mêmes dans les nouveaux niveaux de compatibilité. Par exemple, cela fonctionnera dans 100+:

SELECT DATEDIFF(DAY, CONVERT(SMALLDATETIME, SYSDATETIME()), '00010101');

Mais en 80 (et aussi en 90), cela donne une erreur similaire à celle ci-dessus:

Msg 242, niveau 16, état 3
La conversion d'un type de données varchar en un type de données datetime a donné lieu à une valeur hors plage.


Clauses FOR redondantes dans les déclencheurs

C'est n scénario obscur qui est venu ici . En mode de compatibilité 80, cela réussira:

CREATE TABLE dbo.x(y INT);
GO
CREATE TRIGGER tx ON dbo.x
FOR UPDATE, UPDATE
------------^^^^^^ notice the redundant UPDATE
AS PRINT 1;

Dans la compatibilité 90 et supérieure, cela n'analyse plus et vous obtenez à la place le message d'erreur suivant:

Msg 1034, niveau 15, état 1, procédure tx
Erreur de syntaxe: spécification en double de l'action "UPDATE" dans la déclaration de déclenchement.


PIVOT/UNPIVOT

Certaines formes de syntaxe ne fonctionneront pas sous 80 (mais fonctionnent très bien dans 90+):

SELECT col1, col2
FROM dbo.t1
UNPIVOT (value FOR col3 IN ([x],[y])) AS p;

Cela donne:

Msg 156, niveau 15, état 1
Syntaxe incorrecte près du mot clé "for".

Pour certaines solutions de contournement, y compris CROSS APPLY, Voir ces réponses .


Nouvelles fonctions intégrées

Essayez d'utiliser de nouvelles fonctions comme TRY_CONVERT() dans une base de données avec un niveau de compatibilité <110. Elles n'y sont tout simplement pas reconnues.

SELECT TRY_CONVERT(INT, 1);

Résultat:

Msg 195, niveau 15, état 10
'TRY_CONVERT' n'est pas un nom de fonction intégrée reconnu.


Recommandation

N'utilisez le mode de compatibilité 80 que si vous en avez réellement besoin. Comme il ne sera plus disponible dans la prochaine version après 2008 R2, la dernière chose que vous voulez faire est d'écrire du code dans ce niveau de compatibilité, compter sur les comportements que vous voyez, puis avoir tout un tas de bris lorsque vous ne pouvez plus utiliser ce niveau de compatibilité. Soyez avant-gardiste et n'essayez pas de vous peindre dans un coin en gagnant du temps pour continuer à utiliser une ancienne syntaxe obsolète.

67
Aaron Bertrand

Les niveaux de compatibilité sont uniquement présents pour permettre une migration contrôlée à partir d'une version antérieure de SQL Server. Le niveau de compatibilité 90 n'empêche pas d'utiliser de nouvelles fonctionnalités, cela signifie simplement que certains aspects de la base de données sont conservés d'une manière compatible avec le fonctionnement de SQL Server 2005.

Voir http://msdn.Microsoft.com/en-us/library/bb510680.aspx pour plus d'informations.

9
Max Vernon