Domanda

Date le seguenti tabelle:

Recipes
| id | name
| 1  | 'chocolate cream pie'
| 2  | 'banana cream pie'
| 3  | 'chocolate banana surprise'

Ingredients
| id | name
| 1  | 'banana'
| 2  | 'cream'
| 3  | 'chocolate'

RecipeIngredients
| recipe_id | ingredient_id
|     1     |      2
|     1     |      3
|     2     |      1
|     2     |      2
|     3     |      1
|     3     |      3

Come faccio a costruire una query SQL per trovare le ricette dove ingredients.name = 'cioccolato' e ingredients.name = 'crema'?

È stato utile?

Soluzione

Questa è chiamata divisione relazionale. Una varietà di tecniche sono discussi qui .

Un'alternativa non ancora dato è il doppio NON ESISTE

SELECT r.id, r.name
FROM Recipes r
WHERE NOT EXISTS (SELECT * FROM Ingredients i
                  WHERE name IN ('chocolate', 'cream')
                  AND NOT EXISTS
                      (SELECT * FROM RecipeIngredients ri
                       WHERE ri.recipe_id = r.id
                       AND ri.ingredient_id = i.id))

Altri suggerimenti

Usa:

  SELECT r.name
    FROM RECIPES r
    JOIN RECIPEINGREDIENTS ri ON ri.recipe_id = r.id
    JOIN INGREDIENTS i ON i.id = ri.ingredient_id
                      AND i.name IN ('chocolate', 'cream')
GROUP BY r.name
  HAVING COUNT(DISTINCT i.name) = 2

Il punto chiave qui è che il conteggio deve essere uguale al numero di nomi ingredienti. Se non è un conteggio distinto, c'è il rischio di falsi positivi a causa di duplicati.

Se siete alla ricerca di associazioni multiple allora il modo più semplice di scrivere la query è di utilizzare più condizioni EXISTS invece di un singolo JOIN dritto.

SELECT r.id, r.name
FROM Recipes r
WHERE EXISTS
(
    SELECT 1
    FROM RecipeIngredients ri
    INNER JOIN Ingredients i
        ON i.id = ri.ingredient_id
    WHERE ri.recipe_id = r.id
    AND i.name = 'chocolate'
)
AND EXISTS
(
    SELECT 1
    FROM RecipeIngredients ri
    INNER JOIN Ingredients i
        ON i.id = ri.ingredient_id
    WHERE ri.recipe_id = r.id
    AND i.name = 'cream'
)

Se si sa per certo che le associazioni sono unici (cioè una singola ricetta può avere solo una singola istanza di ogni ingrediente), allora si può imbrogliare un po 'utilizzando una sottoquery raggruppamento con una funzione COUNT e possibilmente accelerarlo (prestazioni dipenderà dal DBMS):

SELECT r.id, r.Name
FROM Recipes r
INNER JOIN RecipeIngredients ri
    ON ri.recipe_id = r.id
INNER JOIN Ingredients i
    ON i.id = ri.ingredient_id
WHERE i.name IN ('chocolate', 'cream')
GROUP BY r.id, r.Name
HAVING COUNT(*) = 2

In alternativa, se una ricetta potrebbe avere più istanze dello stesso ingrediente (nessun vincolo UNIQUE sulla tabella di associazione RecipeIngredients), è possibile sostituire l'ultima riga con:

HAVING COUNT(DISTINCT i.name) = 2
select r.*
from Recipes r
inner join (
    select ri.recipe_id
    from RecipeIngredients ri 
    inner join Ingredients i on ri.ingredient_id = i.id
    where i.name in ('chocolate', 'cream')
    group by ri.recipe_id
    having count(distinct ri.ingredient_id) = 2
) rm on r.id = rm.recipe_id
SELECT DISTINCT r.id, r.name
FROM Recipes r
INNER JOIN RecipeIngredients ri ON
    ri.recipe_id = r.id
INNER JOIN Ingredients i ON
    i.id = ri.ingredient_id
WHERE
    i.name IN ( 'cream', 'chocolate' )

A cura seguente commento, grazie! Questo è il modo giusto allora:

SELECT DISTINCT r.id, r.name
FROM Recipes r
INNER JOIN RecipeIngredients ri ON
    ri.recipe_id = r.id
INNER JOIN Ingredients i ON
    i.id = ri.ingredient_id AND
    i.name = 'cream'
INNER JOIN Ingredients i2 ON
    i2.id = ri.ingredient_id AND
    i2.name = 'chocolate'

un modo diverso:

Versione 2 (come stored procedure) rivisto

select   r.name
from   recipes r
where   r.id  = (select  t1.recipe_id
        from  RecipeIngredients t1 inner join
     RecipeIngredients     t2 on t1.recipe_id = t2.recipe_id
     and     t1.ingredient_id = @recipeId1
     and     t2.ingredient_id = @recipeId2)

Modifica 2: [Prima che si inizino a urlare]:)

Questo può essere posizionato nella parte superiore della versione 2, che consentirà di un'interrogazione per nome anziché passando l'id.

select @recipeId1 = recipe_id from Ingredients where name = @Ingredient1
select @recipeId2 = recipe_id from Ingredients where name = @Ingredient2

Ho provato la versione 2, e funziona. La maggior parte degli utenti in cui collegano sul tavolo Ingrediente, in questo caso non è stato del tutto necessari!

Modifica 3: (i risultati dei test);

Quando questa stored procedure viene eseguita questi sono i risultati.

I risultati sono del formato (Prima Recipe_id, in secondo luogo Recipe_id, Risultato)

1,1, Failed
1,2, 'banana cream pie'
1,3, 'chocolate banana surprise'
2,1, 'banana cream pie'
2,2, Failed
2,3, 'chocolate cream pie'
3,1, 'chocolate banana surprise'
3,2, 'chocolate cream pie'
3,3, Failed

Chiaramente questa ricerca non gestisce caso in cui entrambi i vincoli sono uguali, ma opere per tutti gli altri casi.

Modifica 4: (movimentazione stesso caso vincolo):

sostituire questa riga:

r.id = (select t1...

a

r.id in (select t1...

funziona con i casi riusciti a dare:

1,1, 'banana cream pie' and 'chocolate banana surprise'
2,2, 'chocolate cream pie' and 'banana cream pie'
3,3, 'chocolate cream pie' and 'chocolate banana surprise'
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top