[英]MySQL - Select row only if previous row field was 0?
我有一张桌子,如下所示:
CREATE TABLE IF NOT EXISTS `status`
(`code` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
,`IMEI` varchar(15) NOT NULL
,`ACC` tinyint(1) NOT NULL
,`datetime` datetime NOT NULL
);
INSERT INTO status VALUES
(1, 123456789012345, 0, '2014-07-09 10:00:00'),
(2, 453253453334445, 0, '2014-07-09 10:05:00'),
(3, 912841851252151, 0, '2014-07-09 10:08:00'),
(4, 123456789012345, 1, '2014-07-09 10:10:00'),
(5, 123456789012345, 1, '2014-07-09 10:15:00');
我需要获取给定IMEI (eg 123456789012345)
所有行,其中ACC = 1,而同一IMEI的上一行具有ACC = 0。 这些行可以一个接一个,也可以很分开。
鉴于上面的示例,我想获得第4行(代码4),而不是第5行(代码5)。
有任何想法吗? 谢谢。
假设您按日期时间表示上一行
SELECT *
FROM status s
WHERE s.imei='123456789012345'
AND s.acc=1
AND (
SELECT acc
FROM status
WHERE imei=s.imei
AND datetime<s.datetime
ORDER BY datetime DESC
LIMIT 1
) = 0
我处理此问题的方式与其他答案中给出的方式大不相同。
我将使用的方法是
1)首先按imei
排序行,然后按每个imei
的datetime
排序。 (我假设datetime
是确定行是否在另一行“之前”的方式。
2)顺序地处理的行中,第一比较imei
从当前行到imei
从以前的行,然后检查是否ACC
从当前行是1
和ACC
从上一行是0
。 然后,我知道当前行是要返回的行。
3)对于结果集中的每个已处理行,都包括一列,指示是否应返回该行
4)仅返回已设置指标列的行
查询如下:
SELECT t.code
, t.imei
, t.acc
, t.datetime
FROM ( SELECT IF(s.imei=@prev_imei AND s.acc=1 AND @prev_acc=0,1,0) AS ret
, s.code AS code
, @prev_imei := s.imei AS imei
, @prev_acc := s.acc AS acc
, s.datetime AS datetime
FROM (SELECT @prev_imei := NULL, @prev_acc := NULL) i
CROSS
JOIN `status` s
WHERE s.imei = '123456789012345'
ORDER BY s.imei, s.datetime, s.code
) t
WHERE t.ret = 1
(我可以对其进行一些说明,以解释其工作原理。)
但是这种方法的最大缺点是,它要求MySQL将内联视图具体化为派生表(临时MyISAM表)。 如果status
表上没有谓词(WHERE子句),则内联视图本质上将是整个status
表的副本。 而在MySQL 5.5和更早版本中,该派生表将不会被索引。 因此,这可能会带来大型设备的性能问题。
在内联视图查询中包含谓词(例如WHERE s.imei = '123456789'
来限制status
表中的行)可能会充分限制临时MyISAM表的大小。
这种方法的另一个陷阱是不能保证语句中用户定义变量的行为。 但是,我们确实观察到了可以使用的一致行为。 它确实起作用,但是MySQL文档警告不能保证该行为。
这是MySQL如何处理此查询的粗略概述。
首先,MySQL对别名为i
的内联视图运行查询。 我们真的不在乎此查询返回的内容,只是由于JOIN操作,我们需要它仅返回一行。 我们关心的是两个MySQL用户定义变量@prev_imei
和@prev_acc
。 稍后,我们将使用这些用户定义的变量来“保留”先前处理的行中的值,因此我们可以将这些值与当前行进行比较。
status
表中的行根据ORDER BY子句顺序处理。 (这可能会在将来的发行版中更改,但我们可以观察到它在MySQL 5.1和5.5中的工作方式与此相同。)
对于每一行,我们将当前行中的imei
和acc
值与上一行中保留的值进行比较。 如果IF表达式中的布尔值计算为TRUE,则返回1,以指示应返回此行。 否则,我们返回0,以表示我们不想返回此行。 (对于处理的第一行,我们先前将用户定义的变量初始化为NULL,因此IF表达式的计算结果为0。)
@prev_imei := s.imei
和@prev_acc := s.acc
将当前行中的值分配给用户定义的值,因此它们可用于处理的下一行。
请注意,在使用当前行中的值覆盖之前的值之前 ,对用户定义的变量(SELECT列表中的第一个表达式) 进行测试非常重要。
我们可以仅从内联视图t
运行查询,以观察行为。
外部查询从内联视图返回将派生的ret
列设置为1的行,而这些行我们要返回。
select * from status s1
WHERE
ACC = 1
AND code = (SELECT MIN(CODE) FROM status WHERE acc = 1 and IMEI = s1.IMEI)
AND EXISTS (SELECT * FROM status WHERE IMEI = s1.IMEI AND ACC = 0)
AND IMEI = 123456789012345
SELECT b.code,b.imei,b.acc,b.datetime
FROM
( SELECT x.*
, COUNT(*) rank
FROM status x
JOIN status y
ON y.imei = x.imei
AND y.datetime <= x.datetime
GROUP
BY x.code
) a
JOIN
( SELECT x.*
, COUNT(*) rank
FROM status x
JOIN status y
ON y.imei = x.imei
AND y.datetime <= x.datetime
GROUP
BY x.code
) b
ON b.imei = a.imei
AND b.rank = a.rank + 1
WHERE b.acc = 1
AND a.acc = 0;
您可以执行常规的IN()
,然后将所有重复项IMEI
为一group
(也可以使用limit
但这仅适用于一个IMEI
)
设定:
INSERT INTO `status`
VALUES
(1, 123456789012345, 0, '2014-07-09 10:00:00'),
(2, 453253453334445, 0, '2014-07-09 10:05:00'),
(3, 912841851252151, 0, '2014-07-09 10:08:00'),
(4, 123456789012345, 1, '2014-07-09 10:10:00'),
(5, 123456789012345, 1, '2014-07-09 10:15:00'),
(6, 123456789012345, 1, '2014-07-09 10:15:00'),
(7, 453253453334445, 1, '2014-07-09 10:15:00');
查询:
SELECT * FROM status
WHERE ACC = 1 AND IMEI IN(
SELECT DISTINCT IMEI FROM status
WHERE ACC = 0)
GROUP BY imei;
结果:
与多个0到1的IMEI
一起使用... IMAGE
编辑:如果您也想按输入的日期,则可以先按日期排序,然后再分组。
SELECT * FROM(
SELECT * FROM status
WHERE ACC = 1 AND IMEI IN(
SELECT DISTINCT IMEI FROM status
WHERE ACC = 0)
ORDER BY datetime
) AS t
GROUP BY imei;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.