SQL-запрос через промежуточную таблицу - PullRequest
18 голосов
/ 12 июня 2010

С учетом следующих таблиц:

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 = 'chocolate' и ингридиенты.name = 'cream'?

Ответы [ 6 ]

14 голосов
/ 12 июня 2010

Использование:

  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

Ключевым моментом здесь является то, что количество должно равняться количеству ингредиентов Если это не отдельный счет, существует риск ложных срабатываний из-за дубликатов.

10 голосов
/ 13 июня 2010

Это называется реляционным делением. Разнообразные методы обсуждаются здесь .

Одна альтернатива, которая еще не дана, - двойная НЕ СУЩЕСТВУЕТ

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))
3 голосов
/ 12 июня 2010

Если вы ищете несколько ассоциаций, то самый простой способ написать запрос - использовать несколько 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 и, возможно, ускорить его (производительность будет зависеть от СУБД):

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
2 голосов
/ 12 июня 2010
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
1 голос
/ 13 июня 2010

по-другому:

Версия 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: (результаты теста);

При запуске этой хранимой процедуры этиявляются результатами.

Результаты имеют формат (Первый 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'
1 голос
/ 12 июня 2010
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'
...