MYSQL / MARIADB架构和示例数据:
CREATE DATABASE IF NOT EXISTS `puzzle` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;
USE `puzzle`;
DROP TABLE IF EXISTS `event`;
CREATE TABLE `event` (
`eventId` bigint(20) NOT NULL AUTO_INCREMENT,
`sourceId` bigint(20) NOT NULL COMMENT 'think of source as camera',
`carNumber` varchar(40) NOT NULL COMMENT 'ex: 5849',
`createdOn` datetime DEFAULT NULL,
PRIMARY KEY (`eventId`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `event` (`eventId`, `sourceId`, `carNumber`, `createdOn`) VALUES
(1, 44, '4456', '2016-09-20 20:24:05'),
(2, 26, '26484', '2016-09-20 20:24:05'),
(3, 5, '4456', '2016-09-20 20:24:06'),
(4, 3, '72704', '2016-09-20 20:24:15'),
(5, 3, '399606', '2016-09-20 20:26:15'),
(6, 5, '4456', '2016-09-20 20:27:25'),
(7, 44, '72704', '2016-09-20 20:29:25'),
(8, 3, '4456', '2016-09-20 20:30:55'),
(9, 44, '26484', '2016-09-20 20:34:55'),
(10, 26, '4456', '2016-09-20 20:35:15'),
(11, 3, '72704', '2016-09-20 20:35:15'),
(12, 3, '399606', '2016-09-20 20:44:35'),
(13, 26, '4456', '2016-09-20 20:49:45');
我希望在20:24到20:45期间获得sourceId = 3 AND(26 OR 44)的CarNumber。由于真实表包含超过3亿条记录,因此查询需要很快。
到目前为止,我可以使用查询的最大值(它甚至不能产生有效结果)
select * from event e where
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
and e.sourceId IN(3,26,44) group by e.carNumber;
提供的数据的正确结果:
carNumber
4456
72704
我真的很困惑并陷入困境。我试过EXISTS,Joins,子查询没有运气,所以我想知道SQL是否能够解决这个问题,还是应该使用后端编码?
正在使用的MySQL / MariaDB版本:
MariaDB的-50年5月5日
的MySQL-51年5月5日
答案 0 :(得分:1)
以下内容应该为您解决问题:
SELECT carNumber
FROM event
WHERE sourceID = 3
AND carNumber IN (SELECT carNumber FROM event WHERE sourceID IN(26,44))
GROUP BY carNumber
WHERE子句查找sourceID
3
的记录,然后确保carnumber
在sourceid
表格中至少有一条其他记录是26
或44
不要为SQL之外的任何代码编写任何代码,因为这绝对是构建SQL以尽快解决的问题。
答案 1 :(得分:1)
您可以使用having
子句过滤组。使用sum()
计算一组数据中确定条件存在的次数
select e.carNumber
from event e
where e.createdOn > '2016-09-20 20:24:00'
and e.createdOn < '2016-09-20 20:45:00'
group by e.carNumber
having sum(e.sourceId = 3) > 0
and sum(e.sourceId IN (26,44)) > 0
答案 2 :(得分:1)
如果您需要快速,那么假设您有event(createdOn, carNumber, SourceId)
的索引,则以下可能有效:
select e.carNumber
from event e
where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
group by e.carNumber
having sum(e.sourceId = 3) > 0 and
sum(e.sourceId IN (26, 44)) > 0;
我倾向于将其更改为:
select e.carNumber
from event e
where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' and
e.sourceId in (3, 26, 44)
group by e.carNumber
having sum(e.sourceId = 3) > 0 and
sum(e.sourceId IN (26, 44)) > 0;
然后为了表现,即便如此:
select carNumber
from ((select carNumber, sourceId
from event e
where e.sourceId = 3 and
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
) union all
(select carNumber, sourceId
from event e
where e.sourceId = 26 and
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
) union all
(select carNumber, sourceId
from event e
where e.sourceId = 44 and
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
)
) e
group by e.carNumber
having sum(e.sourceId = 3) > 0 and
sum(e.sourceId IN (26, 44)) > 0;
此版本可以利用event(sourceId, createdOn, carNumber)
上的索引。每个子查询都应该非常有效地使用这个索引,将少量数据一起用于最终聚合。
答案 3 :(得分:1)
缩小表格大小
对于300M行,您应该使用最实用的最小数据类型。
BIGINT
占用8个字节; INT UNSIGNED
(仅4个字节)通常就足够了(最多40亿)。如果摄像机少于65K,请使用2字节SMALLINT UNSIGNED
。
carNumber
看起来像一个数字,为什么要使用VARCHAR
?您拥有的示例在VARCHAR
中占用5-7个字节,适用于INT UNSIGNED
的4个字节或MEDIUMINT UNSIGNED
的3个字节(最大值为16M)。
缩小表格将有助于选择任何解决方案。
覆盖索引
这已经在其他答案中提出过了,但我想说清楚为什么会有所帮助。如果所有列都存在于单个查询中,则可以在索引的BTree中执行查询,而不触及数据。由于体积较小,通常会更快。 A&#39;覆盖&#39;此查询的索引按任何顺序都有source_id, car_number, createdOn
。
索引中的列顺序
由于索引只能从左到右使用,因此订单很重要。 (这不适用于戈登的第一个选择,首先需要createdOn
。)
sourceId
由=
或IN
处理,因此它应该首先出现。在IN
的情况下,您可能需要5.6或更高版本才能获得IN优化。createdOn
是一个范围,因此查找将停止。carNumber
。因此,大多数(并非所有)建议都需要此订单:INDEX(sourceId, createdOn, carNumber)
。
摆脱auto_increment
你在其他表中使用eventID
吗?如果是这样,那么你应该保留它。如果没有,那么组合(sourceId, createdOn, carNumber)
是唯一的吗?如果是,那么将其PRIMARY KEY
。代理PK在某些情况下很不错,但它会阻碍其他情况下的性能。我建议可能在这里成为障碍。
避免慢速操作
UNION
通常涉及临时表;这增加了开销。虽然UNION
有助于更好地使用索引并避免使用OR
,但tmp表的开销可能会超过看似小结果集的好处。
戈登使用UNION ALL
代替默认UNION DISTINCT
是正确的;后者需要一个重复传递,这对他的查询来说是不必要的。
底线
另一种解决方案
(我不知道这是否更好,但值得一试。)
SELECT carNumber
FROM ( SELECT DISTINCT carNumber
FROM event
WHERE sourceId = 3
AND createdOn >= '2016-09-20 20:24:00'
AND createdOn < '2016-09-20 20:45:00'
) AS x
WHERE EXISTS ( SELECT * FROM event
WHERE carNumber = x.carNumber
AND sourceId IN (26,44)
AND createdOn >= '2016-09-20 20:24:00'
AND createdOn < '2016-09-20 20:45:00'
);
需要两个索引:
(sourceId, createdOn, carNumber) -- as before
(carNumber, sourceId, createdOn) -- to optimize the EXISTS