Frage

Bei den folgenden Tabellen:

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

Wie konstruiere ich eine SQL-Abfrage Rezepte zu finden, wo ingredients.name = ‚Schokolade‘ und ingredients.name = ‚Sahne‘?

War es hilfreich?

Lösung

Dies ist relationale Division bezeichnet. Eine Vielzahl von Techniken werden diskutiert hier .

Eine Alternative noch nicht gegeben ist die doppelte VORHANDEN NICHT

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

Andere Tipps

Verwendung:

  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

Der entscheidende Punkt hier ist, dass die Zählung der Anzahl der Zutat Namen betragen muss. Wenn es nicht ein Distinct Count ist, gibt es ein Risiko von Fehlalarmen aufgrund von Duplikaten.

Wenn Sie suchen für mehrere Zuordnungen dann der einfachste Weg, um die Abfrage zu schreiben, ist mehrere EXISTS Bedingungen statt einer einzigen geraden JOIN zu verwenden.

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

Wenn Sie sicher sind, dass die Verbände eindeutig sind (dh ein einziges Rezept kann nur eine einzelne Instanz der einzelnen Inhaltsstoffe haben), dann können Sie ein wenig betrügen eine Gruppierungsunterabfrage mit einer COUNT Funktion und es möglicherweise bis beschleunigen (Performance wird auf dem DBMS abhängig):

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

Oder, wenn ein Rezept mehr Instanzen des gleichen Wirkstoffes haben könnte (keine UNIQUE Einschränkung für die RecipeIngredients Zuordnungstabelle), können Sie die letzte Zeile ersetzen mit:

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

Edited folgenden Kommentar, danke! Dies ist der richtige Weg, dann:

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'

eine andere Art und Weise:

Version 2 (als Stored Procedure) überarbeitet

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)

Edit 2: [Vor schreien Menschen beginnen]:)

Dies kann an der Spitze der Version 2 platziert werden, die Abfrage von Namen erlauben wird stattdessen in der ID übergeben.

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

Ich habe 2 getestete Version, und es funktioniert. Die meisten Anwender, wo auf der Ingredient Verknüpfungstabelle, wurden in diesem Fall absolut nicht erforderlich!

Bearbeiten 3: (Testergebnisse);

Wenn Sie diese gespeicherte Prozedur ausgeführt wird, das sind die Ergebnisse.

Die Ergebnisse sind von dem Format (Erster Recipe_id, zweitens Recipe_id, Ergebnis)

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

Offensichtlich diese Abfrage nicht Fall behandeln, wenn beide Zwänge sind die gleichen, aber funktioniert für alle anderen Fälle.

Bearbeiten 4: (gleiche Einschränkung Sachbearbeitung):

ersetzt diese Zeile:

r.id = (select t1...

r.id in (select t1...

Arbeiten mit den gescheiterten Fälle zu geben:

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'
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top