add a new entry if doesn't exist in mysql table - mysql

Ok let me reframe my same question, to aim to the right output.
I have a table of below structure.
mysql> desc depot;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| recd | date | YES | | NULL | |
| id | int(11) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
Currently I have records in the below manner.
mysql> select * from depot;
+---------------------+------+
| recd | id |
+---------------------+------+
| 2012-07-09 | 33 |
| 2012-07-11 | 32 |
| 2012-07-15 | 32 |
+---------------------+------+
3 rows in set (0.00 sec)
I need the records to print the query in the below manner, keeping the missed entries of dates of a month (say July-01 to July-31) and having 0 to the value id corresponding missed dates.
select < a magical query >;
+------------+------+
| recd | id |
+------------+------+
2012-07-01 0
2012-07-02 0
2012-07-03 0
2012-07-04 0
2012-07-05 0
2012-07-06 0
2012-07-07 0
2012-07-08 0
2012-07-09 33
2012-07-10 0
2012-07-11 32
2012-07-12 0
2012-07-13 0
2012-07-14 0
2012-07-15 32
2012-07-16 0
2012-07-17 0
2012-07-18 0
2012-07-19 0
2012-07-20 0
2012-07-21 0
2012-07-22 0
2012-07-23 0
2012-07-24 0
2012-07-25 0
2012-07-26 0
2012-07-27 0
2012-07-28 0
2012-07-29 0
2012-07-30 0
2012-07-31 0

SELECT
generated_date,
COALESCE(yourTable.id, 0) AS id
FROM
(
SELECT
DATE_SUB(CURDATE(), INTERVAL number_days DAY) AS `generated_date` /*<-- Create dates from now back 999 days*/
FROM
(
SELECT (a + 10*b + 100*c) AS number_days FROM
(SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) aa
, (SELECT 0 AS b UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) bb
, (SELECT 0 AS c UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) cc
)sq /*<-- This generates numbers 0 to 999*/
) q
LEFT JOIN yourTable ON DATE(yourTable.datefield) = q.generated_date
WHERE q.generated_date BETWEEN (SELECT MIN(datefield) FROM yourTable) AND (SELECT MAX(datefield) FROM yourTable)
ORDER BY q.generated_date ASC

