MySQL查询多对多关系:联合?

Posted

技术标签:

【中文标题】MySQL查询多对多关系:联合?【英文标题】:MySQL query over many-to-many realtion: unions? 【发布时间】:2009-07-30 09:36:11 【问题描述】:

除了这个问题SQL query that gives distinct results that match multiple columns 这有非常巧妙的解决方案,我想知道下一步会如何:

 DOCUMENT_ID |     TAG
----------------------------
   1        |   tag1
   1        |   tag2
   1        |   tag3
   2        |   tag2
   3        |   tag1
   3        |   tag2
   4        |   tag1
   5        |   tag3

因此,要获取所有具有标签 1 和 2 的 document_id,我们将执行如下查询:

SELECT document_id
FROM table
WHERE tag = 'tag1' OR tag = 'tag2'
GROUP BY document_id
HAVING COUNT(DISTINCT tag) = 2

现在,有趣的是,我们将如何获得所有具有标签 1 和 2 的不同 document_id,以及除此之外的具有标签 3 的 id。 我们可以想象进行相同的查询并在它们之间执行联合:

SELECT document_id
FROM table
WHERE tag = "tag1" OR tag = "tag2"
GROUP BY document_id
HAVING COUNT(DISTINCT tag) = 2
UNION
SELECT document_id
FROM table
WHERE tag = "tag3"
GROUP BY document_id

但我想知道如果添加了该条件,我们是否可以考虑另一个初始查询。我想象有许多像这样具有不同标签和标签计数的“联合”。 创建这样的工会链在性能方面不是很糟糕吗?

【问题讨论】:

【参考方案1】:

这仍然使用各种联合,但可能更易于阅读和控制。我对这个查询在大型数据集上的速度非常感兴趣,所以请告诉我它有多快。当我放入你的小数据集时,它花了 0.0001 秒。

SELECT DISTINCT (dt1.document_id)
FROM 
  document_tag dt1,
  (SELECT document_id
    FROM document_tag
    WHERE tag =  'tag1'
  ) AS t1s,
  (SELECT document_id
    FROM document_tag
    WHERE tag =  'tag2'
  ) AS t2s,
  (SELECT document_id
    FROM document_tag
    WHERE tag =  'tag3'
  ) AS t3s
WHERE
  (dt1.document_id = t1s.document_id
  AND dt1.document_id = t2s.document_id
  )
  OR dt1.document_id = t3s.document_id

这将使添加新参数变得容易,因为您已经为每个标签指定了结果集。

例如添加:

OR dt1.document_id = t2s.document_id

到最后还会拿起document_id 2

【讨论】:

【参考方案2】:

可以在单个子句中执行此操作,但是您需要将 WHERE 子句提升为 have 子句才能使用析取词。

【讨论】:

【参考方案3】:

您是对的,当您添加要在其他 UNION 子句中查找的新标签时,它会变得越来越慢。每个 UNION 子句都是一个需要计划和执行的附加查询。另外,完成后您将无法进行排序。

您正在寻找一种基本的数据仓库技术。首先,让我用一个额外的表重新创建您的架构。

create table a (document_id int, tag varchar(10));

insert into a values (1, 'tag1'), (1, 'tag2'), (1, 'tag3'), (2, 'tag2'), 
                     (3, 'tag1'), (3, 'tag2'), (4, 'tag1'), (5, 'tag3');

create table b (tag_group_id int, tag varchar(10));

insert into b values (1, 'tag1'), (1, 'tag2'), (2, 'tag3');

表 b 包含“标签组”。第 1 组包含 tag1 和 tag2,第 2 组包含 tag3。

现在您可以修改表 b 以表示您感兴趣的查询。当您准备好查询时,您可以创建临时表来存储聚合数据:

create temporary table c 
(tag_group_id int, count_tags_in_group int, tags_in_group varchar(255));

insert into c
select 
    tag_group_id,
    count(tag),
    group_concat(tag)
from b
group by tag_group_id;

create temporary table d (document_id int, tag_group_id int, document_tag_count int);

insert into d
select
    a.document_id,
    b.tag_group_id,
    count(a.tag) as document_tag_count
from a
inner join b on a.tag = b.tag
group by a.document_id, b.tag_group_id;

现在 c 包含标签组的标签数,d 包含每个文档对每个标签组的标签数。如果 c 中的一行与 d 中的一行匹配,则意味着该文档具有该标签组中的所有标签。

select 
    d.document_id as "Document ID",
    c.tags_in_group as "Matched Tag Group"
from d
inner join c on d.tag_group_id = c.tag_group_id
            and d.document_tag_count = c.count_tags_in_group

这种方法的一个很酷的地方是,您可以运行类似“有多少文档在每个标记组中具有 50% 或更多标记?”之类的报告?

select 
    d.document_id as "Document ID",
    c.tags_in_group as "Matched Tag Group"
from d
inner join c on d.tag_group_id = c.tag_group_id
            and d.document_tag_count >= 0.5 * c.count_tags_in_group

【讨论】:

以上是关于MySQL查询多对多关系:联合?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 表关系及多表操作(联合查询连接查询子查询)

查询多对多关系 - mySQL

MySql查询多对多关系[重复]

Mysql连接查询匹配所有标签的多个“标签”(多对多关系)?

在 MySQL 中,对于连接谓词之间具有多对多关系的大型表,最有效的查询设计是啥?

MySQL8.0入门系列