Я не буду комментировать, существует ли лучше подходящая схема для этого (это вполне возможно), но для схемы, имеющей столбцы name
и item
, следующий запрос должен работать. (синтаксис mysql)
SELECT k.name
FROM (SELECT DISTINCT name FROM sets) AS k
INNER JOIN sets i1 ON (k.name = i1.name AND i1.item = 1)
INNER JOIN sets i2 ON (k.name = i2.name AND i2.item = 3)
INNER JOIN sets i3 ON (k.name = i3.name AND i3.item = 5)
LEFT JOIN sets ix ON (k.name = ix.name AND ix.item NOT IN (1, 3, 5))
WHERE ix.name IS NULL;
Идея состоит в том, что у нас есть все ключи набора в k
, которые мы затем соединяем с данными набора элементов в sets
один раз для каждого элемента набора в наборе, который мы ищем, три в этом случае. Каждое из трех внутренних объединений с псевдонимами таблиц i1
, i2
и i3
отфильтровывает все имена наборов, которые не содержат элемент, который ищется в этом объединении. Наконец, у нас есть левое соединение с sets
с псевдонимом таблицы ix
, которое включает все дополнительные элементы в наборе, то есть каждый элемент, который мы не искали. ix.name
означает NULL
в том случае, если дополнительные элементы не найдены, а это именно то, что нам нужно, то есть предложение WHERE
. Запрос возвращает строку, содержащую ключ набора, если набор найден, иначе строк нет.
Редактировать: Идея ответа коллапсаров, кажется, намного лучше моей, так что вот немного более короткая версия с объяснением.
SELECT sets.name
FROM sets
LEFT JOIN (
SELECT DISTINCT name
FROM sets
WHERE item NOT IN (1, 3, 5)
) s1
ON (sets.name = s1.name)
WHERE s1.name IS NULL
GROUP BY sets.name
HAVING COUNT(sets.item) = 3;
Идея в том, что подзапрос s1
выбирает ключи всех наборов, которые содержат элементы, отличные от тех, которые мы ищем. Таким образом, когда мы оставляем соединение sets
с s1
, s1.name
равно NULL
, когда набор содержит только элементы, которые мы ищем. Затем мы группируем по ключу набора и отфильтровываем любые наборы, имеющие неправильное количество элементов. Затем у нас остаются только наборы, которые содержат только те элементы, которые мы ищем, и имеют правильную длину. Поскольку наборы могут содержать элемент только один раз, может быть только один набор, удовлетворяющий этим критериям, и именно этот мы ищем.
Редактировать: Меня только что осенило, как это сделать без исключения.
SELECT totals.name
FROM (
SELECT name, COUNT(*) count
FROM sets
GROUP BY name
) totals
INNER JOIN (
SELECT name, COUNT(*) count
FROM sets
WHERE item IN (1, 3, 5)
GROUP BY name
) matches
ON (totals.name = matches.name)
WHERE totals.count = 3 AND matches.count = 3;
Первый подзапрос находит общее количество элементов в каждом наборе, а второй - количество подходящих элементов в каждом наборе. Когда matches.count
равен 3, в наборе есть все предметы, которые мы ищем, а если totals.count
также равно 3, в наборе нет никаких дополнительных предметов.