2010-06-30 5 views
6

In SQL Server 2005 habe ich eine Tabelle mit Bestelldetails mit einer Bestell-ID und einer Produkt-ID. Ich möchte eine SQL-Anweisung schreiben, die alle Bestellungen findet, die alle Artikel in einer bestimmten Reihenfolge haben. Also, wenn Auftrag 5 die Positionen 1, 2 und 3 hat, möchte ich alle anderen Aufträge, die auch 1, 2 und 3 haben. Auch wenn Auftrag 5 zweimal 2 und 3 einmal hatte, würde ich alle anderen Aufträge mit wollen zwei 2er und ein 3er.SQL-Anweisung zum Auswählen einer Gruppe, die alle Werte enthält

Meine Präferenz ist, dass es Bestellungen zurückgibt, die genau übereinstimmen, aber Bestellungen, die ein Supersatz sind, sind akzeptabel, wenn das viel einfacher ist/viel besser funktioniert.

habe ich versucht, ein Self-Join wie folgt aus, aber das fand Aufträge mit jeder der Elemente statt alle der Elemente.

SELECT * FROM Order O1 
JOIN Order O2 ON (O1.ProductId = O2.ProductId) 
WHERE O2.OrderId = 5 

Dies gab mir auch Duplikate, wenn Auftrag 5 das gleiche Element zweimal enthielt.

+0

Wenn die Person ein Vielfaches von Artikel # 1 bestellt, dann wäre es in den Bestelldetails als mehrere Zeilen korrekt. Es gibt keine Spalte "Menge"? –

+0

Korrekt - keine Quantitätsspalte –

Antwort

3

Wenn die Orderdetails Tabelle eine eindeutige Einschränkung auf OrderId und ProductId enthält, dann können Sie etwas tun:

Select ... 
From Orders As O 
Where Exists (
       Select 1 
       From OrderDetails As OD1 
       Where OD1.ProductId In(1,2,3) 
        And OD1.OrderId = O.Id 
       Group By OD1.OrderId 
       Having Count(*) = 3 
       ) 

Wenn es möglich ist, die gleiche ProductId auf der gleichen Reihenfolge mehrmals haben, können Sie dann könnte die Having-Klausel zu

ändern Nun, angesichts der oben genannten, wenn Sie die Situation, wo jeder Auftrag die gleiche Signatur mit doppelten Produkteinträge haben wollen, das ist kniffliger. Um dies zu tun, würden Sie die Signatur der fraglichen Bestellung über die fraglichen Produkte benötigen und dann nach dieser Signatur fragen:

With OrderSignatures As 
    (
    Select O1.Id 
     , (
      Select '|' + Cast(OD1.ProductId As varchar(10)) 
      From OrderDetails As OD1 
      Where OD1.OrderId = O1.Id 
      Order By OD1.ProductId 
      For Xml Path('') 
      ) As Signature 
    From Orders As O1 
    ) 
Select ... 
From OrderSignatures As O 
    Join OrderSignatures As O2 
     On O2.Signature = O.Signature 
      And O2.Id <> O.Id 
Where O.Id = 5 
+0

Ich denke, er versucht, es basierend auf den Elementen in einer anderen Reihenfolge ID, nicht auf einer statischen Liste von Elementen (das wäre einfach genug, um mit einem einzelnen Beitritt pro Element zu tun). –

+0

@Adam Robinson - Ich sehe das. Ich habe meine Antwort auf diese Art von Anfrage erweitert. – Thomas

+0

+1. Ich hatte nicht daran gedacht, ein CTE und XML zum Erstellen der Signatur zu verwenden. Viel eleganter, wenn auch vielleicht etwas schwerer zu verstehen. –

1

Diese Art von Sache ist sehr schwierig in SQL zu tun, da SQL entworfen ist, um seine Ergebnismenge zu generieren, auf der grundlegendsten Ebene, Vergleichen einer Reihe von Spaltenwerten in einer einzelnen Zeile jeweils mit einem anderen Wert. Sie versuchen, einen einzelnen Spaltenwert (oder eine Gruppe von Spaltenwerten) unter mehrere Zeilen mit einem anderen Satz mehrere Zeilen zu vergleichen.

