我有以下表格:users
,tags
,tags_data
tags_data
包含tag_id
和user_id
列,用于将users
与tags
链接为1个用户与多个代码的关系。
列出具有tag_id
1001 AND 1003, OR tag_id
1004的所有用户的最佳方式是什么?
编辑:我的意思是,只要肯定有1004 OR(1001和1003),也可以有其他相关的标签。
目前我有两种方法可以做到这一点,在UNION
子句或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
其他表格。 users
表中的50k +行和tags_data
表中的150k +行。
这是一个将数据导出到另一个系统的批处理作业,因此不是最终用户运行的实时查询,因此性能不是非常关键。但是我想尽力获得最好的结果。派生表的查询实际上应该非常快,在我将更多连接,函数和计算字段添加到返回给客户端的结果之前,缩小结果集的范围是有意义的。稍后我将在更大的数据集上运行这些数据集以查看是否存在任何性能差异,但运行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');
答案 0 :(得分: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 +结果集。
答案 1 :(得分: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'
此查询的See SQLFiddle针对您的示例数据运行。
如果许多标记位于其中,您可以将where tag_id in (1001, 1003, 1004)
添加到内部查询中,以将标记列表的大小减小为小优化。测试将显示这是否有很大不同。
这应该表现得非常好,因为每个表只扫描一次。
答案 2 :(得分: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);