3

我有以下表格:users, tags, tags_data.
tags_data包含tag_iduser_id列以将1 个用户与多个标签的关系链接users起来。tags

tag_id列出具有10011003 tag_id1004的所有用户的最佳方式是什么?
编辑:我的意思是,也可能有其他相关的标签,只要肯定有 1004 OR (1001 AND 1003)。

目前我有两种方法可以做到这一点,都UNION在派生表中使用 a ,无论是在FROM子句中还是在INNER JOIN子句中......

SELECT subsel.user_id, users.name 
FROM   ( SELECT user_id 
         FROM   tags_data
         WHERE  tag_id IN (1001, 1003) 
         GROUP  BY user_id 
         HAVING COUNT(tag_id)=2
        UNION 
         SELECT user_id 
         FROM   tags_data 
         WHERE  tag_id=1004
       ) AS subsel 
LEFT JOIN users ON subsel.user_id=users.user_id

或者

SELECT users.user_id, users.name
FROM   users
INNER JOIN ( SELECT user_id
             FROM   tags_data
             WHERE  tag_id  IN (1001, 1003) 
             GROUP  BY user_id
             HAVING COUNT(tag_id)=2
            UNION 
             SELECT user_id
             FROM   tags_data
             WHERE  tag_id=1004
           ) AS subsel ON users.user_id=subsel.user_id

还有其他表,我将对此进行LEFT JOIN讨论。表中有 50k+ 行,users表中有 150k+ 行tags_data

这是将数据导出到另一个系统的批处理作业,因此不是最终用户运行的实时查询,因此性能并不是非常关键。但是,我想尝试并获得最好的结果。派生表的查询实际上应该非常快,并且在我向返回给客户端的结果添加进一步的连接、函数和计算字段之前缩小结果集的范围是有意义的。稍后我将在更大的数据集上运行这些,以查看是否存在任何性能差异,但运行EXPLAIN显示几乎相同的执行计划。

一般来说,UNIONs除非绝对必要,否则我会尽量避免。但我认为在这种情况下,我几乎必须有一个UNION定义的地方,因为这两个实际上不相关的标准。

我可以在这里使用另一种方法吗?
对于这类问题,是否有某种特定的数据库术语?

完整示例架构:

CREATE TABLE IF NOT EXISTS `tags` (
  `tag_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `tag_name` varchar(255) NOT NULL,
  PRIMARY KEY (`tag_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1006 ;

INSERT INTO `tags` (`tag_id`, `tag_name`) VALUES
(1001, 'tag1001'),
(1002, 'tag1002'),
(1003, 'tag1003'),
(1004, 'tag1004'),
(1005, 'tag1005');

CREATE TABLE IF NOT EXISTS `tags_data` (
  `tags_data_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `tag_id` int(11) NOT NULL,
  PRIMARY KEY (`tags_data_id`),
  KEY `user_id` (`user_id`,`tag_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=11 ;

INSERT INTO `tags_data` (`tags_data_id`, `user_id`, `tag_id`) VALUES
(1, 1, 1001),
(2, 1, 1002),
(3, 1, 1003),
(4, 5, 1001),
(5, 5, 1003),
(6, 5, 1005),
(7, 8, 1004),
(8, 9, 1001),
(9, 9, 1002),
(10, 9, 1004);

CREATE TABLE IF NOT EXISTS `users` (
  `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=11 ;

INSERT INTO `users` (`user_id`, `name`) VALUES
(1, 'user1'),
(2, 'user2'),
(3, 'user3'),
(4, 'user4'),
(5, 'user5'),
(6, 'user6'),
(7, 'user7'),
(8, 'user8'),
(9, 'user9'),
(10, 'user10');
4

3 回答 3

1

如果你正在寻找 MySQL 的性能,你绝对应该避免使用嵌套查询和联合——它们中的大多数会导致临时表创建和没有索引的扫描。很少有派生临时表仍然使用索引并且仅适用于某些特定情况和 MySQL 发行版的示例。

我的建议是将查询重写为仅内部/外部联接,如下所示:

select distinct u.* from users as u 
  left outer join tags_data as t on 
    t.user_id=u.user_id and t.tag_id=1003 
  inner join tags_data as t2 on 
    t2.user_id=u.user_id 
    and (t2.tag_id=1004 or (t2.tag_id=1001 and t.tag_id=1003));

如果您可以确定没有用户可以同时拥有 1004 和(1001 和 1003)标签,您还可以从该查询中删除“distinct”,这样可以避免创建临时表。

您还应该绝对使用索引,如下所示:

create index tags_data__user_id__idx on tags_data(user_id);
create index tags_data__tag_id__idx on tags_data(tag_id);

这将使 150k+ 的结果集非常容易查询。

于 2014-03-28T15:47:34.227 回答
0

高效,但不优雅,而且根本不灵活:

SELECT users.*
FROM users
LEFT JOIN tags_data AS tag1001
    ON (tag1001.user_id = users.user_id AND tag1001.tag_id = 1001)
LEFT JOIN tags_data AS tag1003
    ON (tag1003.user_id = users.user_id AND tag1003.tag_id = 1003)
LEFT JOIN tags_data AS tag1004
    ON (tag1004.user_id = users.user_id AND tag1004.tag_id = 1004)
WHERE (tag1001.tag_id AND tag1003.tag_id) OR (tag1004.tag_id);
于 2014-03-31T11:58:32.827 回答
0

使用内部查询将每个用户的所有标签组合成一个值,然后在 where 子句中使用一个简单的过滤器:

select u.*
from users u
join (
  select user_id, group_concat(tag_id order by tag_id) tags
  from tags_data
  group by user_id
) t on t.user_id = u.user_id
where tags rlike '1001.*1003|1004'

请参阅针对您的示例数据运行的此查询的 SQLFiddle

如果有很多标签,您可以添加where tag_id in (1001, 1003, 1004)到内部查询以减少标签列表的大小作为一个小的优化。测试将显示这是否有很大的不同。

这应该执行得很好,因为每个表只被扫描一次。

于 2014-03-27T23:59:04.470 回答