中間テーブルを使用した SQL クエリ
-
26-09-2019 - |
質問
次の表があるとします。
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
材料名 = 'チョコレート' および材料名 .名 = 'クリーム' であるレシピを検索する SQL クエリを作成するにはどうすればよいですか?
解決
これを関係分割といいます。さまざまなテクニックが語られています ここ.
まだ与えられていない代替案の 1 つは、二重の NOT EXISTS です。
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))
他のヒント
使用します。
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
ここで重要なポイントは、カウントは成分名の数に等しくなければならないということです。それは明確な数ではない場合は、原因重複する偽陽性のリスクがあります。
あなたの場合は、複数の関連付けのためにしている検索クエリを作成する最も簡単な方法ではなく、単一のストレート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'
)
あなたは(すなわち、単一のレシピが唯一の各成分の単一のインスタンスを持つことができます)団体が一意であることを確実に知るならば、あなたは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
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' )
編集次のコメント、ありがとう!
:これは、右の方法であります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'
別の方法:
のバージョン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: [人々が悲鳴を開始する前に]:)の
この代わりにIDを渡すの名前クエリに可能になる、バージョン2の上部に配置することができる。
select @recipeId1 = recipe_id from Ingredients where name = @Ingredient1
select @recipeId2 = recipe_id from Ingredients where name = @Ingredient2
私は、バージョン2をテストしてみた、それが動作します。この場合には、成分表にリンクするほとんどのユーザーは、完全に必要ではなかった!
編集3:(試験結果)の
このストアドプロシージャを実行すると、これらの結果です。
の結果は形式である(第Recipe_id;第Recipe_id、結果)
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'