复杂的 SQL 查询——查找匹配多个不同外键的项目

Posted

技术标签:

【中文标题】复杂的 SQL 查询——查找匹配多个不同外键的项目【英文标题】:Complicated SQL Query--finding items matching multiple different foreign keys 【发布时间】:2010-10-25 22:21:38 【问题描述】:

假设您有一个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)   

【问题讨论】:

您是否尝试过创建一个临时表,填充它,然后进行查询? 对我来说这看起来是一个非常性感的解决方案。您应该将其添加为答案。 @mootinator:是的,这也发生在我身上。当临时桌开始对我来说看起来很性感时,我知道是时候出去看看一些真正的女孩了。 @Quassnoi 大声笑,该评论是针对问题的更新,而不是针对临时表。还是摸摸。 【参考方案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 中,您可以将表值参数传递到存储过程中。

【讨论】:

+1 用于查看不需要加入产品以及对匹配项的联合进行分组。我认为这是我的答案,但我希望有我没有考虑过的操作员。你知道一种方法来介绍我的类别和发票 ID 标准而无需加入吗?子查询是否应该移动到 cte 中? @Andy:什么标准?我的查询根本不包含任何连接 这个查询将是一个存储过程的一部分,所以我唯一知道传入类别和发票 id 的列表是varchar,然后将它们拆分为 cte,这必须是加入其他表。 @Andy:你使用的是哪个版本的SQL Server?在SQL Server 2008 中,您可以直接从客户端传递表变量。 哦,我不知道。我在2008 R2。我会试试看。我需要为他们使用连接吗?或者有没有办法使用in 运算符(不是子查询:where id in (select id from table))?【参考方案2】:

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

【讨论】:

【参考方案3】:

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

你可以使用一个函数来返回一个给定字符串的整数表。谷歌“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)
    )

【讨论】:

不幸的是,我的情况比这更复杂。如果我通过 2 个类别,这将提供属于这两个类别之一的产品。我需要它来告诉我两者都有的产品。我编写了这个查询的修改版本,它按产品 ID 分组并返回的 ID 的计数与我传入的类别数量相匹配,但它的性能似乎很差。【参考方案4】:

递归 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 做了一些非常酷的快速的东西,如有必要可以详细说明。

删除了 cte 代码,因为它是错误的,并且不值得修复有更好的解决方案。

【讨论】:

我最近才发现递归 CTE。不过,我很难让它工作。它抱怨包含左连接的递归部分。 对,我以前也遇到过这个错误。一种解决方法可能是对每个标准使用 cte,而不是像我在这里所做的那样不舒服地把它们塞在一起。【参考方案5】:

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

【讨论】:

以上是关于复杂的 SQL 查询——查找匹配多个不同外键的项目的主要内容,如果未能解决你的问题,请参考以下文章

需要对查找多个外键值的 SQL 查询使用“VLOOKUP”类型的操作 - SOS

在 SQL 中拥有子类的外键的正确方法?

查找最佳类别匹配的 SQL 查询

Django Q AND 查找 - 在复杂查找中查询多个条目

Django 将带有外键的查询集转换为 JSON

Sql Server:创建具有多个外键的表