2010-06-03 28 views
6

Tengo una tabla llamada transacciones con una relación de varios a varios elementos a través de la tabla items_transactions.SQL: relación muchos a muchos, condición IN

quiero hacer algo como esto:

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

Pero esto me da todas las transacciones que tienen uno o más de los elementos de la lista de la misma y yo sólo quiero que me dé las transacciones que están asociados con todos esos artículos.

Cualquier ayuda sería apreciada.

+1

Su código SQL sería más fácil de leer si hubiera utilizado la tecla RETORNO. –

+0

Se olvidó de preguntar, ¿de qué Sql estamos hablando - Servidor SQL? – amelvin

+0

Aún no sé cómo hacer esto con SQL, pero puedes escribir una consulta dinámica donde agregarás 'y item.id = itemX', primero en el bucle sería 'donde item.id = itemY' ... – eugeneK

Respuesta

7

Hay que ampliar a cabo la consulta para todos los elementos de la lista:

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

También podría darle masajes usando IN y COUNT DISTINCT, no estoy seguro de cuál sería más rápido. Algo así como (completamente no probado):

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

¿Realmente no hay una manera más agradable de hacer esto? Hay como 100 elementos en la lista, no creo que Postgres pueda manejar consultas tan largas. – Maarten

+0

Me gusta el uso de 'recuento = número de elementos en la lista' – eugeneK

+0

La idea del conteo funciona perfectamente, solo tuve que cambiar transacciones.transacciones_id a "transacciones" .id para mi caso. ¡Muchas gracias! – Maarten

0

El bit final de la consulta se ve mal:

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

el 'en' declaración es como un gran O declaración en lugar de una instrucción AND, por lo que se expande por el optimizador como:

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

EDITAR

Creo que es necesario realizar un correlated subquery en la tabla de artículos.

+1

(items.id = 123 y items.id = 456 y items.id = 789) nunca serán verdaderos. – dcp

+0

@dcp ahora lo mencionas, parece un poco improbable. – amelvin

0

no he ejecutado este, pero que debe obtener el resultado deseado:

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

Quiere transacciones que estén asociadas con * todos * esos artículos, pero su solución le dará transacciones que están asociadas con cualquiera de ellos. – dcp

1

Creo que esto hace lo que quiere.

Me gustaría poner la lista de elementos que necesita en una tabla (la temperatura uno va a estar bien) y unirme a eso. Luego cuente la cantidad de elementos distintos y haga coincidir el recuento con el recuento de transacciones del elemento.

He proporcionado la muestra DDL & Datos que he utilizado.

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> 

Aunque esto probablemente hará un análisis de las transacciones, anidando la consulta correlacionada para cada uno ... no es particularmente eficiente, así que tal vez:

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> 

o algo similar para persuadir al DB para buscar transacciones relacionadas con al menos uno de los elementos primero, y luego verificar que cada transacción se vincule con todos los artículos más adelante.

Como alguien ha notado, también puede simplemente generar cláusulas de unión para cada elemento, lo que podría ser mejor si la cantidad de elementos no es grande.

Cuestiones relacionadas