質問

次のテーブルがあると想像してください。 Products (ID int, Name nvarchar(200)), 、および他の 2 つのテーブル、 ProductsCategories (ProductID int, CategoryID int) そして InvoiceProducts (InvoiceID int, ProductID int).

動的 SQL にフォールバックせずに、製品のリストが指定されたすべてのカテゴリとすべての指定された請求書に一致するように、指定された一連の請求書 ID とカテゴリ ID に一致する一連の製品を生成するクエリを作成する必要があります。カテゴリ 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 テーブル値パラメータをストアド プロシージャに渡すことができます。

他のヒント

パラメータからテーブル化された 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 には (CategoryId、ProductId) のクラスター化インデックスが必要であり、InvoiceProducts には (InvoiceId、ProductId) のクラスター化インデックスが必要です。これにより、クラスター化インデックス内のデータのみを使用して、CategoryId と InvoiceId が指定された製品 ID を検索できるようになります。

関数を使用して、文字列を指定して int のテーブルを返すことができます。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)
    )

再帰的 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 コードは間違っており、修正する価値がないため削除しました。はるかに優れたソリューションが存在します。

それらを XML パラメータとして渡し、一時テーブルに保存して結合します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top