Migration : réécrire ses jointures ANSI 89
Par Arian Papillon le dimanche 12 juin 2011, 15:34 - Lien permanent
Dans le cadre de la migration vers SQL Server, un problème de compatibilité souvent relevé avec l’assistant de migration est la présence de jointures externes (LEFT OUTER JOIN / RIGHT OUTER JOIN) écrites avec la syntaxe ANSI 89 (dans la clause WHERE avec *= ou =*).
Cette syntaxe est encore supportée en SQL Server 2005, 2008 ou 2008 R2, à condition de configurer la base de données avec le mode de compatibilité 80, mais cela reste un mode temporaire jusqu’à la correction complète des requêtes incriminées.
Concernant Denali (prochaine version de SQL Server), il est fort probable que ces syntaxes ne seront plus du tout supportées : la migration vers cette version nécessitera donc impérativement une réécriture préalable de ces requêtes.
Quelle méthode pour limiter le risque d’erreurs lors de la réécriture ?
Le problème :
Constituons d'abord un jeu d’exemples (en SQL 2008 R2 et mode de compatibilité 80) :
CREATE DATABASE ANSI_JOINS GO USE ANSI_JOINS GO EXEC sp_dbcmptlevel 'ANSI_JOINS', 80 GO CREATE TABLE [dbo].[ENTITE] ( [id_entite] [int] IDENTITY(1, 1) NOT NULL, [code] [int] NOT NULL, [indicateur_perimetre] [int] NOT NULL, [devise] [int] NOT NULL, [zone_franc] [int] NULL ) ON [PRIMARY] GO SET IDENTITY_INSERT [dbo].[ENTITE] ON INSERT [dbo].[ENTITE] ([id_entite], [code], [indicateur_perimetre], [devise], [zone_franc]) VALUES (1, 123, 123, 123, 123) INSERT [dbo].[ENTITE] ([id_entite], [code], [indicateur_perimetre], [devise], [zone_franc]) VALUES (2, 123, 123, 123, 123) SET IDENTITY_INSERT [dbo].[ENTITE] OFF GO CREATE TABLE [dbo].[LIB_ENTITE] ( [entite] [int] NOT NULL, [langue] [int] NOT NULL, [libelle_court] char(30) NOT NULL, [libelle_long] char(60) NOT NULL ) ON [PRIMARY] INSERT [dbo].[LIB_ENTITE] ([entite], [langue], [libelle_court], [libelle_long]) VALUES (1, 1033, N'anglais ', N'anglais ') INSERT [dbo].[LIB_ENTITE] ([entite], [langue], [libelle_court], [libelle_long]) VALUES (1, 1036, N'francais ', N'francais ') INSERT [dbo].[LIB_ENTITE] ([entite], [langue], [libelle_court], [libelle_long]) VALUES (2, 1033, N'anglais ', N'anglais ') GO
Considérons la requête suivante :
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S, LIB_ENTITE LS WHERE LS.entite =* S.id_entite
Jusque là, aucun problème de traduction :
SELECT S.id_entite, S.code, LS.libelle_court FROM LIB_ENTITE LS RIGHT JOIN ENTITE S ON LS.entite = S.id_entite
ou
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite
Même l’interface graphique s’y retrouve :
Le problème va se poser lorsque j’ai également des conditions en plus de mes clauses de jointures :
Cas 1 :
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S, LIB_ENTITE LS WHERE LS.entite =* S.id_entite AND LS.langue = 1036
Dans ce cas, l’interface graphique ne sait pas nous lire cette requête correctement, et nous risquerions de faire la même erreur...
Pour obtenir les bons résultats, la condition LS.langue = 1036 doit être dans la clause de jointure et non dans la clause WHERE !
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite AND LS.langue = 1036
Cas 2 :
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S, LIB_ENTITE LS WHERE LS.entite =* S.id_entite AND S.id_entite=1
L’interface graphique a raison dans ce cas et la condition est bien un filtre de la clause WHERE
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite WHERE S.id_entite = 1
Cas 3 (fusion du cas 1 + cas 2) :
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S, LIB_ENTITE LS WHERE LS.entite =* S.id_entite AND S.id_entite=1 AND LS.langue = 1036
Ici, une condition va dans la jointure, l’autre dans le filtre... ! Il est clair que l'interface graphiques'y laissera prendre...
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite AND LS.langue = 1036 WHERE S.id_entite = 1
La méthode
J’ai eu à décrire et expliquer la méthode à un développeur (voyons le cas 3) :
1. Identifier les tables et le prédicat de jointure
SELECT S.id_entite, S.code, LS.libelle_court FROM LIB_ENTITE LS RIGHT JOIN ENTITE S ON LS.entite = S.id_entite
2. La tourner en LEFT JOIN rend les choses plus facile à lire (on peut même se servir de l’interface graphique de SSMS pour réécrire cette partie)
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite
3. Ajouter les conditions qui portent sur la table interne dans la clause WHERE
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite WHERE S.id_entite = 1
4. Ajouter les conditions qui portent sur la table externe dans la clause JOIN
SELECT S.id_entite, S.code, LS.libelle_court FROM ENTITE S LEFT JOIN LIB_ENTITE LS ON LS.entite = S.id_entite AND LS.langue = 103 WHERE S.id_entite = 1
Là où ça devient amusant, c’est quand il y a de nombreuses tables et conditions... Pour être sûr de ne pas laisser d'erreurs, testez bien les jeux de résultat !