Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

SQL Server : Pourquoi @toto IS NULL OR Champ = @toto pose problème ou l’intérêt du SQL dynamique

Prenons le cas d’une table ayant 5 champs :

 

CREATE TABLE [dbo].[MaTable](

      [Id] [int] IDENTITY(1,1) PRIMARY KEY,

      [Nom] [varchar](50) NOT NULL,

      [Param1] [int] NULL,

      [Param2] [int] NULL,

      [Param3] [int] NULL

)

GO

 

Je positionne un index sur chacun des champs ParamX :

 

CREATE INDEX Index1 ON MaTable(Param1)

CREATE INDEX Index2 ON MaTable(Param2)

CREATE INDEX Index3 ON MaTable(Param3)

 

J’ai une procédure stockée ayant 3 paramètre elle aussi : @P1, @P2 et @P3 dont la valeur par défaut est NULL et je souhaite pouvoir rechercher les enregistrements de ma table tel que Param1 = @P1, etc. si le paramètre correspondant est bien passé dans ma procédure stockée. Ce qui donne le code suivant, si l’un des paramètres est à NULL on a la condition complète qui évaluée à vrai grâce à la condition logique OR indiqué et on ignore ainsi le paramètre.

 

CREATE PROC [dbo].[MaProc] @P1 int = NULL, @P2 int = NULL, @P3 int = NULL

AS

 

SELECT *

  FROM [Database1].[dbo].[MaTable]

WHERE (@P1 IS NULL OR Param1 = @P1)

  AND (@P2 IS NULL OR Param2 = @P2)

  AND (@P3 IS NULL OR Param3 = @P3)

GO

 

Cette construction est assez logique, mais une surprise nous attend lors de l’exécution. Je charge cette table avec 500 000 enregistrements avec des valeurs uniques et j’exécute tout simplement :

 

-- Nombre de pages lues 871

EXEC dbo.MaProc 1

 

Résultat 871 pages lues (alors que la table fait 3394 pages), ce qui est énorme et représente la taille totale de Index1. Pour s’en convaincre jetez un coup d’œil au plan d’exécution qui est un Index Scan non ordonnée et complet sur l’index Index1, là où idéalement on devrait avoir une recherche sur Index1 ! Essayons maintenant sur @P2 :

 

-- Nombre de pages lues 1500871

EXEC dbo.MaProc @P2 = 1

 