Found a workaround as it was clinging long time
BASE TABLE
CREATE TABLE `deopt` (
`recd` datetime DEFAULT NULL,
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB;
Seed records to the base table
insert into deopt values ('2012-07-09 23:08:54',22);
insert into deopt values ('2012-07-11 23:08:54',22);
insert into deopt values ('2012-07-11 23:08:54',2222);
insert into deopt values ('2012-07-12 23:08:54',22);
insert into deopt values ('2012-07-14 23:08:54',245);
Create a table for dates of a month
CREATE TABLE seq_dates
(
sdate DATETIME NOT NULL,
);
Create a Stored Procedure to create records for a called month
delimiter //
DROP PROCEDURE IF EXISTS sp_init_dates;
CREATE PROCEDURE sp_init_dates (IN p_fdate DATETIME, IN p_tdate DATETIME)
BEGIN
DECLARE v_thedate DATETIME;
TRUNCATE TABLE seq_dates;
SET v_thedate = p_fdate;
WHILE (v_thedate <= p_tdate) DO
INSERT INTO seq_dates (sdate)
VALUES (v_thedate);
SET v_thedate = DATE_ADD(v_thedate, INTERVAL 1 DAY);
END WHILE;
END;
delimiter ;
Call the procedure for July month with starting and ending values to be seeded to seq_dates table.
call sp_init_dates ('2012-07-01','2012-07-31');
RESULT QUERY - To fetch records of all dates in a month and its corresponding ids keeping 0 inplace of null for ids.
select date(seq_dates.sdate),coalesce (deopt.id,0) from seq_dates LEFT JOIN deopt ON date(deopt.recd)=date(seq_dates.sdate);
+-----------------------+-----------------------+
| date(seq_dates.sdate) | coalesce (deopt.id,0) |
+-----------------------+-----------------------+
| 2012-07-01 | 0 |
| 2012-07-02 | 0 |
| 2012-07-03 | 0 |
| 2012-07-04 | 0 |
| 2012-07-05 | 0 |
| 2012-07-06 | 0 |
| 2012-07-07 | 0 |
| 2012-07-08 | 0 |
| 2012-07-09 | 22 |
| 2012-07-09 | 22 |
| 2012-07-10 | 0 |
| 2012-07-11 | 22 |
| 2012-07-11 | 2222 |
| 2012-07-11 | 22 |
| 2012-07-11 | 2222 |
| 2012-07-12 | 22 |
| 2012-07-13 | 0 |
| 2012-07-14 | 245 |
| 2012-07-15 | 0 |
| 2012-07-16 | 0 |
| 2012-07-17 | 0 |
| 2012-07-18 | 0 |
| 2012-07-19 | 0 |
| 2012-07-20 | 0 |
| 2012-07-21 | 0 |
| 2012-07-22 | 0 |
| 2012-07-23 | 0 |
| 2012-07-24 | 0 |
| 2012-07-25 | 0 |
| 2012-07-26 | 0 |
| 2012-07-27 | 0 |
| 2012-07-28 | 0 |
| 2012-07-29 | 0 |
| 2012-07-30 | 0 |
| 2012-07-31 | 0 |
+-----------------------+-----------------------+
35 rows in set (0.00 sec)

As a two stage query:
SELECT #prev := null;
SELECT datefield, #prev, DATEDIFF(datefield, #prev) AS diff, #prev := datefield
FROM yourtable
HAVING diff > 1
ORDER BY datefield ASC;
that'll detect the dates that have a more-than-1-day difference from the previous row/date

Related

subtract multiple dates within one id

I'm trying to accomplish something in mySQL, which is difficult due to the lack of a "lag" function and because of all the examples I've read regarding date differences, since in all those examples there's always one ID to one date. In here, I am trying to do the date differences within an ID, and when the date difference is over 60, then returning an indicator of 1, else 0.
Not sure what the best way is to go about this. Would it be some combination of using row_number() with the date? The snag is doing this within multiple IDs, as a lot of things I read don't cover that. Any direction would be helpful.
Thanks!
ID | Service Date | Date Difference | Indicator
1 | 1/22/2016 | 0 | 1
1 | 3/26/2016 | 64 | 1
1 | 5/25/2016 | 60 | 0
1 | 9/15/2016 | 113 | 1
2 | 8/1/2016 | 0 | 1
3 | 1/26/2016 | 0 | 1
3 | 3/9/2016 | 43 | 0
3 | 4/30/2016 | 52 | 0
4 | 8/9/2016 | 0 | 1
5 | 11/19/2016 | 0 | 1
6 | 10/14/2016 | 0 | 1
7 | 1/31/2016 | 0 | 1
7 | 8/11/2016 | 193 | 1
You can use variables, but this is tricky. For this to work reliably, all the variables need to be assigned in a single expression:
select t.*, datediff(prev_date, date) as diff,
(case when datediff(prev_date, date) < 60 then 0 else 1 end) as indicator
from (select t.*,
(case when #id = id
then (case when (#prev := #d) = NULL then 'never' -- intentional
when (#d := date) = NULL then 'never' -- intentional
else #prev
end)
when (#d := date) = NULL then 'never' -- intentional
else NULL
end) as prev_date
from t cross join
(select #id := -1, #d := '') params
order by id, date
) t
create view id_and_date as
select id, service_date from your table;
create view id_and_date_and_prior as
select
a.id, a.service_date,
coalesce(
(select max(b.service_date) from id_and_date b
where b.id = a.id and b.service_date < a.service_date),
a.service_date)
as prior_date
from id_and_date a
select a.id, a.service_date, a.prior_date
date_diff(a.service_date, a.prior_date) as diff,
case when date_diff(a.service_date, a.prior_date) > 60
then 1 else 0 end
as indicator
from id_and_date_and_prior a
Posting to simplify and correct the function calls from the answer provided by #tpdi. Please accept/upvote their answer, as this was pretty much copied from it.
Changes:
date_diff to DATEDIFF
removed create view calls in favor of subquery of t
assigned variable to the diff value
indicator of 0 on initial value as 0 diff to 1
replaced when case in favor of IF
SELECT
c.id,
c.service_date,
#diff := DATEDIFF(c.service_date, c.prior_date) AS diff,
IF(#diff = 0 || #diff > 60, 1, 0) AS indicator
FROM (
SELECT
a.id,
a.service_date,
COALESCE(
(SELECT MAX(b.service_date)
FROM t AS b
WHERE b.id = a.id
AND b.service_date < a.service_date),
a.service_date
) AS prior_date
FROM t AS a
) AS c;
Will result in:
| id | service_date | diff | indicator |
| 1 | 2016-01-22 | 0 | 1 |
| 1 | 2016-03-26 | 64 | 1 |
| 1 | 2016-05-25 | 60 | 0 |
| 1 | 2016-09-15 | 113 | 1 |
| 2 | 2016-08-01 | 0 | 1 |
| 3 | 2016-01-26 | 0 | 1 |
| 3 | 2016-03-09 | 43 | 0 |
| 3 | 2016-04-30 | 52 | 0 |
| 4 | 2016-08-09 | 0 | 1 |
| 5 | 2016-11-19 | 0 | 1 |
| 6 | 2016-10-14 | 0 | 1 |
| 7 | 2016-01-31 | 0 | 1 |
| 7 | 2016-08-11 | 193 | 1 |

Showing proper balance ammount

I have a query where I suppose to show debit, credit & balance. I have no balance column in table. I calculate the balance from debit & credit.
I tried to create a balance column where balance will be stored and display it from table. But if I update or delete any row the balance will not proper.
I found the below code on SO. But it works when there is one row for a one date, multiple columns for a date is not working properly. It display the balance with day by day, I want to display balance with row by row.
MySQL
SELECT
m.`id`,
m.`date`,
m.`credit`,
m.`debit`,
SUM(t.`credit`) - SUM(t.`debit`) AS `balance`
FROM `cash_book` m
JOIN (
SELECT
`id`,
`date`,
`credit`,
`debit`
FROM
`cash_book`
) t ON t.`date` <= m.`date`
WHERE `customer_id` = 1
GROUP BY
m.`id`
ORDER BY m.`date` ASC
It's return the result like this:
Date Debit Credit Balance
2016-11-27 0 2000 2000
2016-12-02 0 500000 585000 //same result for date 2016-12-02
2016-12-02 15000 0 585000 //same result for date 2016-12-02
2016-12-02 0 100000 585000 //same result for date 2016-12-02
2016-12-03 1200 0 583800
2016-12-04 3160 0 580540 //same result for date 2016-12-04
2016-12-04 100 0 580540 //same result for date 2016-12-04
2016-12-05 30 0 580510
2016-12-06 0 150 580660
But I want the result like this:
Date Debit Credit Balance
2016-11-27 0 2000 2000
2016-12-02 0 500000 502000
2016-12-02 15000 0 487000
2016-12-02 0 100000 587000
2016-12-03 1200 0 585800
2016-12-04 3160 0 582640
2016-12-04 100 0 582540
2016-12-05 30 0 582510
2016-12-06 0 150 582660
is it this what you looking for ?
SELECT
`id`,
`date`,
`credit`,
`debit`,
#balance := #balance + credit-debit AS balance
FROM `cash_book`
CROSS JOIN ( SELECT #balance := 0) as init
ORDER BY `date` ASC ;
sample
mysql> SELECT * FROM cash_book;
+------+------------+-------+--------+
| id | date | debit | credit |
+------+------------+-------+--------+
| 1 | 2016-11-27 | 0 | 2000 |
| 2 | 2016-12-04 | 3160 | 0 |
| 3 | 2016-12-02 | 15000 | 0 |
| 4 | 2016-12-03 | 1200 | 0 |
| 5 | 2016-12-05 | 30 | 0 |
| 6 | 2016-11-29 | 0 | 10000 |
| 7 | 2016-01-05 | 0 | 0 |
| 8 | 2016-12-01 | 2000 | 0 |
| 9 | 2016-11-29 | 10000 | 0 |
| 10 | 2016-12-02 | 2000 | 100000 |
| 11 | 2016-12-06 | 2000 | 150 |
| 12 | 2016-12-02 | 2000 | 500000 |
+------+------------+-------+--------+
12 rows in set (0,00 sec)
mysql> SELECT
-> `id`,
-> `date`,
-> `credit`,
-> `debit`,
-> #balance := #balance + credit-debit AS balance
-> FROM `cash_book`
-> CROSS JOIN ( SELECT #balance := 0) as init
-> ORDER BY `date` ASC ;
+------+------------+--------+-------+---------+
| id | date | credit | debit | balance |
+------+------------+--------+-------+---------+
| 7 | 2016-01-05 | 0 | 0 | 0 |
| 1 | 2016-11-27 | 2000 | 0 | 2000 |
| 6 | 2016-11-29 | 10000 | 0 | 12000 |
| 9 | 2016-11-29 | 0 | 10000 | 2000 |
| 8 | 2016-12-01 | 0 | 2000 | 0 |
| 3 | 2016-12-02 | 0 | 15000 | -15000 |
| 10 | 2016-12-02 | 100000 | 2000 | 83000 |
| 12 | 2016-12-02 | 500000 | 2000 | 581000 |
| 4 | 2016-12-03 | 0 | 1200 | 579800 |
| 2 | 2016-12-04 | 0 | 3160 | 576640 |
| 5 | 2016-12-05 | 0 | 30 | 576610 |
| 11 | 2016-12-06 | 150 | 2000 | 574760 |
+------+------------+--------+-------+---------+
12 rows in set (0,00 sec)
mysql>
You can use a subquery in select part like this
SELECT
m.`id`,
m.`date`,
m.`credit`,
m.`debit`,
(select sum(n.`credit`) - sum(n.`debit`)
from `cash_book` n
where n.`id` = m.`id`
and n.`date` <= m.`date`) balance
FROM `cash_book` m
WHERE `customer_id` = 1
ORDER BY m.`date` ASC
try this query
select s.Date,s.Debit,s.credit,ABS(#b := #b + s.debit - s.credit) as balance from (select #b:= 0.0) as dummy cross join cash_book as s order by ID;
In your Query you have used JOIN, That will used to join two or more tables. Here you are using only one table. So no need of JOIN inside the query.
You can use this following simple query,
SELECT
m.id,
m.date,
m.credit,
m.debit,
(SELECT SUM(credit)-SUM(debit)
FROM`cash_book` A
WHERE A.date<=m.Date)
FROM `cash_book` m WHERE m.customer_id = 1 ORDER BY m.Date ASC

Delete duplicate rows having count greater than 1 in mysql

Need some help on a MySQL query to delete the rows where the userID count is greater than one and has an acl value=4.
What I've tried as the select query:
DELETE FROM `users_acl`
WHERE userID IN(
SELECT * FROM `users_acl`
WHERE `acl` BETWEEN 2 AND 4
GROUP BY userID
HAVING COUNT(userID) > 1
);
But can't find a proper way to delete the rows in the same query.
Table structure
+--------+-----+---------+
| userID | acl | deleted |
+--------+-----+---------+
| 27 | 2 | 0 |
| 28 | 2 | 0 |
| 31 | 2 | 0 |
| 42 | 2 | 0 |
| 42 | 4 | 0 |
| 45 | 1 | 0 |
| 51 | 1 | 0 |
| 54 | 1 | 0 |
| 63 | 2 | 0 |
| 63 | 4 | 0 |
| 64 | 1 | 0 |
| 69 | 2 | 0 |
| 73 | 2 | 0 |
| 73 | 4 | 0 |
| 76 | 1 | 0 |
| 77 | 2 | 0 |
| 77 | 4 | 0 |
+--------+-----+---------+
In MySQL you can't select from a table you are deleting from at the same time. But with a temp table you can overcome this problem
DELETE FROM `users_acl`
WHERE userID IN
(
SELECT * FROM
(
SELECT userID
FROM `users_acl`
GROUP BY userID
HAVING COUNT(userID) > 1
AND SUM(`acl` = 4) > 0
) tmp
);
or use a joininstead
DELETE u
FROM `users_acl` u
JOIN
(
SELECT userID
FROM `users_acl`
GROUP BY userID
HAVING COUNT(userID) > 1
AND SUM(`acl` = 4) > 0
) tmp on tmp.userID = u.userID
I think the accepted answer here would actually delete all the duplicates, rather than preserving one unique value.
In case anyone is like me and looking to de-duplicate (rather than delete all duplicates), here's a slightly modified solution:
DELETE u
FROM `users_acl` u
JOIN
(
SELECT COUNT(*), MIN(userID) as min_id, MAX(acl) as value
FROM `users_acl`
WHERE 1
GROUP BY name,value,timestamp,ip HAVING COUNT(*) > 1
) tmp on u.id > tmp.min_id and u.value = tmp.value

Count the number of users between certain dates

I have a table of users with when they signed_up and when they were last_seen:
+--------+-----------+-----------+
| userid | signed_up | last_seen |
+--------+-----------+-----------+
| 1 | 1/1/14 | 1/3/14 |
| 2 | 1/1/14 | 1/5/14 |
| 3 | 1/3/14 | 1/5/14 |
| 4 | 1/6/14 | 1/7/14 |
+--------+-----------+-----------+
Lets assume each user comes to the site on everyday between signed_up and last_seen. I'd like to count how many total users there are on each day. The result I'm looking for is below:
+--------+----------------+
| date | count_of_users |
+--------+----------------+
| 1/1/14 | 2 |
| 1/2/14 | 2 |
| 1/3/14 | 3 |
| 1/4/14 | 2 |
| 1/5/14 | 2 |
| 1/6/14 | 1 |
| 1/7/14 | 1 |
+--------+----------------+
And just for clarification, here's how the numbers were calculated (I don't need this table, this is just an illustration)
+--------+----------------+-------+-------+-------+-------+
| date | count_of_users | user1 | user2 | user3 | user4 |
+--------+----------------+-------+-------+-------+-------+
| 1/1/14 | 2 | 1 | 1 | | |
| 1/2/14 | 2 | 1 | 1 | | |
| 1/3/14 | 3 | 1 | 1 | 1 | |
| 1/4/14 | 2 | | 1 | 1 | |
| 1/5/14 | 2 | | 1 | 1 | |
| 1/6/14 | 1 | | | | 1 |
| 1/7/14 | 1 | | | | 1 |
+--------+----------------+-------+-------+-------+-------+
Not sure if this is getting beyond what should be done in MySql... Thanks for the help!
Create a date range table:
mysql> CREATE TABLE IF NOT EXISTS calendar (date DATE NOT NULL PRIMARY KEY);
mysql> INSERT INTO calendar (SELECT calendar.date
FROM (
SELECT (SELECT MIN(signed_up) FROM users)
+ INTERVAL (a.val + (10 * b.val)) DAY
as date
FROM (select 0 as val union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9)
as a
CROSS JOIN (select 0 as val union all select 1 union all select 2
union all select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all select 9)
as b
) calendar
WHERE calendar.date BETWEEN
(SELECT MIN(signed_up) FROM users) AND
(SELECT MAX(last_seen) FROM users));
mysql> select * from calendar;
+------------+
| date |
+------------+
| 2014-01-01 |
| 2014-01-02 |
| 2014-01-03 |
| 2014-01-04 |
| 2014-01-05 |
| 2014-01-06 |
| 2014-01-07 |
+------------+
7 rows in set (0.00 sec)
mysql> select * from users;
+--------+------------+------------+
| userid | signed_up | last_seen |
+--------+------------+------------+
| 1 | 2014-01-01 | 2014-01-03 |
| 2 | 2014-01-01 | 2014-01-05 |
| 3 | 2014-01-03 | 2014-01-05 |
| 4 | 2014-01-06 | 2014-01-07 |
+--------+------------+------------+
4 rows in set (0.00 sec)
Once you have the date range table, the query you are looking for can be expressed as a JOIN / GROUP BY operation:
mysql> SELECT c.date, count(c.date)
FROM calendar c JOIN users u WHERE c.date BETWEEN u.signed_up AND u.last_seen
GROUP BY c.date;
+------------+---------------+
| date | count(c.date) |
+------------+---------------+
| 2014-01-01 | 2 |
| 2014-01-02 | 2 |
| 2014-01-03 | 3 |
| 2014-01-04 | 2 |
| 2014-01-05 | 2 |
| 2014-01-06 | 1 |
| 2014-01-07 | 1 |
+------------+---------------+
7 rows in set (0.00 sec)
If you are doing this kind of query often, it would pay to generate (and keep) the calendar table. However, if you don't want to create the calendar table, the result can be generated on the fly using a single query:
mysql> SELECT c.date, COUNT(c.date)
FROM (SELECT calendar.date
FROM (
SELECT (SELECT MIN(signed_up) FROM users)
+ INTERVAL (a.val + (10 * b.val)) DAY
as date
FROM (select 0 as val union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9)
as a
CROSS JOIN (select 0 as val union all select 1 union all select 2
union all select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all select 9)
as b
) calendar
WHERE calendar.date BETWEEN
(SELECT MIN(signed_up) FROM users) AND
(SELECT MAX(last_seen) FROM users)) c
JOIN users u WHERE c.date BETWEEN u.signed_up AND u.last_seen
GROUP BY c.date
which yields
+------------+---------------+
| date | COUNT(c.date) |
+------------+---------------+
| 2014-01-01 | 2 |
| 2014-01-02 | 2 |
| 2014-01-03 | 3 |
| 2014-01-04 | 2 |
| 2014-01-05 | 2 |
| 2014-01-06 | 1 |
| 2014-01-07 | 1 |
+------------+---------------+
7 rows in set (0.00 sec)
Note that, depending on how large a calendar you need, you may need to add more CROSS JOINS in the INSERT INTO calendar statement. See the link for an example.

Query changes in MySQL records

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?
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.
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
;