Reputation: 741
I have a table like this:
id | status | user_id | created_at
---:--------:---------:--------------------
1 | 0 | 1 | 2014-01-05 07:23:15
2 | 1 | 1 | 2014-01-05 07:23:16
3 | 1 | 1 | 2014-01-05 07:23:17
4 | 0 | 1 | 2014-01-05 07:23:18
5 | 0 | 1 | 2014-01-05 07:23:19
6 | 1 | 1 | 2014-01-05 07:23:20
7 | 0 | 2 | 2014-01-05 07:23:21
8 | 0 | 1 | 2014-01-05 07:23:22
9 | 0 | 2 | 2014-01-05 07:23:23
10 | 1 | 2 | 2014-01-05 07:23:24
11 | 0 | 2 | 2014-01-05 07:23:25
12 | 1 | 2 | 2014-01-05 07:23:26
I'd like to query the changes on the status
field, grouped by user_id
, always fetching the last status (based on created_at
). The result of the query should be something like this:
id | status | user_id | created_at
---:--------:---------:--------------------
1 | 0 | 1 | 2014-01-05 07:23:15
3 | 1 | 1 | 2014-01-05 07:23:17
5 | 0 | 1 | 2014-01-05 07:23:19
6 | 1 | 1 | 2014-01-05 07:23:20
8 | 0 | 1 | 2014-01-05 07:23:22
9 | 0 | 2 | 2014-01-05 07:23:23
10 | 1 | 2 | 2014-01-05 07:23:24
11 | 0 | 2 | 2014-01-05 07:23:25
12 | 1 | 2 | 2014-01-05 07:23:26
Is there a way to query for changes in SQL in a situation like this? How this query should be written?
Upvotes: 1
Views: 56
Reputation: 1859
Probably it is a good idea to use variables in MySQL in such scenarios.
Here is one quick attempt with steps elaborated. Clean and tweak it up to suit the requirements and performance.
select id, status, user_id, created_at from
(select id, status, user_id, created_at,
(case when @user_id != user_id then 'true' else 'false' end) as user_changed,
(case when @status != status then 'true' else 'false' end) as status_changed,
(case when @user_id != user_id then @user_id := user_id end) as new_user_id,
(case when @status != status then @status := status end) as new_status
from (select * from logs order by user_id asc, created_at desc) l
join (select @user_id := 0) u
join (select @status := 0) s) q
where user_changed = 'true' or status_changed = 'true'
order by id
;
Upvotes: 1
Reputation: 33935
Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,status TINYINT NOT NULL DEFAULT 1
,user_id INT NOT NULL
,created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO my_table VALUES
(1 , 0 , 1 ,'2014-01-05 07:23:15'),
(2 , 1 , 1 ,'2014-01-05 07:23:16'),
(3 , 1 , 1 ,'2014-01-05 07:23:17'),
(4 , 0 , 1 ,'2014-01-05 07:23:18'),
(5 , 0 , 1 ,'2014-01-05 07:23:19'),
(6 , 1 , 1 ,'2014-01-05 07:23:20'),
(7 , 0 , 2 ,'2014-01-05 07:23:21'),
(8 , 0 , 1 ,'2014-01-05 07:23:22'),
(9 , 0 , 2 ,'2014-01-05 07:23:23'),
(10 , 1 , 2 ,'2014-01-05 07:23:24'),
(11 , 0 , 2 ,'2014-01-05 07:23:25'),
(12 , 1 , 2 ,'2014-01-05 07:23:26');
For the solution provided below it actually doesn't matter that the id is contiguous, just that it's sequential. I've broken the solution down into bits so you can see what it's doing...
The first part ranks results by user...
SELECT x.*
, COUNT(*) rank
FROM my_table x
JOIN my_table y
ON y.user_id = x.user_id
AND y.id <= x.id
GROUP
BY x.id
ORDER
BY x.user_id,rank;
+----+--------+---------+---------------------+------+
| id | status | user_id | created_at | rank |
+----+--------+---------+---------------------+------+
| 1 | 0 | 1 | 2014-01-05 07:23:15 | 1 |
| 2 | 1 | 1 | 2014-01-05 07:23:16 | 2 |
| 3 | 1 | 1 | 2014-01-05 07:23:17 | 3 |
| 4 | 0 | 1 | 2014-01-05 07:23:18 | 4 |
| 5 | 0 | 1 | 2014-01-05 07:23:19 | 5 |
| 6 | 1 | 1 | 2014-01-05 07:23:20 | 6 |
| 8 | 0 | 1 | 2014-01-05 07:23:22 | 7 |
| 7 | 0 | 2 | 2014-01-05 07:23:21 | 1 |
| 9 | 0 | 2 | 2014-01-05 07:23:23 | 2 |
| 10 | 1 | 2 | 2014-01-05 07:23:24 | 3 |
| 11 | 0 | 2 | 2014-01-05 07:23:25 | 4 |
| 12 | 1 | 2 | 2014-01-05 07:23:26 | 5 |
+----+--------+---------+---------------------+------+
The second part joins this query to itself, and highlights anomalies...
SELECT a.*
, b.id
FROM
( SELECT x.*
, COUNT(*) rank
FROM my_table x
JOIN my_table y
ON y.user_id = x.user_id
AND y.id <= x.id
GROUP
BY x.id
) a
LEFT
JOIN
( SELECT x.*
, COUNT(*) rank
FROM my_table x
JOIN my_table y
ON y.user_id = x.user_id
AND y.id <= x.id
GROUP
BY x.id
) b
ON b.user_id = a.user_id
AND b.status = a.status
AND b.rank = a.rank + 1;
+----+--------+---------+---------------------+------+------+
| id | status | user_id | created_at | rank | id |
+----+--------+---------+---------------------+------+------+
| 1 | 0 | 1 | 2014-01-05 07:23:15 | 1 | NULL |
| 2 | 1 | 1 | 2014-01-05 07:23:16 | 2 | 3 |
| 3 | 1 | 1 | 2014-01-05 07:23:17 | 3 | NULL |
| 4 | 0 | 1 | 2014-01-05 07:23:18 | 4 | 5 |
| 5 | 0 | 1 | 2014-01-05 07:23:19 | 5 | NULL |
| 6 | 1 | 1 | 2014-01-05 07:23:20 | 6 | NULL |
| 7 | 0 | 2 | 2014-01-05 07:23:21 | 1 | 9 |
| 8 | 0 | 1 | 2014-01-05 07:23:22 | 7 | NULL |
| 9 | 0 | 2 | 2014-01-05 07:23:23 | 2 | NULL |
| 10 | 1 | 2 | 2014-01-05 07:23:24 | 3 | NULL |
| 11 | 0 | 2 | 2014-01-05 07:23:25 | 4 | NULL |
| 12 | 1 | 2 | 2014-01-05 07:23:26 | 5 | NULL |
+----+--------+---------+---------------------+------+------+
The third and final part is deliberately left as an exercise for the reader, however, one drawback with this solution is that it does not scale particularly well.
Upvotes: 2