2010-10-25 32 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