我有“人员 <- many-to-many -> 标签”关联和“tag_group <- many-to-many -> 标签”关联。我正在尝试查找选择了相同标签的人员和标签组的组合。
标签
身份证 | 标签 |
---|---|
1 | 注册 |
2 | 绿色 |
3 | 狗 |
4 | 猫 |
标签组
身份证 | 标签 | 排序 |
---|---|---|
1 | 颜色 | 2 |
2 | 动物 | 3 |
3 | 混合 | 1 |
标签_组_标签
tag_groups.id | 标签.id | 评论 |
---|---|---|
1 | 1 | (颜色 - 红色) |
1 | 2 | (颜色 - 绿色) |
2 | 3 | (动物 - 狗) |
2 | 4 | (动物 - 猫) |
3 | 1 | (混合-红色) |
3 | 2 | (混合-绿色) |
3 | 4 | (混合-猫) |
人
身份证 | 姓名 |
---|---|
1 | 约翰 |
2 | 简 |
3 | 皮特 |
人_标签
people.id | 标签.id | 评论 |
---|---|---|
1 | 1 | 约翰链接到所有标签 |
1 | 2 | ... |
1 | 3 | ... |
1 | 4 | ... |
2 | 1 | Jane 链接到与 Mix 组相同的标签 |
2 | 2 | ... |
2 | 4 | ... |
3 | 1 | Pete 仅链接到标签 Red |
问题1:我怎样才能找到人和tag_groups的交集——找到每个组中所有被标记为相同标签的人?
期望的结果1
people.id | tag_groups.id | 评论 |
---|---|---|
1 | 1 | John - 颜色 -> people_tags 将 John 链接到红色和绿色 |
1 | 2 | John - 动物 -> people_tags 将 John 链接到狗和猫 |
1 | 3 | John - Mix -> people_tags 将 John 链接到所有 Red、Green 和 Cat |
2 | 1 | Jane - 颜色 -> people_tags 将 Jane 链接到红色和绿色,以及猫,但这与颜色组无关 |
2 | 3 | Jane - 混合 -> people_tags 将 Jane 链接到所有红色、绿色和猫 |
-- Pete 不在结果中,因为 people_tags 没有将他链接到足够的标签来查找与任何 tag_group 的完全匹配
我对期望结果的看法1
这可以使用数组、CTE 语法来解决它以提高易读性。
WITH people_cte AS (select people_id, array_agg(tags_id) tags from people_tags group by people_id),
groups_cte AS (select tag_groups_id, array_agg(tags_id) tags from tag_groups_tags group by tag_groups_id),
combinations AS (SELECT people_id, tag_groups_id
FROM people_cte,
groups_cte
WHERE people_cte.tags @> groups_cte.tags)
SELECT people_id, name, tag_groups_id, label
FROM combinations
JOIN people ON combinations.people_id = people.id
JOIN tag_groups ON combinations.tag_groups_id = tag_groups.id;
我担心它必须首先让所有的人和团体计算标签数组,然后进行重叠。也许这是一个很好的第一步,但我必须能够将人和群体的范围缩小到只有重叠的人和群体,忽略那些永远无法匹配的人和群体。
我对性能的担忧是否错误?我想我需要找到一个不使用数组的解决方案。
问题2: 如果我按tag_groups.sort ASC 对它们进行排序,如何才能只找到第一个匹配的tag_group?
期望结果2:
people.id | tag_groups.id | 评论 |
---|---|---|
1 | 3 | John - Mix -> people_tags 将 John 链接到所有组,但 Mix 的数量最少 |
2 | 3 | Jane - Mix -> people_tags 将 Jane 链接到 Colors 和 Mix,并且 Mix 由于排序值而获胜 |
-- Pete 不在结果中,因为 people_tags 没有将他链接到足够的标签来查找与任何 tag_group 的完全匹配
问题 3: 我怎样才能获得第一个匹配项(类似于结果 2),但以某种方式左加入组以将所有带有
null
的人包含在 tag_groups 中?
期望结果3:
people.id | tag_groups.id | 评论 |
---|---|---|
1 | 3 | (约翰 - 混合) |
2 | 3 | (简 - 混合) |
3 | 空 | (皮特 - 未找到匹配的 tag_groups) |
数据库小提琴:https://www.db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/14043
就像备份一样,这些将使架构和数据就位:
create table tags (id int, label text);
create table people (id int, name text);
create table tag_groups (id int, label text, sort int);
create table tag_groups_tags (tag_groups_id int, tags_id int);
create table people_tags (people_id int, tags_id int);
insert into tags (id, label) values (1, 'Red'), (2, 'Green'), (3, 'Dog'), (4, 'Cat');
insert into people (id, name) values (1, 'John'), (2, 'Jane'), (3, 'Pete');
insert into tag_groups (id, label, sort) values (1, 'Colours', 2), (2, 'Animals', 3), (3, 'Mix', 1);
insert into tag_groups_tags (tag_groups_id, tags_id) values (1, 1), (1, 2), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4);
insert into people_tags (people_id, tags_id) values (1, 1), (1, 2), (1, 3), (1, 4), (2, 1), (2, 2), (2, 4), (3, 1);
我可能会遗漏一些东西,但是这样的东西会有帮助吗?
with p_tags (people_id, tags) as (
select people_id, array_agg(tags_id order by tags_id)
from people_tags
group by people_id
),
g_tags (group_id, tags) as (
select tag_groups_id, array_agg(tags_id order by tags_id)
from tag_groups_tags
group by tag_groups_id
)
select *
from p_tags pt
join g_tags gt on (pt.tags = gt.tags);
基本上将按 group_id 分组的标签数组与按 people_id 分组的标签数组进行比较。