Самое быстрое выражение SQL для сложного поиска в отношениях «многие ко многим»? - PullRequest
3 голосов
/ 21 апреля 2011

В таблице product_tag столбцы:

id, product_id, tag_id

Если я хочу найти продукт, который является tag1 ИЛИ tag2 ИЛИ tag3прямой путь:

SELECT DISTINCT productId FROM product_tags WHERE tagId IN (2,4);

Если я хотел бы найти продукт, который является tag1, tag2 и tag3, прямой путь:

SELECT productId FROM product_tag WHERE tag_id IN (tag1, tag2, tag3) GROUP BY productId HAVING COUNT(*) = 3

Но вопрос в том,если я хочу найти продукт, который имеет сложную связь тегов, например:

продукт, который (тег1 ИЛИ тег2 ИЛИ тег3) И (тег 4 ИЛИ тег5 ИЛИ тег 6) И (тег7 ИЛИ tag8 ИЛИ tag9)

Какое выражение SQL имеет лучшую производительность?(и желательно элегантно).

Редактировать:
Самым важным приростом производительности было добавление индексов, как рекомендовал Ремус в комментариях.

Ответы [ 5 ]

1 голос
/ 21 апреля 2011

Объединение всех 3 групп. Они 3 выбора, но они действительно простые.

1 голос
/ 21 апреля 2011

Вы действительно не можете сделать это напрямую с языком на основе множеств, таким как SQL.

Ваша простая версия "И" также не будет работать, если у вас нет дубликатов (productId, tagId).

Для ваших сложных отношений необходимо разбить ваш запрос на несколько подзапросов. Первый разрыв по всем пунктам «И»:

WHERE tag_id IN (tag1, tag2, tag3)
WHERE tag_id IN (tag4, tag5, tag6)
WHERE tag_id IN (tag7, tag8, tag9)

Затем выполните ПЕРЕКЛЮЧЕНИЕ результатов запроса.

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

Другими словами, вы можете рекурсивно разбивать логическое дерево по предложениям «И», а затем на каждом уровне дерева выполнять ИНТЕРСЕКТ результатов запроса.

Выполнение этого, вероятно, будет намного быстрее, чем генерация огромного SQL, который вернет результат за один раз - потому что каждый из простого списка OR может использовать индекс, который у вас есть для tag_id.

0 голосов
/ 24 апреля 2011

Известно ли количество тегов заранее?Если это не то, что будет расти со временем, я бы изменил tag_id на битовый набор.

WITH T AS 
 (SELECT product_id, bit_or((1<<tag_id)::bigint) tagset 
  FROM product_tag GROUP BY product_id) 
SELECT product_id 
WHERE (tagset & 7)>0 AND (tagset & 56)>0 AND (tagset & 448)>0;

Я использовал Postgres здесь, где & известен как побитовое И;bit_or - это агрегатная функция (здесь SUM будет работать так же хорошо, при условии, что в таблице product_tag нет дубликатов).Магические числа в масках - это просто биты или степени двух.Двойное двоеточие - это актерский состав Postgres.Все здесь должно быть доступно под немного другими именами в другом месте.Но у PG также есть цепочки битов неопределенного размера, и та же логика с цепочками битов может быть реализована для большого количества тегов.

Кстати, ситуация сопоставления всех тегов просто (tagset & mask)=mask.

Именно поэтому ваши индексы работают так быстро;они, вероятно, объединяются в этот тип теста.

0 голосов
/ 21 апреля 2011

Производительность не будет такой высокой, но вы можете сделать вложенный запрос

SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag1, tag2, tag3)
AND ProductID IN (
SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag4, tag5, tag6)
)
AND ProductID IN (
SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag7, tag8, tag9)
)
0 голосов
/ 21 апреля 2011

Я заметил Выберите значения, которые соответствуют различным условиям в разных строках?

Как насчет

SELECT DISTINCT t1.productId FROM product_tags t1
JOIN product_tags t2 ON t1.productId=t2.productId AND t2.tagId IN (tag4,tag5,tag6)
JOIN product_tags t3 ON t1.productId=t3.productId AND t3.tagId IN (tag7, tag8, tag9)
AND t1.tagId IN (tag1,tag2,tag3)

Было бы еще лучше, если бы DISTINCT можно было удалитькак-то.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...