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

时间:2009-07-30 09:36:12

标签: performance mysql many-to-many

除了这个问题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_ids,我们将执行如下查询:

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

现在,有趣的是,我们将如何获得具有标签1和2的所有不同document_ids,以及具有标签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

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

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

你是对的,当你在其他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