获得通过特定相机的汽车

时间:2016-09-20 17:41:21

标签: mysql sql mariadb

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日

4 个答案:

答案 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的记录,然后确保carnumbersourceid表格中至少有一条其他记录是2644

不要为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。)

  1. sourceId=IN处理,因此它应该首先出现。在IN的情况下,您可能需要5.6或更高版本才能获得IN优化。
  2. createdOn是一个范围,因此查找将停止。
  3. 对于&#34;覆盖&#34;,现在可以添加任何额外的列。在这种情况下,carNumber
  4. 因此,大多数(并非所有)建议都需要此订单:INDEX(sourceId, createdOn, carNumber)

    摆脱auto_increment

    你在其他表中使用eventID吗?如果是这样,那么你应该保留它。如果没有,那么组合(sourceId, createdOn, carNumber)是唯一的吗?如果是,那么将其PRIMARY KEY。代理PK在某些情况下很不错,但它会阻碍其他情况下的性能。我建议可能在这里成为障碍。

    避免慢速操作

    UNION通常涉及临时表;这增加了开销。虽然UNION有助于更好地使用索引并避免使用OR,但tmp表的开销可能会超过看似小结果集的好处。

    戈登使用UNION ALL代替默认UNION DISTINCT是正确的;后者需要一个重复传递,这对他的查询来说是不必要的。

    底线

    1. 收缩桌子。
    2. 尽可能改变PK;如果没有,请添加建议的索引。
    3. 升级至至少5.6
    4. 使用Gordon的第二个查询。
    5. 另一种解决方案

      (我不知道这是否更好,但值得一试。)

      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