Frage

So vorstellen, dass Sie eine Tabelle von Products (ID int, Name nvarchar(200)) haben, und zwei weitere Tabellen, ProductsCategories (ProductID int, CategoryID int) und InvoiceProducts (InvoiceID int, ProductID int).

Ich brauche eine Abfrage zu schreiben, um eine Reihe von Produkten zu erzeugen, die einen bestimmten Satz von Rechnungs ids und Kategorie-IDs entsprechen, so dass die Liste der Produkte, alle angegebenen Kategorien und alle angegebenen Rechnungen entsprechen, ohne auf dynamische SQL zurückzufallen. Stellen Sie sich vor ich brauche eine Liste von Produkten zu finden, die in den beiden Kategorien 1 und 2 und in Rechnungen 3 und 4.

Als Anfang Ich habe eine gespeicherte-Prozedur geschrieben, dass die Kategorie-IDs und Rechnung ids als Strings akzeptieren und analysieren sie in Tabellen:

 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

Die verschiedenen Lösungen, die ich mit Blick habe kommen schrecklich und schlechter abschneiden. Das Beste, was ich gefunden habe, ist eine Ansicht von links alle Kriterien besteht zu erzeugen, beitritt, aber das scheint sehr teuer und löst nicht das Problem der angegebenen alle von den verschiedenen Tasten entsprechen.


Update: Dies ist ein Beispiel Abfrage ich, dass die Renditen, die erwarteten Ergebnisse geschrieben. Fehle ich alle Optimierungsmöglichkeiten? Wie magische Einhorn Matrix-Operationen durch Ninjas?

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)   
War es hilfreich?

Lösung

Sofern Sie haben eindeutige Indizes auf beiden (ProductID, CategoryID) und (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

oder, wenn Ihre Werte werden in CSV Strings übergeben:

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
        )

Beachten Sie, dass in SQL Server 2008 Sie Tabellenwertparameter in die gespeicherten Prozeduren passieren kann.

Andere Tipps

würde ich mit so etwas wie dies beginnen, Ihre Tabled ID-Werte aus den Parametern verwendet. Temp-Tabellen können mit Unterabfrage Geschwindigkeit helfen.

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

Produktkategorien sollten einen Clustered-Index haben auf (CategoryId, ProductId) und InvoiceProducts sollte optimal ein auf (InvoiceID, ProductId) haben. Dies ermöglicht Produkt-IDs zu finden, die CategoryId und InvoiceID unter Verwendung der Daten in dem Clustered-Indizes nur gegeben.

Sie könnten eine Funktion verwenden, um eine Tabelle von Ints einen String gegeben zurückzukehren. Google "CsvToInt" und klicken Sie auf den ersten Link von SqlTeam um den Code zu sehen.

Dann können Sie:

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)
    )

Wie über einen rekursiven CTE?

Erste Add Zeilennummern zu den Kriterien Tabellen, dann einig Pseudo-SQL, wenn man so will:

;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.

Dies gibt Ihnen eine Art der Durchführung und auf mehreren Kriterien, und sollte gut durchführen.

Ergibt das einen Sinn überhaupt? Ich habe einige ziemlich getan schnell Zeug mit CTEs kühlen in letzter Zeit, und erarbeiten können, wenn nötig.

Entfernen CTE Code, weil es falsch war, und nicht wirklich wert Befestigung gibt eine viel bessere Lösung ist.

Geben Sie sie als XML-Parameter, speichern Sie sie in eine temporäre Tabelle und verbinden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top