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
Si je comprends bien, il semble que vos conditions de participation soient l’équivalent deON ((@AddressID IS NOT NULL) AND (alias.column = @AddressID))
et de même pour le groupe rejoindre.
J'utilise cette jointure conditionnelle parfois.
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;)
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)
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)
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
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;
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.
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?
(@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.
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)