Ici encore plus, ceci est du au comportement de la mise en cache du plan d’exécution et sa réutilisation. N’oubliez pas que c’est une procédure stockées et à ce titre le moteur reprend « bêtement » le précédent plan qui utilise le mauvais index :o(

 

Pourquoi ce comportement ? On pourrait croire que la requête est formulée correctement, eh bien non cet IS NULL OR est à proscrire du code SQL car engendre des dégradations très importantes. Une autre formulation à bannir est ChampA IS NOT NULL AND ChampA = X qui pose des problèmes similaires en termes de performances.

 

Quelles sont les alternatives ?

 

Branchement multiples :

 

ALTER PROC [dbo].[MaProc] @P1 int = NULL, @P2 int = NULL, @P3 int = NULL

AS

 

IF @P1 IS NOT NULL

      SELECT *

        FROM [Database1].[dbo].[MaTable]

      WHERE Param1 = @P1

 

IF @P2 IS NOT NULL

      SELECT *

        FROM [Database1].[dbo].[MaTable]

      WHERE Param2 = @P2

       

IF @P3 IS NOT NULL

      SELECT *

        FROM [Database1].[dbo].[MaTable]

      WHERE Param3 = @P3

 

GO

 

Cette requête n’est pas un bon remplaçant de ma précédente, mais c’est pur l’exemple et limiter le nombre de lignes de codes. Je teste la présence du paramètre dans les blocs IF, si il est passé je l’utilise dans la bonne requête.

 

-- Nombre de pages lues 3

EXEC dbo.MaProc 1

 

-- Nombre de pages lues 3

EXEC dbo.MaProc @P2 = 1

 

Et là le plan est correct dans les 2 cas… On a bien une recherche sur Index1 dans le premier et sur Index2 dans le second, soit une requête 500 000 fois plus performante que dans le pire des cas précédent.

 

SQL Dynamique :

 

ALTER PROC [dbo].[MaProc] @P1 int = NULL, @P2 int = NULL, @P3 int = NULL

AS

 

      DECLARE @param nvarchar(500)

      DECLARE @sql nvarchar(max)

 

      set @param = N''

      set @sql = N'SELECT * FROM [Database1].[dbo].[MaTable] WHERE 1 = 1 '

 

      IF @P1 IS NOT NULL

            SET @param = @param + N' AND Param1 = ' + CAST(@P1 as nvarchar(12))

 

      IF @P2 IS NOT NULL

            SET @param = @param + N' AND Param2 = ' + CAST(@P2 as nvarchar(12))

 

      IF @P3 IS NOT NULL

            SET @param = @param + N' AND Param3 = ' + CAST(@P3 as nvarchar(12))

 

      SET @sql = @sql + @param

     

      EXEC(@sql)

 

GO

 

Cette fois ci la requête remplace à 100% celle de départ, on concatène une chaîne qui est la requête à exécuter avec son paramètre. Regardons maintenant les performances :

 

-- Nombre de pages lues 3

EXEC dbo.MaProc 1, 2, 3

 

Dans tous les cas, que vous passiez 1 ou plusieurs paramètres le nombre pages lues est toujours de 3 et le plan utilise la recherche sur les bons index. On a ici le résultat optimal… Bon ok je peux faire encore un peu mieux avec un sp_executesql au lieu du EXEC(), mais pour rester concis cette requête là ira bien.

 

L’intérêt du SQL dynamique est ici énorme il vous évite la création d’une procédure qui aurait due tester toutes les combinaisons de passage de paramètre, qui croissent en fonction du nombre de ce dernier. On n’aura donc ici pas d’autres choix que de générer la requête qui va bien !

 

Bonnes requêtes…

 

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Publié vendredi 25 avril 2008 16:32 par christian

Commentaires

lundi 28 avril 2008 09:48 by KooKiz

# re: SQL Server : Pourquoi @toto IS NULL OR Champ = @toto pose problème ou l’intérêt du SQL dynamique

Ça donne à réfléchir. J'ai toujours utilisé la structure "IS NULL OR ..." pour faire des requêtes plus simples et plus lisible, mais je sens que je vais jeter un oeil sur les impacts en  termes de performances :|

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [WPF] Comment déclencher un évènement sur un contrôle ? par Thomas Lebrun le il y a 1 heure et 17 minutes

- [Expression Web] Astuce de la Semaine : Utilisation et Configuration des Extraits de Code par Chronos, Blog d'un Intégrateur .NET le il y a 16 heures et 36 minutes

- Faire de l'AJAX sans restrictions de domaine par Kévin Gosse le il y a 19 heures et 5 minutes

- [IronPython] : IronPython & Silverlight 2 - Part II par Kim's Blog le 07-22-2008, 14:50

- [WPF] Des requêtes NDepend pour analyser vos projets WPF par Thomas Lebrun le 07-21-2008, 09:27

- Liste de jeux pour Silverlight par Pierrick's Blog le 07-20-2008, 14:37

- T_PAAMAYIM_NEKUDOTAYIM par MadMatt le 07-19-2008, 16:16

- Et je mets le son.... par Pierrick's Blog le 07-19-2008, 12:09

- SharePoint : Comment interdire l’accès à un utilisateur pour tous les sites d’une Web Application par Blog Technique de Romelard Fabrice le 07-18-2008, 19:05

- VPC - Reset de la position de la console par Blog technique de Nicolas Boonaert le 07-18-2008, 16:29