#mysql #sql
#mysql #sql
Вопрос:
Я знаю, что вы могли бы добиться этого, получив все блюда, содержащие A и B, а затем проверяя каждое блюдо в результатах вторым запросом, который подсчитывает, сколько ингредиентов содержит каждое блюдо. Тогда, если число равно 2, это должно быть блюдо из 2 ингредиентов, которое содержит A и B, поэтому оно содержит ТОЛЬКО A и B. Вот так:
Получение всех блюд, содержащих A и B.
SELECT dishes.id, dishes.name
FROM dishes
JOIN dishes_ingredients ON dishes.id = dishes_ingredients.dishes_id
JOIN ingredients ON dishes_ingredients.ingredients_id = ingredients.id
WHERE ingredients.name IN ('A', 'B')
GROUP BY dishes.id
HAVING COUNT(DISTINCT ingredients.id) = 2;
Получение количества ингредиентов в каждом блюде.
SELECT COUNT(ingredients_id)
FROM dishes_ingredients
WHERE dishes_id = (each dish in previous query's results - could be also done in PHP)
Проблема, которую я вижу, заключается в том, что вы не можете использовать один и тот же запрос HAVING COUNT(DISTINCT ingredients.id)
2 раза, один на невыбранном уровне, чтобы получить общее количество ингредиентов блюда, а другой на выбранном уровне, чтобы получить количество выбранных строк с учетом WHERE
используемого вами. Я думаю, это потому HAVING
, что относится к GROUP BY
… Но, может быть, я ошибаюсь? Если бы это было возможно, то вы могли бы проверить оба числа и получить только те блюда, которые содержат только A и B.
Ответ №1:
Удалите WHERE
предложение и задайте условия только в HAVING
предложении:
SELECT d.id, d.name
FROM dishes d
JOIN dishes_ingredients di ON d.id = di.dishes_id
JOIN ingredients i ON di.ingredients_id = i.id
GROUP BY d.id, d.name
HAVING COUNT(DISTINCT CASE WHEN i.name IN ('A', 'B') THEN i.name END) = 2
AND SUM(i.name NOT IN ('A', 'B')) = 0
Если каждый ингредиент появляется только один раз для каждого блюда, то HAVING
предложение можно упростить до:
HAVING SUM(i.name IN ('A', 'B')) = 2
AND SUM(i.name NOT IN ('A', 'B')) = 0
Другой способ сделать это с использованием GROUP_CONCAT()
:
SELECT d.id, d.name
FROM dishes d
JOIN dishes_ingredients di ON d.id = di.dishes_id
JOIN ingredients i ON di.ingredients_id = i.id
GROUP BY d.id, d.name
HAVING GROUP_CONCAT(DISTINCT i.name ORDER BY i.name) = 'A,B'
Если каждый ингредиент появляется только один раз для каждого блюда, тогда нет необходимости DISTINCT
.
Комментарии:
1. … вам также может потребоваться добавить DISTINCT в GROUP_CONCAT .
2. Я не думаю, что это необходимо. , Как я упоминаю в своем ответе, я предполагаю, что каждый ингредиент появляется только один раз для каждого блюда, поэтому нет необходимости в ОТДЕЛЬНЫХ
3. На самом деле ингредиенты появляются более одного раза (я должен был указать это в вопросе).
4. @Pirula более одного раза для каждого блюда? Почему? Это странно.
5. Извините за странность. Как заявил Гордон Линофф в своем ответе, НАЛИЧИЕ COUNT (ОТДЕЛЬНЫЙ СЛУЧАЙ … выполняет свою работу, НО я все еще получаю блюда, содержащие ингредиенты, отличные от A и B.
Ответ №2:
Вы можете легко сделать это с помощью условной агрегации:
SELECT d.id, d.name, COUNT(*) as num_ingredients
FROM dishes d JOIN
dishes_ingredients di
ON d.id = di.dishes_id JOIN
ingredients i
ON di.ingredients_id = i.id
GROUP BY d.id
HAVING SUM(i.name IN ('A', 'B')) = 2;
Если ингредиенты могут повторяться в рецепте, то используйте:
HAVING COUNT(DISTINCT CASE WHEN i.name IN ('A', 'B') THEN i.id END) = 2;
Как и в случае с вашим предыдущим вопросом, я настоятельно рекомендую вам использовать псевдонимы таблиц в своих запросах, чтобы их было легче писать и читать.
Только для этих ингредиентов, затем:
HAVING COUNT(DISTINCT CASE WHEN i.name IN ('A', 'B') THEN i.id END) = 2 AND
COUNT(DISTINCT i.name) = 2
Или, чтобы избежать COUNT(DISTINCT)
(который может иметь низкую производительность при некоторых обстоятельствах):
HAVING SUM(i.name = 'A') > 0 AND
SUM(i.name = 'B') > 0 AND
SUM(i.name NOT IN ('A', 'B')) = 0
Комментарии:
1. Зачем выбирать count(*) как num_ingredients? Вы не используете его после. Когда я включаю это в запрос, он выдает 0 результатов. Когда я его опустил, он работает, но показывает блюда, которые содержат и другие ингредиенты.
2. Ингредиенты на самом деле могут повторяться, поэтому я попробовал ваш ответ с помощью COUNT(ОТДЕЛЬНЫЙ СЛУЧАЙ … и он становится ближе, не выбирая блюда, которые содержат 2 раза A и без B. Проблема в том, что он по-прежнему показывает блюда, содержащие другие ингредиенты.
3. @Pirula . , , я пропустил эту часть вопроса, но обновил ответ.
Ответ №3:
Несмотря на то, что я принял ответ forpas, то, что я закончил, больше похоже на то, что Гордон Линофф добавил в конце. Итак, это то, что я реализовал:
SELECT d.id, d.name
FROM dishes d
JOIN dishes_ingredients di ON d.id = di.dishes_id
JOIN ingredients i ON di.ingredients_id = i.id
GROUP BY d.id
HAVING COUNT(DISTINCT CASE WHEN i.name IN ('A', 'B') THEN i.name END) = 2
AND COUNT(DISTINCT i.name) = 2