2010-06-03 6 views
6

J'ai une table appelée transactions avec une relation plusieurs-à-plusieurs aux éléments via la table items_transactions.SQL: relation plusieurs-à-plusieurs, condition IN

Je veux faire quelque chose comme ceci:

SELECT "transactions".* 
    FROM "transactions" 
INNER JOIN "items_transactions" 
     ON "items_transactions".transaction_id = "transactions".id 
INNER JOIN "items" 
     ON "items".id = "items_transactions".item_id 
WHERE (items.id IN (<list of items>)) 

Mais cela me donne toutes les transactions qui ont un ou plusieurs des éléments dans la liste qui y sont associés et je ne le veux me donner les transactions sont associés à tous ces éléments.

Toute aide serait appréciée.

+1

Votre code SQL serait plus facile à lire si vous aviez utilisé la touche RETURN. –

+0

Oublié de demander, de qui parle-t-on - Sql Server? – amelvin

+0

Je ne sais pas encore comment faire cela avec SQL par ses propres moyens mais vous pouvez écrire une requête dynamique où vous ajouterez 'et item.id = itemX', la première dans la boucle serait 'où item.id = itemY' .. – eugeneK

Répondre

7

Vous devez développer votre requête pour tous les éléments de la liste:

SELECT "transactions".* 
FROM "transactions" 
WHERE EXISTS (SELECT 1 FROM "items_transactions" 
       INNER JOIN "items" ON "items".id = "items_transactions".item_id 
       WHERE "items_transactions".transaction_id = "transactions".id 
       AND "items".id = <first item in list>) 
AND EXISTS (SELECT 1 FROM "items_transactions" 
       INNER JOIN "items" ON "items".id = "items_transactions".item_id 
       WHERE "items_transactions".transaction_id = "transactions".id 
       AND "items".id = <second item in list>) 
... 

Vous pouvez également être en mesure de le masquer en utilisant IN et COUNT DISTINCT, je ne suis pas sûr de ce qui serait plus rapide. Quelque chose comme (complètement non testé):

SELECT "transactions".* 
FROM "transactions" 
INNER JOIN (SELECT "items_transactions".transaction_id 
      FROM "items_transactions" 
      INNER JOIN "items" ON "items".id = "items_transactions".item_id 
      WHERE "items".id IN (<list of items>) 
      GROUP BY "items_transactions".transaction_id 
      HAVING COUNT(DISTINCT "items".id) = <count of items in list>) matches ON transactions.transaction_id = matches.transaction_id 
+0

Est-ce qu'il n'y a vraiment pas de meilleure façon de faire cela? Il y a environ 100 éléments dans la liste, je ne pense pas que postgres puisse gérer les requêtes aussi longtemps. – Maarten

+0

J'aime l'utilisation de 'count = nombre d'éléments dans la liste' – eugeneK

+0

L'idée de compte fonctionne parfaitement, je devais seulement changer transactions.transactions_id à "transactions" .id pour mon cas. Merci beaucoup! – Maarten

0

Le bit final de la requête semble erroné:

WHERE (items.id IN (<list of items>)) 

la mention « dans » est comme un grand ou déclaration plutôt qu'une instruction AND, il est développé par l'optimiseur comme:

WHERE (items.id = 123 OR items.id = 456 OR items.id = 789) 

EDIT

Je tu crois besoin d'effectuer une correlated subquery sur la table des éléments.

+1

(items.id = 123 et items.id = 456 et items.id = 789) ne sera jamais vrai. – dcp

+0

@dcp maintenant vous le mentionnez, il semble un peu improbable. – amelvin

0

Je n'ai pas exécuté, mais que si vous obtenir le résultat souhaité:

SELECT t.* FROM items i 
    INNER JOIN items_transactions it ON i.id = it.item_id 
     INNER JOIN transactions t ON it.transaction_id = t.id 
WHERE i.id IN (1,2,3) 
+2

Il veut que les transactions soient associées à * tous * ces éléments, mais votre solution lui donnera des transactions associées à l'un d'entre eux. – dcp

1

Je pense que cela fait ce que vous voulez.

Je voudrais mettre la liste des éléments dont vous avez besoin dans une table (temp un sera très bien) et se joindre à cela. Ensuite, comptez le nombre d'éléments distincts et faites correspondre le compte au nombre de transactions d'articles.

J'ai fourni l'exemple DDL & Données que j'ai utilisées.

Create table #trans 
(
transId int identity(1,1), 
trans varchar(10) 
) 

Create Table #itemTrans 
(
transId int, 
itemId int 
) 

Create table #items 
(
itemId int identity(1,1), 
item varchar(10) 
) 

Create table #itemsToSelect 
(
itemId int 
) 


Insert Into #trans 
Values ('Trans 1') 

Insert Into #trans 
Values ('Trans 2') 

Insert Into #trans 
Values ('Trans 3') 


Insert Into #Items 
Values ('Item 1') 

Insert Into #Items 
Values ('Item 2') 

Insert Into #Items 
Values ('Item 3') 

Insert Into #Items 
Values ('Item 4') 

Insert Into #itemTrans 
Values (1, 1) 

Insert Into #itemTrans 
Values (1, 2) 

Insert Into #itemTrans 
Values (1, 3) 

Insert Into #itemTrans 
Values (2, 1) 

Insert Into #itemTrans 
Values (2, 3) 

Insert Into #itemTrans 
Values (3, 4) 



Insert Into #itemsToSelect 
Values (1) 
Insert Into #itemsToSelect 
Values (2) 
Insert Into #itemsToSelect 
Values (3) 


Select t.transId 

From #items i 
Join #itemTrans it on i.itemId = it.itemId 
Join #trans t on it.transId = t.transId 

Join #itemsToSelect its on it.ItemId = its.ItemId 

Where it.TransId is not null 
Group by t.transId 
Having count(distinct(it.itemId)) = (Select count(distinct(itemId)) from #itemsToSelect) 
1
SELECT transactions.* 
WHERE (SELECT count(*) 
     FROM items_transactions 
     WHERE items_transactions.transaction_id = transactions.transaction_id 
      AND items_transactions.item_id IN (<list of items>) 
    ) = <number of items> 

Bien que ce sera probablement faire une analyse des transactions, l'imbrication de la sous-requête corrélative pour chacun ... pas particulièrement efficace, peut-être:

SELECT transactions.* 
WHERE EXISTS (SELECT 1 FROM items_transactions 
       WHERE items_transactions.transaction_id = transactions.transaction_id 
       AND items_transactions.item_id IN (<list of items>) 
    ) 
     AND 
     (SELECT count(*) 
     FROM items_transactions 
     WHERE items_transactions.transaction_id = transactions.transaction_id 
      AND items_transactions.item_id IN (<list of items>) 
    ) = <number of items> 

ou quelque chose de similaire à persuader le DB pour rechercher d'abord les transactions liées à au moins un des éléments, puis vérifier que chaque transaction est liée à tous les éléments ultérieurement.

Comme quelqu'un l'a noté, vous pouvez également simplement générer des clauses de jointure pour chaque élément, ce qui pourrait être mieux si le nombre d'éléments n'est pas important.