Соединение между таблицей сопоставления (соединения) с определенной мощностью - PullRequest
6 голосов
/ 07 февраля 2012

У меня простой вопрос о наиболее эффективном способе выполнения определенного объединения.

Возьмите эти три таблицы, настоящие имена были изменены, чтобы защитить невинных:

Таблица: животное

animal_id   name   ...
======================
1           bunny
2           bear
3           cat
4           mouse

Таблица: теги

tag_id     tag
==================
1          fluffy
2          brown
3          cute
4          small

Таблица сопоставления: animal_tag

animal_id   tag_id
==================
1           1
1           2
1           3
2           2
3           4
4           2

Я хочу найти всех животных, помеченных как ' пушистый ', ' коричневый ' и ' cute '. То есть животное должно быть помечено всеми тремя . В действительности количество требуемых тегов может варьироваться, но оно не должно иметь значения для этого обсуждения. Вот запрос, который я придумал:

SELECT * FROM animal
JOIN (
      SELECT at.animal_id FROM animal_tag at
      WHERE at.tag_id IN (
                          SELECT tg.tag_id FROM tag tg
                          WHERE tg.tag='fluffy' OR tg.tag='brown' OR tg.tag='cute'
                          )
      GROUP BY at.animal_id HAVING COUNT(at.tag_id)=3
      ) AS jt
ON animal.animal_id=jt.animal_id

Для таблицы с тысячами «животных» и сотнями «тегов» этот запрос выполняется прилично ... 10 секунд миллисекунд. Однако, когда я смотрю на план запроса (Apache Derby - это DB), оценочная стоимость оптимизатора довольно высока (9945,12), а план довольно обширен. Для такого «простого» запроса я обычно пытаюсь получить планы запросов с ориентировочной стоимостью в одну или две цифры.

Итак, мой вопрос: есть ли лучший способ выполнить этот запрос? Похоже на простой запрос, но я был в тупике, придумывая что-то лучше.

Ответы [ 5 ]

1 голос
/ 08 февраля 2012

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

Хотя я много лет назад проходил курс по модели реляционных данных Кодда, курс, как и многие, на самом деле не охватывал реляционное деление.Невольно мой исходный запрос на самом деле является приложением реляционного деления.

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

1 голос
/ 07 февраля 2012

попробуйте это:

SELECT DISTINCT f.Animal_ID, g.Name
FROM Animal f INNER JOIN 
    (SELECT a.Animal_ID, a.Name, COUNT(*) as iCount
     FROM   Animal a INNER JOIN Animal_Tag b
                  ON a.Animal_ID = b.animal_ID
                     INNER JOIN Tags c
                  On b.tag_ID = c.tag_ID
    WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
    GROUP BY a.Animal_ID) g
WHERE g.iCount = 3 -- No. of tags

ОБНОВЛЕНИЕ

    SELECT DISTINCT a.Animal_ID, a.Name, COUNT(*) as iCount
    FROM    Animal a INNER JOIN Animal_Tag b
                  ON a.Animal_ID = b.animal_ID
                     INNER JOIN Tags c
                  On b.tag_ID = c.tag_ID
    WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
    GROUP BY Animal_ID
    HAVING  iCount = 3 -- No. of tags
1 голос
/ 07 февраля 2012

Дайте это вращение:

SELECT a.*
FROM animal a
INNER JOIN 
  ( 
    SELECT at.animal_id
    FROM tag t
    INNER JOIN animal_tag at ON at.tag_id = t.tag_id
    WHERE tag IN ('fluffy', 'brown', 'cute')
    GROUP BY at.animal_id
    HAVING count(*) = 3
  ) f ON  a.animal_id = f.animal_id

Вот еще один вариант, просто для удовольствия:

SELECT a.*
FROM animal a
INNER JOIN animal_tag at1 on at1.animal_id = a.animal_id
INNER JOIN tag t1 on t1.tag_id = at1.tag_id
INNER JOIN animal_tag at2 on at2.animal_id = a.animal_id
INNER JOIN tag t2 on t2.tag_id = at2.tag_id
INNER JOIN animal_tag at3 on at3.animal_id = a.animal_id
INNER JOIN tag t3 on t3.tag_id = at3.tag_id
WHERE t1.tag = 'fluffy' AND t2.tag = 'brown' AND t3.tag = 'cute'

Я не ожидаю, что этот последний вариант будет успешным ... другие параметры не требуют многократного возврата к таблице тегов для разрешения имени тега из идентификатора ... но вы никогда не знаете, что запрос оптимизатор будет делать, пока вы не попробуете.

1 голос
/ 07 февраля 2012

Вы можете создать временную таблицу, используя ОБЪЯВИТЬ ГЛОБАЛЬНУЮ ВРЕМЕННУЮ ТАБЛИЦУ А затем сделайте ВНУТРЕННЕЕ СОЕДИНЕНИЕ, чтобы устранить «ГДЕ ВХОД». Работа с объединениями, основанными на наборах, обычно намного эффективнее, чем операторы Where, которые должны оцениваться для каждой строки.

0 голосов
/ 07 февраля 2012

Мне было интересно, как плохо было бы использовать там реляционное деление.Можете ли вы дать ему пробежку?Я знаю, что это займет больше времени, но я заинтригован тем, сколько :) Если вы можете предоставить и приблизительную стоимость, и время, это было бы здорово.

select a2.animal_id, a2.animal_name from animal2 a2
where not exists (
    select * from animal1 a1, tags t1
    where not exists (
        select * from animal_tag at1
        where at1.animal_id = a1.animal_id and at1.animal_tag = t1.tag_id
    ) and a2.animal_id = a1.animal_id and t1.tag in ('fluffy', 'brown', 'cute')
)

Теперь ищем быстрый запрос,Я не могу думать быстрее, чем Джон или твой.На самом деле Джон может быть немного медленнее, чем у вас, потому что он выполняет ненужные операции (уберите отличное и уберите количество (*) из выбора):

SELECT a.Animal_ID, a.Name FROM Animal a
INNER JOIN Animal_Tag b ON a.Animal_ID = b.animal_ID
INNER JOIN Tags c On b.tag_ID = c.tag_ID
WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
GROUP BY Animal_ID, a.Name
HAVING count(*) = 3 -- No. of tags

Это должно быть так же быстро, как у вас.

PS: есть ли способ убрать эту проклятую 3 без дублирования предложения where?Мой мозг кипит:)

...