2009-12-04 8 views
33

J'ai une procédure stockée avec un certain nombre de paramètres. Je voudrais écrire ma requête pour qu'elle se connecte à certaines tables mais seulement si un paramètre particulier a une valeur. Prenons l'exemple suivant: J'ai une table Person. Il existe également une table d'adresses contenant des adresses de personnes et une table de groupes contenant des groupes de personnes. Les deux sont des relations un à plusieurs avec la table Person. Ma procédure stockée a un paramètre @AddressID et un paramètre @GroupID.T-SQL - Comment écrire une jointure conditionnelle

La requête renvoie toujours les champs de la table Person. Si aucun paramètre n'a de valeur, la requête doit renvoyer tous les enregistrements de la table Person. Si le paramètre @AddressID est fourni, il doit retourner uniquement les enregistrements qui ont un enregistrement correspondant dans la table Address et ignorer la table Groups. Si le paramètre @GroupID est fourni, il doit retourner uniquement les enregistrements qui ont un enregistrement correspondant dans la table Groups et ignorer la table Addresses. Si les deux paramètres sont fournis, il ne doit afficher que les enregistrements ayant un enregistrement correspondant dans les deux tables. Avoir du sens?

Existe-t-il un moyen simple de faire cela qui me manque?

Merci, Corey

+0

Merci pour vos questions. Personne d'autre n'a. Ceci est très utile lors de l'utilisation de LEFT JOIN par exemple. – Jorge

Répondre

34

Si je comprends bien il semble que vos conditions de jointure seraient l'équivalent de
ON ((@AddressID IS NOT NULL) AND (alias.column = @AddressID)) et de même pour le groupe rejoindre.

J'utilise cette jointure conditionnelle à certains moments.

+0

Parfait! Travaillé comme un charme. Merci beaucoup. –

+0

sympa! ......... – fearofawhackplanet

+0

Ça pourrait être si simple parfois! Merci * facepalm * – Shion

7

Oui, c'est très simple. Faites des jointures à gauche sur l'adresse et les groupes. Puis dans la clause where ...

(@group_id is null or g.group_id = @group_id) 
and (@address_id is null or a.address_id = @address_id) 
+1

Cela donnera la bonne réponse, mais la performance sera lente avec des conditions OR dans vos jointures. –

2

Vous devriez être en mesure de développer ce ...

DECLARE @SQL varchar(max) 

    SET @SQL = 'SELECT * FROM PERSON P' 

    IF NULLIF(@ADDRESSID,"") IS NULL SET @SQL = @SQL + " INNER JOIN ADDRESSES A ON P.AddressID = A.AddressID" 

    EXEC sp_executesql @SQL, N'@ADDRESSID int', @ADDRESSID 
+0

@Paul - Ne faites pas ceci – JonH

+0

Pourquoi pas? SQL dynamique est génial! –

+7

Non, c'est le propre spawn du diable la plupart du temps et ne devrait être utilisé qu'en l'absence d'une réelle alternative. Simplement essayer de sauvegarder une ligne de SQL ne compte pas. Non seulement cela, mais votre SQL dynamique va générer un plan de requête moins efficace, complique grandement les mises à niveau ou l'audit, et surtout vous laisse vulnérable aux attaques par injection SQL. La plupart du temps c'est pour les paresseux. – MartW

0

Ce qui est affiché Quntin agréable, mais il y a quelques problèmes de performance avec elle. Croyez-le ou non ce qui est plus rapide est de vérifier chaque paramètre et écrire le code SQL Rejoignez basé sur le cas

En plus:

IF @AddressParameter IS NOT NULL 
BEGIN 
SELECT blah1, blah2 FROM OneTable INNER JOIN AddressTable WHERE .... 
-more code 
END 
ELSE... 
BEGIN 
END 
... 

Une autre chose que vous pouvez faire est effectuer les jointures et dans la requête filtre (la clause where) vous pouvez faire:

WHERE 
(Address = @Address OR @Address IS NULL) 

la performance est ombragée et ici.

+0

Eh bien cela ne fonctionnera pas vraiment dans cette situation. J'ai environ 9 paramètres qui peuvent être ou ne pas être nul. Les combinaisons possibles de tous ces différents paramètres seraient comme 9 factoriel ou un nombre énorme. –

+0

Je ne suis pas sûr de comprendre ce que vous voulez dire Corey. Tout ce que vous feriez est de gérer cela dans le WHERE pour n Nombre de paramètres. En outre, WHERE ((Adresse = @Address OU @Address IS NULL) ET (Groupe = @Group OU @Group IS NULL) ET (...)) – JonH

+0

Une jointure simple exclura les enregistrements. Il doit être une jointure à gauche. – RayLoveless

22

Les moyens simples sont actuellement pas de bonnes solutions. Aussi mauvais que cela puisse paraître, la meilleure solution est d'avoir explicitement IF dans le code et des requêtes distinctes:

