2

所以想象一下,你有一个表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)   
4

5 回答 5

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您可以将表值参数传递到存储过程中。

于 2010-10-27T13:18:10.673 回答
0

ProductCategories should have a clustered index on (CategoryId, ProductId) and InvoiceProducts should have one on (InvoiceId, ProductId) optimally. This will allow finding product ids given the CategoryId and InvoiceId by using the data in the clustered indexes only.

You could use a function to return a table of ints given a string. Google "CsvToInt" and click on the first link from SqlTeam to see the code.

Then you could:

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)
    )
于 2010-10-26T04:25:54.193 回答
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
于 2010-10-25T23:00:30.313 回答
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.

这将为您提供一种在多个标准上执行 AND 的方式,并且应该表现良好。

这有任何意义吗?我最近用 CTE 做了一些非常酷的快速的东西,如有必要可以详细说明。

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

于 2010-10-25T23:26:30.010 回答
-1

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

于 2010-10-25T22:50:44.037 回答