Reputation: 345
I have a table as below:
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');
I need to get all rows for a given IMEI (e.g 123456789012345)
where ACC=1 AND the previous row for same IMEI has ACC=0. The rows may be one after the other or very apart.
Given the exampl above, I'd want to get the 4th row (code 4) but not 5th (code 5).
Any ideas? Thanks.
Upvotes: 0
Views: 940
Reputation: 108530
The way I would approach this problem is much different from the approaches given in other answers.
The approach I would use would be to
1) order the rows, first by imei
, and then by datetime
within each imei
. (I'm assuming that datetime
is how you are going to determine if a row is "previous" to another row.
2) sequentially process the rows, first comparing imei
from the current row to the imei
from the previous row, and then checking if the ACC
from the current row is 1
and the ACC
from the previous row is 0
. Then I would know that the current row was a row to be returned.
3) for each processed row, in the resultset, include a column that indicates whether the row should be returned or not
4) return only the rows that have the indicator column set
A query something like this:
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
(I can unpack that a bit, to explain how it works.)
But the big drawback of this approach is that it requires MySQL to materialize the inline view as a derived table (temporary MyISAM table). If there was no predicate (WHERE clause) on the status
table, the inline view would essentially be a copy of the entire status
table. And with MySQL 5.5 and earlier, that derived table won't be indexed. So, this could present a performance issue for large sets.
Including predicates (e.g. WHERE s.imei = '123456789'
to limit rows from the status
table in the inline view query may sufficiently limit the size of the temporary MyISAM table.
The other gotcha with this approach is that the behavior of user-defined variables in the statement is not guaranteed. But we do observe a consistent behavior, which we can make use of; it does work, but the MySQL documentation warns that the behavior is not guaranteed.
Here's a rough overview of how MySQL processes this query.
First, MySQL runs the query for the inline view aliased as i
. We don't really care what this query returns, except that we need it to return exactly one row, because of the JOIN operation. What we care about is the initialization of the two MySQL user-defined variables, @prev_imei
and @prev_acc
. Later, we are going to use these user-defined variables to "preserve" the values from the previously processed row, so we can compare those values to the current row.
The rows from the status
table are processed in sequence, according to the ORDER BY clause. (This may change in some future release, but we can observe that it works like this in MySQL 5.1 and 5.5.)
For each row, we compare the values of imei
and acc
from the current row to the values preserved from the previous row. If the boolean in the IF expression evaluates to TRUE, we return a 1, to indicate that this row should be returned. Otherwise, we return a 0, to indicate that we don't want to return this row. (For the first row processed, we previously initialized the user-defined variables to NULL, so the IF expression will evaluate to 0.)
The @prev_imei := s.imei
and @prev_acc := s.acc
assigns the values from the current row to the user-defined values, so they will be available for the next row processed.
Note that it's important that the tests of the user-defined variables (the first expression in the SELECT list) before we overwrite the previous values with the values from the current row.
We can run just the query from the inline view t
, to observe the behavior.
The outer query returns rows from the inline view that have the derived ret
column set to a 1, rows that we wanted to return.
Upvotes: 2
Reputation: 25872
you can do a regular IN()
and then group
any duplicates (you could also use a limit
but that would only work for one IMEI
)
SETUP:
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');
QUERY:
SELECT * FROM status
WHERE ACC = 1 AND IMEI IN(
SELECT DISTINCT IMEI FROM status
WHERE ACC = 0)
GROUP BY imei;
RESULTS:
works with multiple IMEI
that have a 0 then a 1... IMAGE
EDIT: if you would like to go by the date entered as well then you can just order it first by date and then group.
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;
Upvotes: 0
Reputation: 13110
Assuming that you mean previous row by datetime
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
Upvotes: 2
Reputation: 33935
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;
Upvotes: 0
Reputation: 3135
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
Upvotes: 0