MySQL select rows based on previous and next rows - mysql

Suppose I have a table looking like:
mytable
category | begintime
---------|----------------------
cat1 | 2016-09-25 15:00:00
cat2 | 2016-09-25 16:00:00
cat1 | 2016-09-25 17:30:00
cat3 | 2016-09-25 19:00:00
cat1 | 2016-09-25 20:00:00
: :
Note that it doesn't have an ID-number, the begintime column is my primary key.
In the end, I would like to select all rows that are surrounded by a certain category, that is, to select all rows such that the category from the previous row is #catBefore and the category from the next row is #catAfter.
For example, what I would like is something like:
SELECT * FROM mytable WHERE previousRow.category = 'cat1' AND nextRow.category = 'cat1'
resulting in
SELECT * FROM ...
category | begintime
---------|----------------------
cat2 | 2016-09-25 16:00:00
cat3 | 2016-09-25 19:00:00
: :
The previousRow and nextRow in this, don't seem to be definable.
Idea
I have tried some things, but nothing has worked out yet. One of my ideas was to first select the previous and next category as new columns, so something like:
SELECT mytable.*,
previousRow.category AS prevCat,
nextRow.category AS nextCat
FROM mytable, [stuff-I-don't-know]
resulting in
SELECT mytable.* ...
category | begintime | prevCat | nextCat
---------|----------------------|---------|---------
cat1 | 2016-09-25 15:00:00 | null | cat2
cat2 | 2016-09-25 16:00:00 | cat1 | cat1
cat1 | 2016-09-25 17:30:00 | cat2 | cat3
cat3 | 2016-09-25 19:00:00 | cat1 | cat1
cat1 | 2016-09-25 20:00:00 | cat3 | ...
: :
and then filtering using a WHERE clause.
Is this idea possible, or could it be done in some other way?

One method uses correlated subqueries. This should be okay performance-wise, if you do indeed have primary key declarations:
select t.*
from (select t.*,
(select t2.category
from mytable t2
where t2.begintime < t.begintime
order by begintime desc
limit 1
) as prev_category,
(select t2.category
from mytable t2
where t2.begintime > t.begintime
order by begintime asc
limit 1
) as next_category
from mytable t
) t
where prev_category = #cat1 and next_category = #cat2;
EDIT:
You can do this with variables:
select t.*
from (select t.*,
(#pn := (case when (#pcy := #pn) = NULL then -1 -- never gets here
when (#pn := category) = NULL then -1 -- never gets here
else #pcy
end)
) as next_category
from (select t.*,
(#pc := (case when (#pcx := #pc) = NULL then -1 -- never gets here
when (#pc := category) = NULL then -1 -- never gets here
else #pcx
end)
) as prev_category
from t cross join
(select #pc := '') params
order by t.begintime
) t cross join
(select #pn := '') params
order by t.begintime desc
) t
where prev_category = #cat1 and next_category = #cat2;

I did this one... it seems to work, but it seems too slow actually... =\ I'll try to improve it. Please, check if it works for you:
SELECT
t1.category,
t1.begintime
FROM myTable t1 -- current
INNER JOIN myTable t2 ON 1=1 -- prev
AND t2.begintime < t1.begintime
INNER JOIN myTable t3 ON 1=1 -- next
AND t3.begintime > t1.begintime
LEFT JOIN myTable t4 ON 1=1 -- between current and prev
AND t4.begintime < t1.begintime
AND t4.begintime > t2.begintime
LEFT JOIN myTable t5 ON 1=1 -- between current and next
AND t5.begintime > t1.begintime
AND t5.begintime < t3.begintime
WHERE 1=1
AND t2.category = 'cat1' -- prev cat
AND t3.category = 'cat1' -- next cat
AND t4.begintime IS NULL -- nothing between current and prev
AND t5.begintime IS NULL -- nothing between current and next
;

Related

Sum of repeating values in successive rows

I have a table like this:
id col1 col2 col3
10 1 3
9 1 2 3
8 2 3
7 2 3
6 1 2
5 3
Each column has one value only or null. Eg. Col1 has 1 or empty. Col2 has 2 or empty.
I'd like to get the sum of repeating values only between two successive rows.
so the result would look like this:
I need to get the sum of total repeating values in each row.
id col1 col2 col3 Count
10 1 3 2 (shows the repeating values between id10 & id9 rows)
9 1 2 3 2 (shows the repeating values between id9 & id8 rows)
8 2 3 1
7 2 1
6 1 2 0
5 3
I googled and tried some queries I found on the web but couldn't get the right result. Thanks in advance for your help.
To further clarify, for example:
id10 row has (1,,3) and id9 row has (1,2,3). so there is two values repeating. so count is 2.
If the ids are consecutive and there are no gaps, you can do it with a self join:
select
t.*,
coalesce((t.col1 = tt.col1), 0) +
coalesce((t.col2 = tt.col2), 0) +
coalesce((t.col3 = tt.col3), 0) count
from tablename t left join tablename tt
on tt.id = t.id - 1
See the demo.
Results:
| id | col1 | col2 | col3 | count |
| --- | ---- | ---- | ---- | ----- |
| 10 | 1 | | 3 | 2 |
| 9 | 1 | 2 | 3 | 2 |
| 8 | | 2 | 3 | 1 |
| 7 | | 2 | | 1 |
| 6 | 1 | 2 | | 0 |
| 5 | | | 3 | 0 |
And if there are gaps...
SELECT a.id
, a.col1
, a.col2
, a.col3
, COALESCE(a.col1 = b.col1,0) + COALESCE(a.col2 = b.col2,0) + COALESCE(a.col3 = b.col3,0) n
FROM
( SELECT x.*
, MIN(y.id) y_id
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
) a
LEFT
JOIN my_table b
ON b.id = a.y_id;
Were you to restructure your schema, then you could do something like this instead...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,val)
);
INSERT INTO my_table VALUES
(10,1),
(10,3),
( 9,1),
( 9,2),
( 9,3),
( 8,2),
( 8,3),
( 7,2),
( 7,3),
( 6,1),
( 6,2),
( 5,3);
SELECT a.id
, COUNT(b.id) total
FROM
( SELECT x.*
, MIN(y.id) next
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
, x.val
) a
LEFT
JOIN my_table b
ON b.id = a.next
AND b.val = a.val
GROUP
BY a.id;
+----+-------+
| id | total |
+----+-------+
| 5 | 0 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
+----+-------+
You can use :
select t1_ID, t1_col1,t1_col2,t1_col3, count
from
(
select t1.id as t1_ID, t1.col1 as t1_col1,t1.col2 as t1_col2,t1.col3 as t1_col3, t2.*,
case when t1.col1 = t2.col1 then 1 else 0 end +
case when t1.col2 = t2.col2 then 1 else 0 end +
case when t1.col3 = t2.col3 then 1 else 0 end as count
from tab t1
left join tab t2
on t1.id = t2.id + 1
order by t1.id
) t3
order by t1_ID desc;
Demo
If there are gaps between id values for the next row, you could have user defined variables to explicitly assign values to rows in their natural ordering in the table. Rest logic remains the same as already answered. You would do an inner join between current row number and next row number to get the col1,col2 and col3 values and use coalesce for computation of count.
select derived_1.*,
coalesce((derived_1.col1 = derived_2.col1), 0) +
coalesce((derived_1.col2 = derived_2.col2), 0) +
coalesce((derived_1.col3 = derived_2.col3), 0) count
from (
select #row := #row + 1 as row_number,t1.*
from tablename t1,(select #row := 0) d1
) derived_1
left join (
select *
from (
select #row2 := #row2 + 1 as row_number,t2.*
from tablename t2,(select #row2 := 0) d2
) d3
) derived_2
on derived_1.row_number + 1 = derived_2.row_number;
Demo: https://www.db-fiddle.com/f/wAzb67zSEfbZKg5RywQvC8/1

How to Select First Date, Previous Date, Latest Date where first date is higher than a reference date

I want to SELECT the Latest Date, the Second Latest Date and the First Date FROM a table1 where the First Date is higher than a reference Date found in another table2. And that reference Date should also be the latest from that table2. I have a solution, supposed to be. But the problem is, the solutions will not return an output if there is ONLY 1 record from table1. Example of the tables:
table1
Reg ID | DateOfAI | byTechnician
2GP001 | 2015-01-13 | 31
2GP001 | 2015-02-18 | 31
2GP001 | 2017-11-10 | 45
2GP001 | 2017-11-30 | 32
2GP044 | 2017-11-30 | 28
2GP001 | 2017-12-23 | 32
table2
Reg ID | DateOfCalving | DryOffDate
2GP001 | 2016-01-14 |
2GP070 | 2016-01-14 |
2GP065 | 2017-04-08 |
2GP001 | 2017-04-12 |
my expected output would be:
Reg ID | LatestDateOfCalving | 1stDateOfAI | PreviousAIDate | LastestAIDate
2GP001 | 2017-04-12 | 2017-11-10 | 2017-11-30 | 2017-12-23
I have searched everywhere from the moon and back... still no luck. these are the queries that i have used
the Fisrt:
SELECT b.actualDam,COUNT(x.actualDam) AS ilanba, max(b.breedDate) AS huli, max(x.breedDate) AS nex,MIN(x.breedDate) AS una,IFNULL(c.calvingDate,NULL) AS nganak,r.*,h.herdID,a.animalID,a.regID, IFNULL(a.dateOfBirth,NULL) AS buho
FROM x_animal_breeding_rec b
LEFT JOIN x_animal_calving_rec c ON b.recID=c.brecID
LEFT JOIN x_herd_animal_rel r ON b.actualDam=r.animal
LEFT JOIN x_herd h ON r.herd=h.herdID
LEFT JOIN x_animal_main_info a ON b.actualDam=a.animalID
JOIN x_animal_breeding_rec x ON b.actualDam = x.actualDam AND x.breedDate < b.breedDate
WHERE h.herdID = ? AND x.mateType = ? AND x.recFlag = ? GROUP BY b.actualDam
and the Second one that I've tried is this code:
SELECT b.recID
, b.actualDam
, b.breedDate
, min(b.breedDate) AS una
, max(b.breedDate) AS huli
, COUNT(b.actualDam) AS sundot
, b.mateType
, b.recFlag
, a.animalID
, a.regID
, h.*
FROM
( SELECT c.recID, c.actualDam
, c.breedDate
, c.mateType
, c.recFlag
, CASE WHEN #prev=c.recID THEN #i:=#i+1 ELSE #i:=1 END i
, #prev:=c.recID prev
FROM x_animal_breeding_rec c
, ( SELECT #prev:=null,#i:=0 ) vars
ORDER BY c.recID,c.breedDate DESC
) b
LEFT JOIN x_animal_main_info a ON b.actualDam=a.animalID
LEFT JOIN x_herd_animal_rel h ON b.actualDam=h.animal
WHERE i <= 2 GROUP BY b.actualDam HAVING h.herd = ? AND b.mateType = ? AND b.recFlag = ? ORDER BY b.breedDate DESC
Another problem here is the first solution returns a WRONG COUNT. the second solution returns a CORRECT COUNT, however, wrong Dates were returned. I hope you could give me an idea. Thanx in Advance.
The following query answers your question:
SELECT
RegID,
LatestDateOfCalving,
MIN(DateOfAI) AS 1stDateOfAI,
REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(DateOfAI ORDER BY DateOfAI DESC), ',', 2), CONCAT(MAX(DateOfAI), ','), '') AS PreviousAIDate,
MAX(DateOfAI) AS LatestAIDate
FROM (
SELECT
t1.RegID,
LatestDateOfCalving,
DateOfAI,
IF(DateOfAI >= LatestDateOfCalving, 1, 0) AS dates
FROM table1 AS t1
INNER JOIN (
SELECT
RegID,
MAX(DateOfCalving) AS LatestDateOfCalving
FROM table2 GROUP BY RegID
) AS tt2 ON t1.RegID = tt2.RegID) AS x
WHERE dates = 1
GROUP BY RegID
HAVING COUNT(dates) >= 3;
Output:
+--------+---------------------+-------------+----------------+--------------+
| RegID | LatestDateOfCalving | 1stDateOfAI | PreviousAIDate | LatestAIDate |
+--------+---------------------+-------------+----------------+--------------+
| 2GP001 | 2017-04-12 | 2017-11-10 | 2017-11-30 | 2017-12-23 |
+--------+---------------------+-------------+----------------+--------------+
DEMO
In a subquery we select RegID and LatestDateOfCalving from table2 in order to have a reference date. Then join it to table1 and flag the record whether DateOfAI is greater or equal to LatestDateOfCalving (IF(DateOfAI >= LatestDateOfCalving, 1, 0)). We use this subquery in the outer query (SELECT RegID, LatestDateOfCalving, MIN(DateOfAI) AS 1stDateOfAI, MAX(DateOfAI) AS LatestAIDate, ...) and select only those records where the DateOfAI are at or after LatestDateOfCalving (WHERE dates = 1, where 1 is the flag where the condition was true) and have at least 3 records (HAVING COUNT(dates) >= 3). In the outer query I use the REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(...))) structure in order to extract the previousAIDate from a comma (,) separated list of dates.

