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

47

Quelqu'un pourrait-il me donner une meilleure idée de la fonctionnalité du mode de compatibilité? Il se comporte différemment que prévu.

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

Cela n'affecte pas le fonctionnement interne de la version du moteur de base de données. Cela empêcherait l'utilisation de fonctionnalités et de constructions qui n'étaient pas encore disponibles dans les versions précédentes.

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

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

À mon avis, puisque la fonction row_number n’a été introduite qu’en 2005, une erreur en mode compat 80 serait alors générée.

Mais à ma grande surprise, cela a bien fonctionné. Ensuite, les règles de compatibilité ne sont sûrement évaluées que lorsque 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.

souplex
la source

Réponses:

66

De la docs :

Définit la compatibilité de certains comportements de la base de données avec la version spécifiée de SQL Server.
...
Le niveau de compatibilité n'offre qu'une compatibilité ascendante 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 des 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 échapper avec une syntaxe qui n'est plus prise en charge, et parfois vous empêche d'utiliser de nouvelles constructions de syntaxe. La documentation énumère plusieurs exemples explicites, mais voici quelques démonstrations:


Passer des fonctions intégrées en tant qu'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 dans 80 cela donne:

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

Le problème spécifique ici est que dans 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);

Passer un type de table à une fonction table

Comme dans ce qui précède, vous pouvez obtenir une erreur de syntaxe lorsque vous utilisez un programme TVP et essayez de le transmettre à une fonction table. Cela fonctionne dans les niveaux de compat 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 réexécutez 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 si ce n’est d’améliorer le niveau de compatibilité ou d’obtenir un résultat différent.


Utilisation de noms de colonne 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 transmise à la fonction soulève une erreur de syntaxe générique:

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


ORDER BY un alias qui correspond à un nom de colonne

Considérons 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 la table est entièrement ignoré. Il est donc ordonné par l'expression définie par l'alias de la SELECTliste. Dans les nouveaux niveaux de compatibilité, le préfixe de la table est pris en compte. SQL Server utilisera donc cette colonne dans la table (si elle est trouvée). Si l' ORDER BYalias ne figure pas dans la table, les nouveaux niveaux de compatibilité ne sont pas aussi clairs en ce qui concerne l'ambiguïté. Considérons 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' mynameexpression dans 80, car à nouveau le préfixe de table est ignoré, mais dans 90, il génère ce message d'erreur:

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

Tout cela est expliqué aussi dans la documentation :

Lorsque vous liez les références de colonne de la ORDER BYliste aux colonnes définies dans la SELECTliste, 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 ORDER BYclause avec une seule colonne en deux parties ( <table_alias>.<column>) utilisée comme référence à une colonne dans une liste SELECT est acceptée, mais l'alias de table est ignoré. Considérons la requête suivante.

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

Lorsqu'il est exécuté, le préfixe de colonne est ignoré dans le fichier 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 il se produit sur le dérivéc1colonne 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 calculées en premier, puis que les valeurs calculées sont triées.


ORDER BY quelque chose qui ne figure 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, Etat 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.

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


Vieux, icky jointures externes

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 êtes dans 90 ou plus, vous obtenez ce message détaillé:

Msg 4147, niveau 15, état 1
La requête utilise des opérateurs de jointure externe non-ANSI (" *=" ou " =*"). Pour exécuter cette requête sans modification, définissez 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 vivement 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 en mode de compatibilité ascendante.

Dans SQL Server 2012, cette syntaxe n'est plus valide et génère les éléments suivants:

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 à l'aide du niveau de compatibilité, car 80 n'est plus pris en charge. Si vous mettez à niveau une base de données en mode 80 compat (mise à niveau sur place, détachement / liaison, sauvegarde / restauration, envoi des journaux, mise en miroir, etc.), elle sera automatiquement mise à niveau à 90 pour vous.


Conseils de table sans WITH

En mode 80 compat, vous pouvez utiliser ce qui suit et le conseil de la table sera observé:

SELECT * FROM dbo.whatever NOLOCK; 

Dans 90+, ce NOLOCKn'est plus un indice de table, 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 comme une indication de table, un mot-clé WITH et une parenthèse sont désormais obligatoires. Consultez la documentation en ligne de SQL Server pour connaître 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 s'agit d'un niveau de compat 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 d'erreur. Et c’est aussi quelque chose que le conseiller de mise à niveau et d’autres outils risquent de ne pas voir, car 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, dateet datetime2) prennent en charge une plage beaucoup plus large que celle d'origine datetimeet smalldatetime). Les conversions explicites de valeurs situées 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 limites.

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 dans 80 (et aussi dans 90), cela produit 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 entraîné une valeur hors limites.


Clauses FOR redondantes dans les déclencheurs

Ceci est un 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 les versions 90 et supérieures, cette fonctionnalité n'est plus analysée et vous obtenez le message d'erreur suivant:

Msg 1034, Niveau 15, Etat 1, Procédure tx
Erreur de syntaxe: spécification en double de l'action "UPDATE" dans la déclaration du déclencheur.


PIVOT / UNPIVOT

Certaines formes de syntaxe ne fonctionneront pas sous 80 (mais fonctionnent parfaitement 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é 'pour'.

Pour certaines solutions de contournement, notamment 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 dont le niveau de compatibilité est <110. Elles ne 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é reconnu.


Recommandation

Utilisez uniquement le mode de compatibilité 80 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 souhaitiez faire est d'écrire du code dans ce niveau de compatibilité, de vous fier aux comportements que vous voyez et d'avoir tout un tas de casse quand vous ne pouvez plus utiliser ce niveau de compat. Réfléchissez bien et n'essayez pas de vous mettre dans une impasse en gagnant du temps pour continuer à utiliser une ancienne syntaxe, obsolète.

Aaron Bertrand
la source
1
Clairement, c'est une meilleure réponse que la mienne!
Max Vernon
Merci beaucoup pour cette réponse complexe, Aaron! Et pour avoir corrigé mes nombreuses fautes d'orthographe.
souplex
1
Les notes de compatibilité de SQL Server 2014 sont disponibles ici: msdn.microsoft.com/fr-fr/library/bb510680(v=sql.120).aspx
Josh Gallagher
9

Les niveaux de compatibilité ne sont présents que pour permettre une migration contrôlée à partir d'une version antérieure de SQL Server. Compat Level 90 n’empêche pas l’utilisation 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.

Max Vernon
la source