استعلام 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 للعثور على وصفات حيث المكونات. name = 'الشوكولاتة' والمكونات. name = 'cream'؟
المحلول
وهذا ما يسمى التقسيم العلائقي. وتناقش مجموعة متنوعة من التقنيات هنا.
بديل واحد لم يتم تقديمه بعد هو عدم وجود مزدوج
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: [قبل أن يبدأ الناس في الصراخ] :)
يمكن وضع ذلك في الجزء العلوي من الإصدار 2 ، والذي سيسمح للاستعلام بالاسم بدلاً من تمرير المعرف.
select @recipeId1 = recipe_id from Ingredients where name = @Ingredient1
select @recipeId2 = recipe_id from Ingredients where name = @Ingredient2
لقد اختبرت الإصدار 2 ، وهو يعمل. معظم المستخدمين حيث يربطون على جدول المكونات ، في هذه الحالة لم تكن هناك حاجة تماما!
تحرير 3: (نتائج الاختبار) ؛
عندما يتم تشغيل هذا الإجراء المخزن ، فهذه هي النتائج.
النتائج من التنسيق (الوصفة الأولى ؛ الوصفة الثانية ، النتيجة)
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'