MySQL group by with start and end times

I have a table called map_item_group in MySQL that looks like this example:
item_serial | group_code | start_date | end_date
===================================================
item1 | group1 | 2015-01-01 | 2016-01-01
item1 | group2 | 2016-02-01 | 2016-03-15
item2 | group1 | 2015-06-01 | 2016-06-30
item1 | group2 | 2016-05-18 | 2016-06-30
I want to create a MySQL view called group_info that looks like this:
group_code | start_date | end_date | items_string
=======================================================
group1 | 2015-01-01 | 2015-06-01 | item1
group1 | 2015-06-01 | 2016-01-01 | item1,item2
group1 | 2016-01-01 | 2016-06-30 | item2
group2 | 2016-02-01 | 2016-03-15 | item1
group2 | 2016-05-18 | 2016-06-30 | item1
In other words, I want one row for each group showing the items in that group over each time span.
Simply grouping by group_code, start_date and end_date (i.e. SELECT group_code, start_date, end_date, GROUP_CONCAT(item_serial) FROM map_item_group GROUP BY group_code, start_date, end_date) does not give the desired output.
I can imagine ways to do this with subqueries, but subqueries aren't allowed in MySQL views. I can create other views in place of subqueries as a workaround, but I'd rather avoid adding a bunch of extra views to my schema. What's the cleanest way to do this?
First I create a list of all dates (start + end) by group_code using UNION I called T1 but should choose a different name
Then use variables to asign a row_number to each date. Subquery T1 and T2
Then have to duplicate the code to join the result to itself and create ranges. Subquery R
You could simplify it making that a separated view.
Now I have the ranges, join back to the original table to see if the item belong to that range.
OUPUT
SQL Demo
SELECT R.`group_code`, R.`start_date`, R.`end_date`, GROUP_CONCAT(T.item_serial SEPARATOR ', ') items
FROM (
SELECT T1.`group_code`, T1.range_date as start_date, T2.range_date as end_date
FROM (
SELECT `group_code`, range_date,
#rn := IF( #grpCode = `group_code`, #rn + 1 , IF(#grpCode := `group_code`, 1, 1)) as rn
FROM (
SELECT `group_code`, `start_date` as range_date
FROM Table1
UNION
SELECT `group_code`, `end_date` as range_date
FROM Table1
ORDER BY 1, 2
) as T1,
(SELECT #rn := 0, #grpCode := '') r
) T1
JOIN (
SELECT `group_code`, range_date,
#rn := IF( #grpCode = `group_code`, #rn + 1 , IF(#grpCode := `group_code`, 1, 1)) as rn
FROM (
SELECT `group_code`, `start_date` as range_date
FROM Table1
UNION
SELECT `group_code`, `end_date` as range_date
FROM Table1
ORDER BY 1, 2
) as T1,
(SELECT #rn := 0, #grpCode := '') r
) T2
ON T1.rn = T2.rn -1
AND T1.group_code = T2.group_code
) R
JOIN Table1 T
ON R.start_date < T.end_date
AND R.end_date > T.start_date
AND R.group_code = T.group_code
GROUP BY R.`group_code`, R.`start_date`, R.`end_date`
ORDER BY 1,2, 4

Count total flow inventory in and out php mysql

For the record I'm not using different table, I calculate using the same table but I added more column called stock.
I have a record table:
Table A
=======================================================
**id** | **code** | **status** | **total** | **date** |
1 | B01 | IN | 500 |2013-01-15|
2 | B01 | OUT | 100 |2013-01-20|
3 | B01 | OUT | 200 |2013-02-01|
4 | B01 | IN | 300 |2013-02-05|
The output that I want using select mysql is like this:
Table A
==================================================================
**id** | **code** | **status** | **total** | **date** | **stock**
1 | B01 | IN | 500 |2013-01-15| 500
2 | B01 | OUT | 100 |2013-01-20| 400
3 | B01 | IN | 200 |2013-02-01| 600
4 | B01 | OUT | 300 |2013-02-05| 300
As you can see I added the stock column in table A.. so my question is how can I achieved that using mysql ?
UPDATE
I've been saved by #Juergen D answer so I'm using his method:
select t.*, #stock := #stock + case when status = 'IN'
then total
else -total
end as stock
from your_table t
cross join (select #stock := 0) s
order by t.id
in case you have a same problem as me :)
select t.*, #stock := #stock + case when status = 'IN'
then total
else -total
end as stock
from your_table t
cross join (select #stock := 0) s
order by t.id
Use a CASE statement inside of SUM to determine whether to add or subtract.
SELECT
t1.id,
t1.code,
t1.status,
t1.date,
SUM(
CASE
WHEN t2.status = 'IN'
THEN t2.total
WHEN t2.status = 'OUT'
THEN (t2.total * -1)
END
) stock
FROM table t1
JOIN table t2
ON t2.date <= t1.date
AND t2.code = t1.code
GROUP BY t1.id

Calculate delta(difference of current and previous row) mysql group by specific column

I have a table like : session is the name of the table for example
With columns: Id, sessionDate, user_id
What i need:
Delta should be a new calculated column
Id | sessionDate | user_id | Delta in days
------------------------------------------------------
1 | 2011-02-20 00:00:00 | 2 | NULL
2 | 2011-03-21 00:00:00 | 2 | NULL
3 | 2011-04-22 00:00:00 | 2 | NULL
4 | 2011-02-20 00:00:00 | 4 | NULL
5 | 2011-03-21 00:00:00 | 4 | NULL
6 | 2011-04-22 00:00:00 | 4 | NULL
Delta is the Difference between the timestamps
What i want is a result for Delta Timestamp (in Days) for the the previous row and the current row grouped by the user_id.
this should be the result:
Id | sessionDate | user_id | Delta in Days
------------------------------------------------------
1 | 2011-02-20 00:00:00 | 2 | NULL
2 | 2011-02-21 00:00:00 | 2 | 1
3 | 2011-02-22 00:00:00 | 2 | 1
4 | 2011-02-20 00:00:00 | 4 | NULL
5 | 2011-02-23 00:00:00 | 4 | 3
6 | 2011-02-25 00:00:00 | 4 | 2
I already have a solution for a specific user_id:
SELECT user_id, sessionDate,
abs(DATEDIFF((SELECT MAX(sessionDate) FROM session WHERE sessionDate < t.sessionDate and user_id = 1), sessionDate)) as Delta_in_days
FROM session AS t
WHERE t.user_id = 1 order by sessionDate asc
But for more user_ids i didnĀ“t find any solution
Hope somebody can help me.
Try this:
drop table a;
create table a( id integer not null primary key, d datetime, user_id integer );
insert into a values (1,now() + interval 0 day, 1 );
insert into a values (2,now() + interval 1 day, 1 );
insert into a values (3,now() + interval 2 day, 1 );
insert into a values (4,now() + interval 0 day, 2 );
insert into a values (5,now() + interval 1 day, 2 );
insert into a values (6,now() + interval 2 day, 2 );
select t1.user_id, t1.d, t2.d, datediff(t2.d,t1.d)
from a t1, a t2
where t1.user_id=t2.user_id
and t2.d = (select min(d) from a t3 where t1.user_id=t3.user_id and t3.d > t1.d)
Which means: join your table to itself on user_ids and adjacent datetime entries and compute the difference.
If id is really sequential (as in your sample data), the following should be quite efficient:
select t.id, t.sessionDate, t.user_id, datediff(t2.sessiondate, t.sessiondate)
from table t left outer join
table tprev
on t.user_id = tprev.user_id and
t.id = tprev.id + 1;
There is also another efficient method using variables. Something like this should work:
select t.id, t.sessionDate, t.user_id, datediff(prevsessiondate, sessiondate)
from (select t.*,
if(#user_id = user_id, #prev, NULL) as prevsessiondate,
#prev := sessiondate,
#user_id := user_id
from table t cross join
(select #user_id := 0, #prev := 0) vars
order by user_id, id
) t;
(There is a small issue with these queries where the variables in the select clause may not be evaluated in the order we expect them to. This is possible to fix, but it complicates the query and this will usually work.)
Although you have choosen an answer here is another way of achieving it
SELECT
t1.Id,
t1.sessionDate,
t1.user_id,
TIMESTAMPDIFF(DAY,t2.sessionDate,t1.sessionDate) as delta
from myTable t1
left join myTable t2
on t1.user_id = t2.user_id
AND t2.Id = (
select max(Id) from myTable t3
where t1.Id > t3.Id AND t1.user_id = t3.user_id
);
DEMO