对于多对多关系,从多中搜索一个
With a many-to-many relationship, search by the many for the one
我在版本和工件之间存在多对多关系,其中给定版本与多个工件相关联,而给定工件与多个版本相关联。
我知道如何建模:我有一个 releases
table 和一个 ID 列:
CREATE TABLE releases (
release_uuid uuid PRIMARY KEY
);
和带有 ID 列的 artifacts
table:
CREATE TABLE artifacts (
artifact_uuid uuid PRIMARY KEY,
hash bytea
-- other data
);
和一个连接 table 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)
);
我想做的是找到一个版本 "containing" 一组给定的工件,这样我就可以警告重复版本。也就是说,对于工件 A1
、A2
和 A3
,这三个工件究竟定义了哪些版本 Rx
?更形象地说,给定 release_artifacts
table:
release ID | artifact ID
-----------+------------
R1 | A1
R1 | A2
R1 | A3
R2 | A4
R2 | A2
R2 | A3
我可以使用 A1
、A2
、A3
作为输入来执行什么搜索,然后返回 R1
?在 A2
、A3
上搜索会 return NULL
。或者我需要不同的型号吗?我认为如果 release_artifacts
table 将一个版本映射到一组工件 ID 会更容易,但随后我失去了 artifact
table.[= 的参照完整性31=]
我不需要最大性能或最大并发保护,但如果这些不会显着增加查询的复杂性,我会很高兴。这是在 Postgres 9.6 数据库中,尽管我认为这是一个版本。
您可以使用聚合:
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';
这是relational-division的一个例子。这是基本技术库:
- How to filter SQL results in a has-many-through relation
对于给定的(典型的)many-to-many 设置,这是最快的查询之一:
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(推荐!):
- Using same column multiple times in WHERE clause
在 (artifact_id, release_id)
上设置约束(及其实现 index)对性能有很大帮助,而不是在 (release_id, artifact_id)
上相反,因为第一个也是(希望)最具选择性的谓词在 artifact_id
上。在反向组合上增加一个索引以覆盖所有碱基通常是值得的。参见:
- Is a composite index also good for queries on the first field?
- How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
另外 将搜索限制为具有确切给定工件集的版本(没有其他工件)- :
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
。参见:
- Select rows which are not present in other table
应该只会稍微多花点钱,并以类似的方式扩展。
我在版本和工件之间存在多对多关系,其中给定版本与多个工件相关联,而给定工件与多个版本相关联。
我知道如何建模:我有一个 releases
table 和一个 ID 列:
CREATE TABLE releases (
release_uuid uuid PRIMARY KEY
);
和带有 ID 列的 artifacts
table:
CREATE TABLE artifacts (
artifact_uuid uuid PRIMARY KEY,
hash bytea
-- other data
);
和一个连接 table 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)
);
我想做的是找到一个版本 "containing" 一组给定的工件,这样我就可以警告重复版本。也就是说,对于工件 A1
、A2
和 A3
,这三个工件究竟定义了哪些版本 Rx
?更形象地说,给定 release_artifacts
table:
release ID | artifact ID
-----------+------------
R1 | A1
R1 | A2
R1 | A3
R2 | A4
R2 | A2
R2 | A3
我可以使用 A1
、A2
、A3
作为输入来执行什么搜索,然后返回 R1
?在 A2
、A3
上搜索会 return NULL
。或者我需要不同的型号吗?我认为如果 release_artifacts
table 将一个版本映射到一组工件 ID 会更容易,但随后我失去了 artifact
table.[= 的参照完整性31=]
我不需要最大性能或最大并发保护,但如果这些不会显着增加查询的复杂性,我会很高兴。这是在 Postgres 9.6 数据库中,尽管我认为这是一个版本。
您可以使用聚合:
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';
这是relational-division的一个例子。这是基本技术库:
- How to filter SQL results in a has-many-through relation
对于给定的(典型的)many-to-many 设置,这是最快的查询之一:
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(推荐!):
- Using same column multiple times in WHERE clause
在 (artifact_id, release_id)
上设置约束(及其实现 index)对性能有很大帮助,而不是在 (release_id, artifact_id)
上相反,因为第一个也是(希望)最具选择性的谓词在 artifact_id
上。在反向组合上增加一个索引以覆盖所有碱基通常是值得的。参见:
- Is a composite index also good for queries on the first field?
- How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
另外 将搜索限制为具有确切给定工件集的版本(没有其他工件)-
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
。参见:
- Select rows which are not present in other table
应该只会稍微多花点钱,并以类似的方式扩展。