2010-10-25 69 views
2

想象一下,您有一个表Products (ID int, Name nvarchar(200)),以及另外两个表,ProductsCategories (ProductID int, CategoryID int)InvoiceProducts (InvoiceID int, ProductID int)复杂的SQL查询 - 查找与多个不同外键匹配的项目

我需要编写一个查询来生成一组匹配给定的发票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) 
+1

您是否尝试过创建临时表,填充它,然后执行查询? – BobbyShaftoe 2010-10-25 22:29:37

+0

这看起来像一个非常性感的解决方案给我。你应该添加这个答案。 – 2010-10-27 14:45:39

+0

@mootinator:是的,这也发生在我身上。当临时表开始看起来很性感时,我知道是时候出去看看一些真正的女孩。 – Quassnoi 2010-10-27 15:00:56

回答

1

前提是你必须在两个(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可以将表值参数传递给存储过程。

+0

+1是因为看到加入产品并不是必要的,而且不需要对比赛联盟进行分组。我想这是我的答案,但我希望有一些我没有考虑过的操作员。你知道有一种方法来介绍我的类别标准和没有联接的发票ID吗?子查询是否应该移入cte? – 2010-10-27 13:43:40

+0

@安迪:哪个标准?我的查询根本不包含任何连接 – Quassnoi 2010-10-27 13:45:33

+0

此查询将成为存储过程的一部分,所以我唯一知道要在类别和发票ID列表中传递的内容是'varchar',然后将它们拆分为cte,这将会有在其他桌子上加入。 – 2010-10-27 13:47:58

-1

将它们作为XML参数传递,将它们存储到临时表并加入。

0

我会从这样的事情开始,利用参数中的表格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 
0

递归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. 

这会给你表演上多标准和办法,并应表现良好。

这是否有任何意义呢?最近我用CTE做了一些非常酷的快速内容,并且可以在必要时进行阐述。

删除cte代码,因为它是错误的,并不真正值得去解决那里有更好的解决方案。

+0

我最近才发现递归CTE的。尽管如此,我仍然遇到了麻烦。它抱怨包含左连接的递归部分。 – 2010-10-27 13:16:03

+0

对,我甚至遇到过这个错误。一种解决方法可能就是针对每个标准使用一个cte,而不是像我在这里所做的那样不舒服地将它们塞到一起。 – 2010-10-27 14:40:55

0

ProductCategories应该在(CategoryId,ProductId)上有一个聚集索引,而InvoiceProducts应该有一个(InvoiceId,ProductId)最佳。这将允许通过仅使用聚集索引中的数据来查找给定CategoryId和InvoiceId的产品ID。

您可以使用函数返回给定字符串的整数表。 Google“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) 
    ) 
+0

我的情况比那个不幸更复杂。如果我通过两个类别,这将使产品属于两个类别之一。我需要它告诉我这两个产品。我已经编写了这个查询的一个修改版本,它按产品ID和返回的ID进行了分组,这些ID的数量与我通过的类别数量相匹配,但它看起来好像性能很差。 – 2010-10-27 12:52:42