سؤال

بالنظر إلى الجداول التالية:

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'
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top