Compliqué de requêtes SQL - éléments de constatation correspondant à plusieurs différentes clés étrangères
-
26-09-2019 - |
Question
Alors, imaginez que vous avez une table de Products (ID int, Name nvarchar(200))
, et deux autres tables, ProductsCategories (ProductID int, CategoryID int)
et InvoiceProducts (InvoiceID int, ProductID int)
.
Je dois écrire une requête pour produire un ensemble de produits qui répondent à un ensemble de ids de facture et catégorie ids tels que la liste des produits correspondant à toutes les catégories spécifiées et toutes les factures spécifiées, sans tomber à SQL dynamique. Imaginez que je dois trouver une liste de produits qui sont dans les deux catégories 1 et 2 et dans les factures 3 et 4.
Pour commencer, j'ai écrit une procédure stockée qui accepte la catégorie ids et ids de la facture sous forme de chaînes, et les analyser dans les tables:
CREATE PROCEDURE dbo.SearchProducts (@categories varchar(max), @invoices varchar(max))
AS BEGIN
with catids as (select cast([value] as int) from dbo.split(@categories, ' ')),
invoiceids as (select cast([value] as int) from dbo.split(@invoices, ' '))
select * from products --- insert awesomeness here
END
Les différentes solutions que je suis venu avec un regard terrible, et de moins bons résultats. La meilleure chose que j'ai trouvé est de générer une vue composée de gauche rejoint de tous les critères, mais qui semble très cher et ne résout pas la question de faire correspondre toutes les différentes touches spécifiées.
Mise à jour: Ceci est un exemple de requête que j'ai écrit que les rendements les résultats escomptés. Est-ce que je manque des possibilités d'optimisation? Comme les opérations de matrice licorne magique par ninjas?
with catids as (select distinct cast([value] as int) [value] from dbo.split(@categories, ' ')),
invoiceids as (select distinct cast([value] as int) [value] from dbo.split(@invoices, ' '))
select pc.ProductID from ProductsCategories pc (nolock)
inner join catids c on c.value = pc.CategoryID
group by pc.ProductID
having COUNT(*) = (select COUNT(*) from catids)
intersect
select ip.ProductID from InvoiceProducts ip (nolock)
inner join invoiceids i on i.value = ip.InvoiceID
group by ip.ProductID
having COUNT(*) = (select COUNT(*) from invoiceids)
La solution
A condition que vous avez des indices uniques sur les deux (ProductID, CategoryID)
et (ProductID, InvoiceID)
:
SELECT ProductID
FROM (
SELECT ProductID
FROM ProductInvoice
WHERE InvoiceID IN (1, 2)
UNION ALL
SELECT ProductID
FROM ProductCategory pc
WHERE CategoryID IN (3, 4)
) q
GROUP BY
ProductID
HAVING COUNT(*) = 4
ou, si vos valeurs sont transmises dans les chaînes de CSV
:
WITH catids(value) AS
(
SELECT DISTINCT CAST([value] AS INT)
FROM dbo.split(@categories, ' '))
),
(
SELECT DISTINCT CAST([value] AS INT)
FROM dbo.split(@invoices, ' '))
)
SELECT ProductID
FROM (
SELECT ProductID
FROM ProductInvoice
WHERE InvoiceID IN
(
SELECT value
FROM invoiceids
)
UNION ALL
SELECT ProductID
FROM ProductCategory pc
WHERE CategoryID IN
(
SELECT value
FROM catids
)
) q
GROUP BY
ProductID
HAVING COUNT(*) =
(
SELECT COUNT(*)
FROM catids
) +
(
SELECT COUNT(*)
FROM invoiceids
)
Notez que dans SQL Server 2008
vous pouvez passer des paramètres table dans les procédures stockées.
Autres conseils
Je commence par quelque chose comme cela, en utilisant vos valeurs d'ID Dépôt des paramètres. tables temporaires peuvent aider à la vitesse de sous-requête.
select p.*
from
(
select pc.*
from catids c
inner join ProductsCategories pc
on pc.CategoryID = c.value
) catMatch
inner join
(
select pin.*
from invoiceids i
inner join ProductsInvoices pin
on pin.InvoiceID = i.value
) invMatch
on invMatch.ProductID = catMatch.ProductID
inner join Products p
on p.ID = invMatch.ProductID
devrait avoir un catégories de produit index ordonné en clusters sur (CategoryId, ProductId) et InvoiceProducts devrait avoir un sur (InvoiceID, ProductId) de façon optimale. Cela permettra de trouver ids de produits et compte tenu de la CategoryId InvoiceID en utilisant les données dans les index en cluster uniquement.
Vous pouvez utiliser une fonction pour retourner une table de ints d'une chaîne. Google « CsvToInt » et cliquez sur le premier lien de SqlTeam pour voir le code.
Ensuite, vous pouvez:
SELECT *
FROM Products
WHERE ID IN (SELECT DISTINCT ProductId
FROM ProductCategories
WHERE CategoryId in dbo.CsvToInt(@categories)
) AND ID IN (SELECT DISTINCT ProductId
FROM InvoiceProducts
WHERE InvoiceId in dbo.CsvToInt(@invoices)
)
Que diriez-vous d'un CTE récursive?
d'abord ajouter des numéros de ligne aux tableaux de critères, certains SQL pseudo si vous voulez:
;WITH cte AS(
Base case: Select productid, criteria from products left join criteria where row_number = 1 if it matches criteria from both row 1s or one is null.
UNION ALL
Recursive case: Select n+1 criteria row from products left join criteria where row_number = cte.row_number + 1 AND matches criteria from both row_number + 1 or one or the other (but not both) is null
)
SELECT *
WHERE criteria = maximum id from criteria table.
Cela vous donnera un moyen d'exécution et sur des critères multiples, et devrait fonctionner correctement.
Est-ce aucun sens du tout? Je l'ai fait un peu refroidir assez vite des choses avec CTEs ces derniers temps, et peut élaborer si nécessaire.
Removed code cte parce qu'il était mal, et pas vraiment la peine de fixation ayant une bien meilleure solution là-bas.
les passer en tant que paramètre XML, les stocker dans une table temporaire et rejoindre.