Вопрос

Учитывая следующие таблицы:

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, чтобы найти рецепты, в которых INGREDIENTS.name = 'Chocolate' и Ingredients.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 Функция и, возможно, скорость его (производительность будет зависеть от СУБД):

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, что позволит запросить по имени вместо прохождения в ID.

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