web-dev-qa-db-ja.com

中間テーブルを介したSQLクエリ

次の表があるとします。

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

材料.name = 'チョコレート'および材料.name = 'クリーム'であるレシピを見つけるためにSQLクエリを作成するにはどうすればよいですか?

18
Bryan Ash

これはリレーショナル分割と呼ばれます。さまざまな手法について説明します ここ

まだ与えられていない1つの選択肢は、二重のNOTEXISTSです。

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))
10
Martin Smith

使用する:

  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

ここで重要な点は、カウントが成分名の数と等しくなければならないということです。明確なカウントでない場合、重複による誤検知のリスクがあります。

14
OMG Ponies

複数の関連付けを検索する場合、クエリを作成する最も簡単な方法は、単一のストレートEXISTSではなく複数のJOIN条件を使用することです。

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

関連付けが一意であることが確実にわかっている場合(つまり、単一のレシピに各成分のインスタンスを1つだけ含めることができる)、COUNT関数を使用してグループ化サブクエリを使用して少しごまかし、場合によっては高速化できます。 (パフォーマンスは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

または、レシピに同じ材料の複数のインスタンスが含まれている可能性がある場合(UNIQUE関連付けテーブルにRecipeIngredients制約がない場合)、最後の行を次のように置き換えることができます。

HAVING COUNT(DISTINCT i.name) = 2
3
Aaronaught
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
2
RedFilter

別の方法:

バージョン2(ストアドプロシージャとして)が改訂されました

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)

編集2:[人々が叫び始める前に]:)

これはバージョン2の上部に配置でき、IDを渡す代わりに名前でクエリを実行できます。

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

バージョン2をテストしましたが、動作します。この場合、成分テーブルでリンクするほとんどのユーザーはまったく必要ありませんでした!

編集3 :(テスト結果);

このストアドプロシージャを実行すると、これらが結果になります。

結果の形式は(First Recipe_id; Second Recipe_id、Result)です。

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

明らかに、このクエリは両方の制約が同じ場合はケースを処理しませんが、他のすべてのケースでは機能します。

編集4 :(同じ制約ケースの処理):

この行を置き換える:

r.id = (select t1...

r.id in (select t1...

失敗したケースと連携して以下を提供します。

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

以下のコメントを編集しました、ありがとう!これが正しい方法です。

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'
1
Diego Pereyra