IF (condition) 
    SELECT ... FROM Person WHERE ... 
ELSE IF (otherCondition) 
    SELECT ... FROM Person JOIN ... ON ... WHERE ... 
ELSE IF (moreCondition) 
    SELECT ... FROM Persons JOIN ... JOIN ... WHERE ... 

La raison est que si vous essayez de construire une seule requête qui correspond à tous les trois (ou plus) conditions alors le moteur doit produire un seul plan de requête qui fonctionne en toutes les conditions. Dans T-SQL, une instruction est égale à un plan. Rappelez-vous que les plans sont créés pour le cas générique, pour toute valeur de variable, donc le résultat est toujours un très, très mauvais plan.

Bien que ce soit contre-intuitif et semble être une solution horrible à tout programmeur, c'est ainsi que fonctionnent les bases de données. La raison pour laquelle ce n'est pas un problème dans 99,99% des cas est qu'après avoir essayé ce que vous demandez et voir ce qu'il faut faire, les développeurs reviennent rapidement à la raison et révisent leurs exigences afin qu'ils n'aient jamais à lancer des requêtes. en fonction des valeurs des variables d'exécution;)

+2

LINQ2SQL est, peut-être étonnamment, une meilleure solution pour quelque chose comme ceci, en raison de la nature des expressions de requête étant des objets à part entière qui peuvent être manipulés et affinés, voir http://stackoverflow.com/questions/1849005/converting-conditional- build-sql-where-clause-into-linq/1849041 # 1849041 –

+1

C'est bien si vous avez des critères mutuellement exclusifs, ce qui n'est pas le cas de la plupart des écrans de recherche. Et cela augmente les frais de maintenance - si un JOIN change, vous devez apporter les modifications à toutes les copies. Le SQL dynamique est la solution la plus réaliste dans de telles situations. –

+0

@Pony: Oui, le SQL dynamique est meilleur pour les conditions complexes. –

0

joindre les trois tables ensemble et utiliser quelque chose comme ça dans votre clause WHERE:

WHERE Addresses.ID = COALESCE(@AddressID, Addresses.ID) 
AND Groups.ID = COALESCE(@GroupID, Groups.ID) 
+0

Une jointure simple exclura les enregistrements. Il doit être un joint gauche je crois. – RayLoveless

0

Euh, probablement tous que vous avez résolu ce jusqu'à présent. Comme je vous comprends, vous voulez avoir une requête 'dynamique', pour joindre la table si le paramètre existe, ou pour omettre la jointure si le paramètre est null. Le secret utilise la jointure externe gauche. Comme:

SELECT p.* 
FROM Parent AS p 
LEFT OUTER JOIN Child AS c ON p.Id = c.ParentId 
WHERE 
     (@ConditionId IS NULL OR c.ConditionId = @ConditionId) 

Comment ça marche? Si le paramètre de filtre @ConditionId est nul, alors il n'y a pas d'enfant pour la jointure externe, et le résultat aura tous les parents.

  • Si le paramètre de filtre @ConditionId n'est pas nul, la jointure externe rejoindra Child avec ce parent, et la condition (@ConditionId IS NULL OR c.ConditionId = @ConditionId) rejettera les parents qui n'ont pas rejoint Child's avec la condition c.ConditionId = @ConditionId. LEFT OUTER JOIN a certainement des problèmes de performance, mais autant que cela fonctionne rapidement, je ne veux pas concaténer les chaînes pour créer une requête.

  • 3

    Voilà comment j'avais fait pour mon cas.

    
    DECLARE 
        @ColorParam varchar(500) 
    
    SET 
        @ColorParam = 'red, green, blue' 
    
    declare @Colors table 
    (
        Color NVARCHAR(50) PRIMARY KEY 
    ) 
    
    -- populate @Colors table by parsing the input param, 
    -- table can be empty if there is nothing to parse, i.e.: no condition 
    INSERT @Colors SELECT Value FROM dbo.Splitter(@ColorParam, ',') 
    
    SELECT 
        m.Col1, 
        c.Color 
    FROM 
        MainTable AS m 
    FULL JOIN -- instead of using CROSS JOIN which won't work if @Colors is empty 
        @Colors AS c 
    ON 
        1 = 1 -- the trick 
    WHERE 
        (@ColorParam IS NULL OR c.Color = m.Color) 
    
    +0

    La jointure complète renverra une série de zéros dans les colonnes de la table principale où les ID ne correspondent pas. U a besoin d'une jointure à gauche à la place. – RayLoveless

    0

    jointure gauche et où la clause devrait faire l'affaire:

    SELECT Customers.CustomerName, Customers.Country, Orders.OrderID 
        FROM Customers 
        LEFT JOIN Orders 
        ON Customers.CustomerID=Orders.CustomerID 
        WHERE Country= @MyOptionalCountryArg or @MyOptionalCountryArg is null; 
    
    Questions connexes