web-dev-qa-db-fra.com

T-SQL - Comment écrire une jointure conditionnelle

J'ai une procédure stockée avec un certain nombre de paramètres. J'aimerais écrire ma requête afin qu'elle se connecte à certaines tables, mais uniquement si un paramètre particulier a une valeur. Prenons l'exemple suivant: j'ai une table de personne. Il existe également une table d'adresses contenant les adresses de personnes et une table de groupes contenant des groupes de personnes. Les deux sont des relations un à plusieurs avec la table Personne. Ma procédure stockée a un paramètre @AddressID et un paramètre @GroupID.

La requête renvoie toujours uniquement les champs de la table Personne. Si aucun paramètre n'a de valeur, la requête doit renvoyer tous les enregistrements de la table Personne. Si le paramètre @AddressID est fourni, il ne doit renvoyer que les enregistrements pour lesquels un enregistrement correspondant correspond à la table Address et pour ignorer la table Groups. Si le paramètre @GroupID est fourni, il ne doit renvoyer que les enregistrements pour lesquels un enregistrement correspondant correspond à la table Groupes et ignorer la table Adresses. Si les deux paramètres sont fournis, seuls les enregistrements ayant un enregistrement correspondant dans les deux tables doivent être affichés. Avoir un sens?

Y at-il un moyen simple de faire cela qui me manque?

Merci, Corey

41
Corey Burnett

Si je comprends bien, il semble que vos conditions de participation soient 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 parfois.

42
Quintin Robinson

Les moyens les plus simples ne sont en réalité pas de bonnes solutions. Aussi mauvais que cela puisse paraître, la meilleure solution consiste à avoir un IF explicite 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 en est que si vous essayez de créer une requête unique correspondant aux trois conditions (ou plus), le moteur doit générer un plan de requête single fonctionnant dans des conditions all. Dans T-SQL, une instruction est égale à un plan. Rappelez-vous que les plans sont créés pour le cas générique, pour la valeur de variable any, de sorte que le résultat est toujours un très, très mauvais plan.

Bien que ce soit contre-intuitif et que cela ressemble à une solution horrible pour tout programmeur, voici comment fonctionnent les bases de données. La raison pour laquelle ce n'est pas un problème 99.99% des cas est qu'après avoir essayé ce que vous demandez et vu ce qu'il fallait faire, les développeurs ont rapidement retrouvé leur sens et révisé leurs exigences pour ne jamais avoir à exécuter des requêtes auxquelles ils peuvent éventuellement adhérer basé sur les valeurs de variables d'exécution;)

24
Remus Rusanu

Oui, c'est très simple. Ne gauche joint 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)
7
dotjoe

C'est ce que 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)
    
3
Irawan Soetomo

Vous devriez pouvoir développer ceci ...

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
3
Paul Creasey

Left join et où 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;
0
RayLoveless

Ce que Quntin a écrit est agréable, mais il y a des problèmes de performances avec ce dernier.

En outre:

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 d’effectuer les jointures et dans le filtre de requête (la clause where) Vous pouvez faire:

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

La performance ici est louche aussi.

0
JonH

Euh, vous avez probablement tous résolu ce problème jusqu'à présent.

Si je vous ai bien compris, vous souhaitez avoir une requête 'dynamique', joindre une table si le paramètre existe ou omettre join si le paramètre est null . 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 null, 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 null, la jointure externe joindra Child's à ce parent et la condition (@ConditionId IS NULL OR c.ConditionId = @ConditionId) éliminera les parents qui n'ont pas rejoint Child avec la condition c.ConditionId = @ConditionId.

LEFT OUTER JOIN a certainement un problème de performances, mais autant que cela fonctionne rapidement, je ne veux pas concaténer une chaîne pour créer une requête.

0
vllado2

Joignez les trois tables ensemble et utilisez quelque chose comme ceci dans votre clause WHERE:

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