2010-06-30 3 views
6

Dans SQL Server 2005, j'ai une table de détails de la commande avec un numéro de commande et un identifiant de produit. Je veux écrire une instruction sql qui trouve toutes les commandes qui ont tous les éléments dans un ordre particulier. Donc, si l'ordre 5 a les items 1, 2 et 3, je voudrais que tous les autres ordres aient aussi 1, 2 et 3. Aussi, si l'ordre 5 avait 2 fois 2 et 3 une fois, je voudrais toutes les autres commandes avec deux 2 et un 3.instruction SQL pour sélectionner un groupe contenant tous un ensemble de valeurs

Ma préférence est que le retour des commandes qui correspondent exactement, mais les commandes qui sont sont acceptables si surensemble qui est beaucoup plus facile/est bien meilleur.

J'ai essayé autojointure comme suit, mais que les commandes avec ce une des éléments plutôt que tous des articles. Cela m'a également donné des doublons si l'ordre 5 contenait le même article deux fois.

+0

Si la personne de commandes multiples article # 1, il serait alors dans l'ordre Détails sur plusieurs lignes, correct. Il n'y a pas de colonne "quantité"? –

+0

Correct - aucune quantité colonne –

Répondre

3

Si la table OrderDetails contient une contrainte unique sur OrderId et ProductId, alors vous pouvez faire quelque chose comme ceci:

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 
       ) 

S'il est possible d'avoir la même ProductId du même ordre plusieurs fois, vous pourrait changer la clause Having à Count(Distinct ProductId) = 3

Maintenant, compte tenu de ce qui précède, si vous voulez que la situation où chaque commande a la même signature avec des entrées de produit en double, c'est plus délicat. Pour ce faire, vous auriez besoin de la signature de l'ordre en question sur les produits en question, puis demander cette signature:

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

Je pense qu'il essaye de le faire correspondre en fonction des éléments contenus dans un autre identifiant de commande, et non basé sur une liste statique d'éléments (ce serait assez facile à faire avec une seule jointure par élément). –

+0

@Adam Robinson - Je vois ça. J'ai élargi ma réponse pour inclure ce type de demande. – Thomas

+0

+1. Je n'avais pas pensé à utiliser un CTE et XML pour créer la signature. Beaucoup plus élégant, mais peut-être un peu plus difficile à comprendre. –

1

Ce genre de chose est très difficile à faire en SQL, car SQL est conçu pour générer son jeu de résultats en comparant, au niveau le plus basique, un ensemble de valeurs de colonnes sur une seule ligne chacune à une autre valeur. Qu'est-ce que vous essayez de faire est de comparer une valeur de colonne unique (ou un ensemble de valeurs de colonnes) sur plusieurs lignes à un autre ensemble de plusieurs lignes.

Pour ce faire, vous devrez créer une sorte de signature de la commande. Strictement parlant, il n'est pas possible d'utiliser la syntaxe de requête seule; vous devrez utiliser du T-SQL.

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 

Cela suppose (basé sur le libellé de votre question) qui commande multiple du même article dans un ordre entraînera plusieurs lignes, plutôt que d'utiliser une colonne Quantity. Si ce dernier est le cas, il suffit de retirer la group by de la requête de la population @Items et remplacer Count(ItemID) avec Quantity.

+0

Vous m'avez fait me sentir mieux en commençant par "ce genre de chose est très difficile à faire en SQL." :) –

+0

LOL. C'est suffisant. :) –

1

Je pense que cela devrait fonctionner. J'utilise 108 comme exemple de OrderID, donc vous devrez le remplacer deux fois plus bas ou utiliser une variable.

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) 

Il utilise un CTE pour obtenir une liste d'un de commander des produits, sélectionne alors tous les ID d'ordre qui ont des produits qui sont tous dans cette liste. Pour s'assurer que les commandes retournées ont tous les produits, ceci compare le compte du CTE aux comptes des produits de la commande retournés.

+0

Notez que cela ne fonctionne pas pour les commandes avec plusieurs articles du même article. Par exemple, si l'ordre "1" a deux items "10" et un item "20", alors que "2" a un item "10" et deux item "20", ce code donnera une correspondance. –

+0

Est-ce que cela ne trouverait pas un ordre qui a seulement certains des mêmes éléments tant que les comptes correspondent? Donc, si l'ordre 108 avait 3 items, ceci trouverait n'importe quel ordre d'article 3 (ou plus) avec au moins un article de 108? –

+0

@Adam J'ai ajouté DISTINCT à COUNT (DISTINCT ...), ce qui résoudra ce problème. @Eddie, non: c'est seulement comparer le COUNT pour les articles qui sont IN, pas tous les produits de cette commande. – Nick

Questions connexes