复杂的SQL查询 - 发现项匹配多个不同的外键
-
26-09-2019 - |
题
所以,想象一下,你有Products (ID int, Name nvarchar(200))
的表,和其他两个表,ProductsCategories (ProductID int, CategoryID int)
和InvoiceProducts (InvoiceID int, ProductID int)
。
我需要编写一个查询,以产生一组匹配一组给定的发票ID和类别ID的产品,使得产品清单满足指定的所有类别和所有指定的发票,没有回落到动态SQL。想象我需要找到在两个类别1和2以及在发票3和4的产品的列表。
作为开始,我已经写了一个存储过程接受该类别ID和发票ID作为字符串,并将其解析为表:
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
在不同的解决方案,我想出来的样子可怕,而表现较差。我找到的最好的事情是生成由左视图的所有标准连接,但似乎非常昂贵,并不能解决所有匹配指定的不同的密钥的问题。
<强>更新强>这是一个示例查询我写产生预期的结果。我失去了任何优化的机会?像由忍者神奇独角兽矩阵运算?
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)
解决方案
只要你有两个(ProductID, CategoryID)
和(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
或者,如果您的值在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
)
注意,在SQL Server 2008
可以传递表值参数到存储过程。
其他提示
我会跟这样的事情做起,从参数利用你的表D ID值。临时表可以与子查询速度帮助。
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
ProductCategories应该对(类别ID,产品编号)和InvoiceProducts聚簇索引应该有一个上(InvoiceId,产品编号)最佳。这将允许通过在聚簇索引的使用数据找到给出的类别ID和InvoiceId产品ID仅
您可以使用函数返回给定的字符串INTS的表。谷歌“CsvToInt”,然后点击从SqlTeam第一个链接上看到的代码。
然后,您可以:
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)
)
如何递归CTE?
首先添加行号的标准表,那么一些伪SQL如果你会:
;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.
这会给你执行多个条件的AND的一种方式,并且应当表现良好。
这是否让所有任何意义?我做了一些很酷的东西,快与最近热膨胀系数,并在必要时详细说明。
删除了CTE的代码,因为它是错误的,不值得有固定在那里。一个更好的解决方案的
将它们作为XML参数,将其存储到一个临时表,并加入。