web-dev-qa-db-fra.com

Clause WHERE conditionnelle T-SQL

J'ai trouvé quelques questions similaires ici à ce sujet, mais je n'ai pas pu trouver comment appliquer à mon scénario.

Ma fonction a un paramètre appelé @ IncludeBelow. Les valeurs sont 0 ou 1 (BIT).

J'ai cette requête:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue

Si @IncludeBelow vaut 0, j'ai besoin que la requête soit la suivante:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue
AND   p.LocationType = @LocationType -- additional filter to only include level.

Si @IncludeBelow vaut 1, cette dernière ligne doit être exclue. (c'est-à-dire que vous n'appliquez pas de filtre).

Je suppose que cela doit être une instruction CASE, mais je ne peux pas comprendre la syntaxe.

Voici ce que j'ai essayé:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue
AND (CASE @IncludeBelow WHEN 0 THEN p.LocationTypeId = @LocationType ELSE 1 = 1)

Évidemment, ce n'est pas correct.

Quelle est la syntaxe correcte?

28
RPM1984

J'ai changé la requête pour utiliser EXISTS car s'il y a plus d'un emplacement associé à un POST, il y aurait des enregistrements en double POST qui nécessiteraient une clause DISTINCT ou GROUP BY pour se débarrasser. ..

Le non-sargable

Cela effectuera la pire des solutions possibles:

SELECT p.*
  FROM POSTS p
 WHERE EXISTS(SELECT NULL
                FROM LOCATIONS l
               WHERE l.LocationId = p.LocationId
                 AND l.Condition1 = @Value1
                 AND l.SomeOtherCondition = @SomeOtherValue)
   AND (@IncludeBelow = 1 OR p.LocationTypeId = @LocationType)

La version sargable et non dynamique

Auto-explicatif ....

BEGIN
  IF @IncludeBelow = 0 THEN
    SELECT p.*
      FROM POSTS p
     WHERE EXISTS(SELECT NULL
                    FROM LOCATIONS l
                   WHERE l.LocationId = p.LocationId
                     AND l.Condition1 = @Value1
                     AND l.SomeOtherCondition = @SomeOtherValue)
       AND p.LocationTypeId = @LocationType
  ELSE
    SELECT p.*
      FROM POSTS p
     WHERE EXISTS(SELECT NULL
                    FROM LOCATIONS l
                   WHERE l.LocationId = p.LocationId
                     AND l.Condition1 = @Value1
                     AND l.SomeOtherCondition = @SomeOtherValue) 
END

La version dynamique et sargable (SQL Server 2005+):

Que vous l'aimiez ou le détestiez, le SQL dynamique vous permet d'écrire la requête une seule fois. Sachez simplement que sp_executesql met en cache le plan de requête, contrairement à EXEC dans SQL Server. Je recommande vivement de lire La malédiction et les bénédictions de Dynamic SQL avant d'envisager le SQL dynamique sur SQL Server ...

DECLARE @SQL VARCHAR(MAX)
    SET @SQL = 'SELECT p.*
                  FROM POSTS p
                 WHERE EXISTS(SELECT NULL
                                FROM LOCATIONS l
                               WHERE l.LocationId = p.LocationId
                                 AND l.Condition1 = @Value1
                                 AND l.SomeOtherCondition = @SomeOtherValue)'

    SET @SQL = @SQL + CASE 
                        WHEN @IncludeBelow = 0 THEN
                         ' AND p.LocationTypeId = @LocationType '
                        ELSE ''
                      END   

BEGIN 

  EXEC sp_executesql @SQL, 
                     N'@Value1 INT, @SomeOtherValue VARCHAR(40), @LocationType INT',
                     @Value1, @SomeOtherValue, @LocationType

END
38
OMG Ponies

Vous pouvez l'écrire comme

SELECT  p.*
  FROM  Locations l
INNER JOIN Posts p
    ON  l.LocationId = p.LocationId
  WHERE l.Condition1 = @Value1
    AND l.SomeOtherCondition = @SomeOtherValue
    AND ((@IncludeBelow = 1) OR (p.LocationTypeId = @LocationType))

qui est un modèle que vous voyez beaucoup par exemple pour les paramètres de recherche facultatifs. Mais l'IIRC peut perturber les plans d'exécution des requêtes, il peut donc y avoir une meilleure façon de le faire.

Puisqu'il ne s'agit que d'un peu, il pourrait presque être utile de choisir entre deux blocs de SQL avec ou sans vérification, par ex. en utilisant un IF dans une procédure stockée ou avec différentes chaînes de commande dans le code appelant, basé sur le bit?

11
Rup

Vous pouvez changer votre instruction CASE en ceci. Le planificateur de requêtes voit cela différemment, mais ce n'est peut-être pas plus efficace que d'utiliser OR:

(p.LocationTypeId = CASE @IncludeBelow WHEN 0 THEN p.LocationTypeId ELSE @LocationType END)
3
Hai Phan

Modifiez l'instruction sql comme suit:

SELECT p.*
FROM Locations l
    INNER JOIN Posts p
    on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
    AND l.SomeOtherCondition = @SomeOtherValue
    AND l.LocationType like @LocationType

La variable @IncludeBelow n'est pas nécessaire

Pour inclure tous les types d'emplacement Définissez @LocationType = '%'

Pour limiter les types d'emplacement renvoyés par la requête Définissez @LocationType = '[Un type d'emplacement spécifique]'

Les instructions Set ci-dessus supposent que la variable @LocationType est un type de données de caractère

0
Rich Capp