SQL: отношение многие ко многим, состояние IN - PullRequest
6 голосов
/ 03 июня 2010

У меня есть таблица транзакций с отношением «многие ко многим» к элементам в таблице items_transactions.

Я хочу сделать что-то вроде этого:

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

Но это дает мне все транзакции, которые имеют один или несколько элементов в списке, связанных с ним, и я хочу, чтобы он только дал мне транзакции, которые связаны со всеми этими элементами.

Любая помощь будет оценена.

Ответы [ 5 ]

9 голосов
/ 03 июня 2010

Вы должны расширить свой запрос для всех элементов в списке:

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

Вы также можете помассировать его, используя IN и COUNT DISTINCT, я не уверен, что будет быстрее. Что-то вроде (полностью не проверено):

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
1 голос
/ 03 июня 2010
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>

Хотя это, вероятно, выполнит сканирование транзакций, вложив коррелированный подзапрос для каждого ... не особенно эффективно, поэтому, возможно:

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>

или что-то подобное, чтобы убедить БД сначала найти транзакции, связанные по крайней мере с одним из элементов, а затем проверить, что каждая транзакция связана со всеми элементами позже.

Как кто-то заметил, вы также можете просто сгенерировать предложения соединения для каждого элемента, что может быть лучше, если количество элементов невелико.

1 голос
/ 03 июня 2010

Я думаю, это делает то, что вы хотите.

Я бы положил список нужных вам предметов в таблицу (временный вариант будет в порядке) и присоединился к этому. Затем подсчитайте количество различных элементов и сопоставьте их с количеством транзакций элементов.

Я предоставил образец DDL и данные, которые я использовал.

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)
0 голосов
/ 03 июня 2010

Я не выполнил это, но это должно дать вам желаемый результат:

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)
0 голосов
/ 03 июня 2010

Последний бит запроса выглядит неправильно:

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

оператор 'in' подобен большому оператору OR, а не оператору AND, поэтому он расширяется оптимизатором как:

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

EDIT

Я считаю, что вам нужно выполнить коррелированный подзапрос для таблицы предметов.

...