Um dies zu tun, müssen Sie eine Art Bestellsignatur erstellen. Streng genommen ist dies nicht möglich, wenn nur die Abfragesyntax verwendet wird. Sie müssen etwas T-SQL verwenden.

declare @Orders table 
(
    idx int identity(1, 1), 
    OrderID int, 
    Signature varchar(MAX) 
) 
declare @Items table 
(
    idx int identity(1, 1), 
    ItemID int, 
    Quantity int 
) 

insert into @Orders (OrderID) select OrderID from [Order] 

declare @i int 
declare @cnt int 

declare @j int 
declare @cnt2 int 

select @i = 0, @cnt = max(idx) from @Orders 

while @i < @cnt 
begin 
    select @i = @i + 1 

    declare @temp varchar(MAX) 

    delete @Items 

    insert into @Items (ItemID, Quantity) 
    select 
     ItemID, 
     Count(ItemID) 

    from OrderItem oi  

    join @Orders o on o.idx = @i and o.OrderID = oi.OrderID 

    group by oi.ItemID 

    order by oi.ItemID 

    select @j = min(idx) - 1, @cnt2 = max(idx) from @Items 

    while @j < @cnt2 
    begin 
     select @j = @j + 1 

     select @temp = isnull(@temp + ', ','') + 
      '(' + 
      convert(varchar,i.ItemID) + 
      ',' + 
      convert(varchar, i.Quantity) + 
      ')' 
     from @Items i where idx = @j 
    end 

    update @Orders set Signature = @temp where idx = @i 

    select @temp = null 
end 

select 
    o_other.OrderID 

from @Orders o 

join @Orders o_other on 
     o_other.Signature = o.Signature 
    and o_other.OrderID <> o.OrderID 

where o.OrderID = @OrderID 

Dies setzt voraus, (bezogen auf die Formulierung Ihrer Frage), die in einer Reihenfolge mehrere des gleichen Elements Bestellung in mehreren Reihen führen, anstatt eine Quantity Spalte. Wenn letzteres der Fall ist, entfernen Sie einfach die group by aus der @Items Abfrage und ersetzen Sie Count(ItemID) durch .

+0

Sie haben mir geholfen, mich besser zu fühlen, indem Sie mit "diese Art von Sache ist sehr schwierig, in SQL zu tun." :) –

+0

LOL. Meinetwegen. :) –

1

Ich denke, das sollte funktionieren. Ich verwende 108 als Beispiel OrderID, also müssen Sie das zweimal unten ersetzen oder eine Variable verwenden.

WITH TempProducts(ProductID) AS 
(
    SELECT DISTINCT ProductID FROM CompMarket 
    WHERE OrderID = 108 
) 
SELECT OrderID FROM CompMarket 
WHERE ProductID IN (SELECT ProductID FROM TempProducts) 
AND OrderID != 108 
GROUP BY OrderID 
HAVING COUNT(DISTINCT ProductID) >= (SELECT COUNT(ProductID) FROM TempProducts) 

Dieser verwendet einen CTE eine Liste eines Auftrags-Produkte zu bekommen, wählt dann alle Auftrags-IDs, die Produkte haben, die alle in dieser Liste enthalten sind. Um sicherzustellen, dass die zurückgegebenen Bestellungen alle Produkte enthalten, vergleicht dies die Anzahl der CTE mit den Counts der Produkte der zurückgegebenen Bestellung.

+0

Beachten Sie, dass dies nicht für Bestellungen mit mehreren gleichen Artikeln funktioniert. Wenn beispielsweise die Reihenfolge "1" zwei Elemente "10" und ein Element "20" aufweist, während die Reihenfolge "2" ein Element "10" und zwei Elemente "20" aufweist, führt dieser Code zu einer Übereinstimmung. –

+0

Wäre das nicht eine Reihenfolge, die nur einige der gleichen Elemente enthält, solange die Anzahl übereinstimmt? Also, wenn Bestellung 108 drei Artikel hätte, würde dies eine 3 (oder mehr) Artikelbestellung mit mindestens einem Artikel von 108 ergeben? –

+0

@Adam Ich habe DISTINCT zu COUNT (DISTINCT ...) hinzugefügt, was dieses Problem lösen wird. @Eddie, nein: es wird nur der COUNT für die Artikel verglichen, die IN sind, nicht alle Produkte der Bestellung. – Nick

Verwandte Themen