如何找到通过多对多关联间接链接的两个表的交集?

问题描述 投票:0回答:1

我有“人员 <- 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);
postgresql many-to-many
1个回答
0
投票

我可能会遗漏一些东西,但是这样的东西会有帮助吗?

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 分组的标签数组进行比较。

© www.soinside.com 2019 - 2024. All rights reserved.