запрос для набора в реляционной базе данных - PullRequest
3 голосов
/ 30 марта 2011

Я хотел бы запросить реляционную базу данных, если существует набор элементов.

Данные, которые я моделирую, имеют следующую форму:

key1 = [ item1, item3, item5 ]
key2 = [ item2, item7 ]
key3 = [ item2, item3, item4, item5 ]
...

Я храню их втаблица со следующей схемой

CREATE TABLE sets (key INTEGER, item INTEGER);

Так, например, следующие операторы вставки будут вставлять три вышеуказанных набора.

INSERT INTO sets VALUES ( key1, item1 );
INSERT INTO sets VALUES ( key1, item3 );
INSERT INTO sets VALUES ( key1, item5 );
INSERT INTO sets VALUES ( key2, item2 );
INSERT INTO sets VALUES ( key2, item7 );
INSERT INTO sets VALUES ( key3, item2 );
INSERT INTO sets VALUES ( key3, item3 );
INSERT INTO sets VALUES ( key3, item4 );
INSERT INTO sets VALUES ( key3, item5 );

Учитывая набор элементов, я хотел бы, чтобы связанный ключс набором, если он хранится в таблице, и NULL, если это не так.Можно ли сделать это с помощью SQL-запроса?Если это так, пожалуйста, предоставьте детали.

Подробности, которые могут иметь отношение к делу:

  • Меня в первую очередь интересует стратегия проектирования / запроса базы данных, хотя в конечном итоге я буду реализовывать это в MySQL и предварительно сформировать запрос из with в python, используяпакет mysql-python.
  • У меня есть свобода реструктурировать схему базы данных, если для этого типа запроса будет удобнее другой макет.
  • Каждый набор, если он существует, долженбыть уникальным.
  • Меня не интересуют частичные совпадения.
  • Масштаб базы данных составляет порядка <1000 наборов, каждый из которых содержит <10 элементов каждый, поэтому производительность на этом этапе неприоритет. </li>

Заранее спасибо.

Ответы [ 4 ]

2 голосов
/ 30 марта 2011

Я не буду комментировать, существует ли лучше подходящая схема для этого (это вполне возможно), но для схемы, имеющей столбцы 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, в наборе нет никаких дополнительных предметов.

1 голос
/ 31 марта 2011

Этот запрос имеет хорошо известное имя.Google " реляционное деление ", " установить объединение включения ", " установить равенство объединения ".

1 голос
/ 30 марта 2011

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

     SELECT CASE COUNT(ddd.key) WHEN 0 THEN NULL ELSE MIN(ddd.key) END
       FROM (
                 SELECT s4.key
                      , COUNT(*) icount
                   FROM sets s4
                   JOIN (
                          SELECT DISTINCT d.key
                            FROM (
                                   SELECT s1.key
                                     FROM sets s1
                                    WHERE s1.item IN ('item1', 'item3', 'item5')
                                    MINUS
                                   SELECT s2.key
                                     FROM sets s2
                                    WHERE s2.item NOT IN ('item1', 'item3', 'item5')
                                 ) d    
                         ) dd ON ( dd.key = s4.key )
                GROUP BY s4.key
             ) ddd
       WHERE ddd.icount = (
                             SELECT COUNT(*)
                               FROM (
                                      SELECT DISTINCT s3.item
                                        FROM sets s3
                                       WHERE s3.item IN ('item1', 'item3', 'item5')
                                    )
                          )
           ;                 

набор результатов dd предоставляет набор ключей-кандидатов, которые не связаны с другими элементами, отличными от набора из тестируемого набора.единственная двусмысленность может возникнуть из-за ключей, которые ссылаются на правильное подмножество проверенного набора элементов.таким образом, мы подсчитываем количество предметов, связанных с ключами dd, и выбираем тот ключ, где это число соответствует количеству элементов тестируемого набора предметов.если такой ключ существует, он уникален (поскольку мы знаем, что наборы элементов уникальны).выражение case в крайнем выборе является просто причудливым способом гарантировать, что их набор не будет пустым результирующим набором, то есть будет возвращено нулевое значение, если набор элементов не представлен отношением.

возможно, это решениебудет вам полезен,

С наилучшими пожеланиями

Карстен

0 голосов
/ 31 марта 2011

Чтобы упростить решение коллапсара, которое уже было упрощено Алекси Торхамо:

Нет необходимости получать все ключи, которые НЕ ПОДХОДЯТ, которые могут быть большими, просто получите те, которые соответствуют и вызываютих частичное совпадение.

-- get all partial matches
CREATE TEMPORARY VIEW partial_matches AS
SELECT DISTINCT key FROM sets WHERE item IN (1,3,5);

-- filter for full matches
SELECT sets.key
FROM  sets, partial_matches
WHERE sets.key = partial_matches.key
GROUP BY sets.key HAVING COUNT(sets.key) = 3;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...