Имея отношения многие ко многим, ищите многими множество - PullRequest
1 голос
/ 11 июня 2019

У меня есть отношение "многие ко многим" между выпусками и артефактами, когда данный выпуск связан с несколькими артефактами, а данный артефакт связан с несколькими выпусками.

Я понимаю, как смоделировать это:У меня есть таблица releases со столбцом идентификатора:

CREATE TABLE releases (
    release_uuid uuid PRIMARY KEY
);

и таблица artifacts со столбцом идентификатора:

CREATE TABLE artifacts (
    artifact_uuid uuid PRIMARY KEY,
    hash          bytea
    -- other data
);

и соединительная таблица release_artifacts, которая имеетстолбцы внешнего ключа от каждого из остальных:

CREATE TABLE release_artifacts (
    id            serial PRIMARY KEY,
    release_uuid  uuid REFERENCES releases(release_uuid) NOT NULL,
    artifact_uuid uuid REFERENCES artifacts(artifact_uuid) NOT NULL,
    UNIQUE (release_uuid, artifact_uuid)
);

Что я хочу сделать, так это найти релиз, «содержащий» заданный набор артефактов, чтобы я мог предупреждать о дублированных выпусках.То есть, для артефактов A1, A2 и A3, какой релиз (ы) Rx определяется именно этими тремя артефактами?Более наглядно, учитывая таблицу release_artifacts:

release ID | artifact ID
-----------+------------
R1         | A1
R1         | A2
R1         | A3
R2         | A4
R2         | A2
R2         | A3

, какой поиск я могу выполнить с A1, A2, A3 в качестве ввода, которое вернет мне R1?Поиск по A2, A3 вернет NULL.Или мне нужна другая модель?Я предполагаю, что было бы проще, если бы таблица release_artifacts отображала выпуск в массив идентификаторов артефактов, но тогда я теряю ссылочную целостность с таблицей artifact.

Мне не нужна максимальная производительность илимаксимальная защита от параллелизма, но я был бы рад, если бы эти вещи не увеличили сложность запроса.Это в базе данных Postgres 9.6, хотя я бы посчитал, что это версия версии.

Ответы [ 2 ]

3 голосов
/ 11 июня 2019

Вы можете использовать агрегацию:

select release_id
from release_artifacts
group by release_id
having sum( artifact_id in ('A1', 'A2', 'A3') ) = 3 and
       count(*) = 3;

Это не предполагает дублирования.

Или вы можете использовать агрегацию строк или массивов:

select release_id
from release_artifacts
group by release_id
having string_agg(artifact_id order by artifact_id) = 'A1,A2,A3';
1 голос
/ 11 июня 2019

Это случай .Вот арсенал основных приемов:

Для ваших данных (типично) настройка многие-ко-многим, это один из самых быстрых возможных запросов:

SELECT release_id
FROM   release_artifacts ra1
JOIN   release_artifacts ra2 USING (release_id)
JOIN   release_artifacts ra3 USING (release_id)
WHERE  ra1.artifact_id = 'A1' 
AND    ra2.artifact_id = 'A2' 
AND    ra3.artifact_id = 'A3';

Недостаток этого запроса: вам нужно настроить сборку для количества искомых артефактов.Если это всегда 3 , недостатков нет вообще.

Для динамического числа артефактов вы можете построить запрос динамически.Или используйте рекурсивный CTE, как указано здесь (рекомендуется!):

Это значительно повышает производительностьбит для ограничения (и его реализации index ) на (artifact_id, release_id), а не наоборот на (release_id, artifact_id), так как первый и (надеюсь) наиболее селективный предикат находится на artifact_id.Часто бывает полезно иметь дополнительный индекс на обратной комбинации, чтобы охватить все базы.См .:

До дополнительно ограничивает поиск выпусками с точным заданным набором артефактов (и без дополнительных) - , как вы прокомментировали:

SELECT release_id
FROM   release_artifacts ra1
JOIN   release_artifacts ra2 USING (release_uuid)
JOIN   release_artifacts ra3 USING (release_uuid)
WHERE  ra1.artifact_uuid = 'A1' 
AND    ra2.artifact_uuid = 'A2'
AND    ra2.artifact_uuid = 'A3'
AND    NOT EXISTS (      -- no other artifacts
   SELECT FROM release_artifacts rax
   WHERE  rax.release_uuid   = ra1.release_uuid
   AND    rax.artifact_uuid <> ra1.artifact_uuid
   AND    rax.artifact_uuid <> ra2.artifact_uuid
   AND    rax.artifact_uuid <> ra3.artifact_uuid
   );

В качестве альтернативы:

   ...
   AND    rax.artifact_uuid <> ALL ('{A1, A2, A3}'::uuid[])
   );

Или с LEFT JOIN / IS NULL.См .:

Должен стоить только немного больше и масштабироваться аналогичным образом.

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