How to calculate date difference for group data - mysql

I would like to count the date-time difference, when I group the data.
Lets have a look on table content example :
id | session_id | created
1 | 101 | 1/10/2010 9:30:10
2 | 102 | 1/10/2010 9:31:10
3 | 101 | 1/10/2010 9:32:10
4 | 103 | 1/10/2010 9:35:10
5 | 102 | 1/10/2010 9:38:10
6 | 103 | 1/10/2010 9:39:10
Output should be as follow :
session_id | time_count
101 | 2 (minutes)
102 | 7 (minutes)
103 | 4 (minutes)
So here i want to calculate time difference of 'created' field for data which is group by session_id.
Any help would be great. Thanks in advance :)
My Situation :
I have a table with following fields :
id, session_id, platform, event, created
there are 2 event (Start,End)
Now if there are records of Start and End for same session_id then i want the time takes to complete this session.
But if i have 2 Start session, but only 1 End session then i do not want the time difference for 2nd Session because it did not end

SELECT session_id,
DATEDIFF(MINUTE, MAX(created), MIN(created)) AS diff
FROM table
GROUP BY session_id

Try this:
Table Schema:
CREATE TABLE A(id INT, session_id INT,Event VARCHAR(20), created DATETIME);
INSERT INTO A VALUES(1, 101,'Start','2010/10/01 9:30:10')
,(2, 102,'Start' , '2010/10/01 9:31:10')
,(3, 101,'End' , '2010/10/01 9:32:10')
,(4, 103,'Start' , '2010/10/01 9:35:10')
,(5, 102,'End' , '2010/10/01 9:38:10')
,(6, 103,'End' , '2010/10/01 9:39:10')
,(7, 101,'Start' , '2010/10/01 9:39:10')
,(8, 102,'Start' , '2010/10/01 9:39:10')
,(9, 101,'End' , '2010/10/01 9:55:10');
Query:
SELECT D.session_id
,TIMESTAMPDIFF(MINUTE,MIN(D.created), MAX(D.created)) time_count
FROM(
SELECT a.id,a.session_id,a.created
,ROUND(count(*)/2,0) as RN
,count(*) as row_number1
FROM a AS a
JOIN a AS b ON a.session_id = b.session_id AND a.id >= b.id
GROUP BY a.id,a.session_id,a.created
ORDER BY 2,3
)D
GROUP BY D.session_id,D.RN
HAVING COUNT(1)>1
Output:
session_id time_count
101 2
102 7
103 4
101 16
Fiddle:
Check the Output in the #SQL Fiddle

You can try it's sintax :
WITH cte AS (
SELECT
session_id,
DATEDIFF(minute, created, LAG(created, 1)) AS diff,
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created) rn
FROM yourTable
)
SELECT
session_id,
diff AS time_count
FROM cte
WHERE rn % 2 = 0
;

Related

Looking for latest results by using group_by

i'm trying to create an overview page for my customers and would like to show when the customer (within the last year) used a type of service (e.g. window cleaning or floor cleaning).
I tried it this way:
SELECT start, project_id, category_id, MAX(start)
FROM `time_entries`
WHERE project_id = '$project_id' AND start > '$oneyearago'
GROUP BY category_id
project_id = kind of a unique customer id
category_id = 1 for window cleaning, 2 for floor cleaning and so on
start = DATETIME when the last service started
I would like to show the LAST services, but it only shows the oldest services. What am i Doing wrong? does anyone here has a idea for me?
Thank you very much in advance.
EDIT: This is the database example
id project_id category_id start
---|------------|-----------|-------------------
1 | 1 | 1 | 2019-11-11 09:51:07
2 | 1 | 2 | 2019-11-12 09:51:07
5 | 1 | 3 | 2019-11-13 09:51:07
3 | 1 | 1 | 2020-11-11 09:51:07
4 | 1 | 2 | 2020-11-12 09:51:07
5 | 1 | 3 | 2020-11-13 09:51:07
6 | 1 | 1 | 2021-11-11 09:51:07
7 | 1 | 2 | 2021-11-12 09:51:07
I would like to see
category 1: last appointment was 2021-11-11
category 2: last appointment was 2021-11-12
category 3: last appointment was 2020-11-13
I was asking for scripts like this
CREATE TABLE time_entries (id int, project_id int, category_id int, start datetime);
INSERT INTO time_entries VALUES(1 , 1 , 1 , '2019-11-11 09:51:07'),(2 , 1 , 2 , '2019-11-12 09:51:07'), (5 , 1 , 3 , '2019-11-13 09:51:07'), (3 , 1 , 1 , '2020-11-11 09:51:07'), (4 , 1 , 2 , '2020-11-12 09:51:07'), (5 , 1 , 3 , '2020-11-13 09:51:07'), (6 , 1 , 1 , '2021-11-11 09:51:07'), (7 , 1 , 2 , '2021-11-12 09:51:07');
Now, solution to your problem. you can add date check in where clause.
SELECT T1.id, T1.start, T1.project_id, T1.category_id, T2.start
FROM time_entries T1
INNER JOIN(
SELECT category_id, MAX(start)start
FROM time_entries
WHERE project_id=1
GROUP BY category_id
)T2 ON T1.category_id=T2.category_id AND T1.start = T2.Start
ORDER BY T1.category_id
Please try below query
SELECT project_id, category_id, MAX(start) as last_datetime FROM time_entries WHERE project_id = '$project_id' GROUP BY category_id

MySQL - Count Rows between two values in a Column repeatedly

I have a table like so
id | status | data | date
----|---------|--------|-------
1 | START | a4c | Jan 1
2 | WORKING | 2w3 | Dec 29
3 | WORKING | 2d3 | Dec 29
4 | WORKING | 3ew | Dec 26
5 | WORKING | 5r5 | Dec 23
6 | START | 2q3 | Dec 22
7 | WORKING | 32w | Dec 20
8 | WORKING | 9k5 | Dec 10
and so on...
What I am trying to do, is to get the number of 'WORKING' rows between two 'START' i.e.
id | status | count | date
----|---------|--------|-------
1 | START | 4 | Jan 1
6 | START | 2 | Dec 22
and so on ...
I am using MySql 5.7.28.
Highly appreciate any help/suggestion!
date is unusable in the example, try using id as an ordering column instead
select id, status,
(select count(*)
from mytable t2
where t2.id > t.id and t2.status='WORKING'
and not exists (select 1
from mytable t3
where t3.id > t.id and t3.id < t2.id and status='START')
) count,
date
from mytable t
where status='START';
Fiddle
Assuming id is safe then you can do this by finding the next id for each block (and assigning some dummy values) then grouping by next id
drop table if exists t;
create table t
(id int,status varchar(20), data varchar(3),date varchar(10));
insert into t values
( 1 , 'START' , 'a4c' , 'Jan 1'),
( 2 , 'WORKING' , '2w3' , 'Dec 29'),
( 3 , 'WORKING' , '2d3' , 'Dec 29'),
( 4 , 'WORKING' , '3ew' , 'Dec 26'),
( 5 , 'WORKING' , '5r5' , 'Dec 23'),
( 6 , 'START' , '2q3' , 'Dec 22'),
( 7 , 'WORKING' , '32w' , 'Dec 20'),
( 8 , 'WORKING' , '9k5' , 'Dec 10');
SELECT MIN(ID) ID,
'START' STATUS,
SUM(CASE WHEN STATUS <> 'START' THEN 1 ELSE 0 END) AS OBS,
Max(DATE) DATE
FROM
(
select t.*,
CASE WHEN STATUS = 'START' THEN DATE ELSE '' END AS DT,
COALESCE(
(select t1.id from t t1 where t1.STATUS = 'START' and t1.id > t.id ORDER BY T1.ID limit 1)
,99999) NEXTID
from t
) S
GROUP BY NEXTID;
+------+--------+------+--------+
| ID | STATUS | OBS | DATE |
+------+--------+------+--------+
| 1 | START | 4 | Jan 1 |
| 6 | START | 2 | Dec 22 |
+------+--------+------+--------+
2 rows in set (0.00 sec)
This is a form of gaps-and-islands problem -- which is simpler in MySQL 8+ using window functions.
In older versions, probably the most efficient method is to accumulate a count of starts to define groupings for the rows. You can do this using variables and then aggregate:
select min(id) as id, 'START' as status, sum(status = 'WORKING') as num_working, max(date) as date
from (select t.*, (#s := #s + (t.status = 'START')) as grp
from (select t.* from t order by id asc) t cross join
(select #s := 0) params
) t
group by grp
order by min(id);
Here is a db<>fiddle.
SELECT id, status, `count`, `date`
FROM ( SELECT #count `count`,
id,
status,
`date`,
#count:=(#status=status)*#count+1,
#status:=status
FROM test,
( SELECT #count:=0, #status:='' ) init_vars
ORDER BY id DESC
) calculations
WHERE status='START'
ORDER BY id
> Since I am still in design/development I can move to MySQL 8 if that makes it easier for this logic? Any idea how this could be done with Windows functions? – N0000B
WITH cte AS ( SELECT id,
status,
`date`,
SUM(status='WORKING') OVER (ORDER BY id DESC) workings
FROM test
ORDER BY id )
SELECT id,
status,
workings - COALESCE(LEAD(workings) OVER (ORDER BY id), 0) `count`,
`date`
FROM cte
WHERE status='START'
ORDER BY id
fiddle

SQL groups inside groups

I have transaction data like this example:
Name | Price
George | 20
George | 20
George | 20
George | 30
Paul | 20
Paul | 20
Paul | 30
Paul | 30
Paul | 35
I need to group by user and sort by the number of transactions in general, but within that group of users also make groups by price ordering by the amount of transactions at that price.
I need this result:
Name | Price | Count
Paul | 20 | 2
Paul | 30 | 2
Paul | 35 | 1
George | 20 | 3
George | 30 | 1
UPDATE
It is in a MySQL 5.5 database. I need to make the query for fluent in Laravel but having it in SQL is a great advance.
Thanks in advance.
SELECT t1.*
FROM ( SELECT name,
price,
COUNT(*) cnt
FROM srctable
GROUP BY name, price ) t1
JOIN ( SELECT name,
COUNT(*) tcnt
FROM srctable
GROUP BY name ) t2 USING (name)
ORDER BY tcnt DESC,
cnt DESC;
fiddle
Here you go. It can be done in MySQL 8.x. The double ordering you want requires the use of a couple of table expressions, as shown below:
select
x.name, x.price, x.cnt
from (
select name, price, count(*) as cnt
from t
group by name, price
) x
join (
select name, row_number() over(order by cnt desc) as ob
from (
select name, count(*) as cnt
from t
group by name
) y
) z on x.name = z.name
order by z.ob, x.cnt desc
Result:
name price cnt
------ ----- ---
Paul 20 2
Paul 30 2
Paul 35 1
George 20 3
George 30 1
For reference, the data script I used is:
create table t (
name varchar(10),
price int
);
insert into t (name, price) values
('George', 20),
('George', 20),
('George', 20),
('George', 30),
('Paul', 20),
('Paul', 20),
('Paul', 30),
('Paul', 30),
('Paul', 35);

How to limit a query by column value

Following query...
SELECT event_id, user_id FROM EventUser WHERE user_id IN (1, 2)
...gives me the following result:
+----------+---------+
| event_id | user_id |
+----------+---------+
| 3 | 1 |
| 2 | 1 |
| 1 | 1 |
| 5 | 1 |
| 4 | 1 |
| 6 | 1 |
| 4 | 2 |
| 2 | 2 |
| 1 | 2 |
| 5 | 2 |
+----------+---------+
Now, I want to modify the above query so that I only get for example two rows for each user_id, eg:
+----------+---------+
| event_id | user_id |
+----------+---------+
| 3 | 1 |
| 2 | 1 |
| 4 | 2 |
| 5 | 2 |
+----------+---------+
I am thinking about something like this, which of course does not work:
SELECT event_id, user_id FROM EventUser WHERE user_id IN (1, 2) LIMIT 2 by user_id
Ideally, this should work with offsets as well because I want to use it for paginations.
For performance reasons it is essential to use the WHERE user_id IN (1, 2) part of the query.
One method -- assuming you have at least two rows for each user -- would be:
(select min(event_id) as event_id, user_id
from t
where user in (1, 2)
group by user_id
) union all
(select max(event_id) as event_id, user_id
from t
where user in (1, 2)
group by user_id
);
Admittedly, this is not a "general" solution, but it might be the simplest solution for what you want.
If you want the two biggest or smallest, then an alternative also works:
select t.*
from t
where t.user_id in (1, 2) and
t.event_id >= (select t2.event_id
from t t2
where t2.user_id = t.user_id
order by t2.event_id desc
limit 1, 1
);
Here is a dynamic example for such problems, Please note that this example is working in SQL Server, could not try on mysql for now. Please let me know how it works.
CREATE TABLE mytable
(
number INT,
score INT
)
INSERT INTO mytable VALUES ( 1, 100)
INSERT INTO mytable VALUES ( 2, 100)
INSERT INTO mytable VALUES ( 2, 120)
INSERT INTO mytable VALUES ( 2, 110)
INSERT INTO mytable VALUES ( 3, 120)
INSERT INTO mytable VALUES ( 3, 150)
SELECT *
FROM mytable m
WHERE
(
SELECT COUNT(*)
FROM mytable m2
WHERE m2.number = m.number AND
m2.score >= m.score
) <= 2
How about this?
SELECT event_id, user_id
FROM (
SELECT event_id, user_id, row_number() OVER (PARTITION BY user_id) AS row_num
FROM EventUser WHERE user_id in (1,2)) WHERE row_num <= n;
And n can be whatever
Later but help uses a derived table and the cross join.
For the example in this post the query will be this:
SELECT
#row_number:=CASE
WHEN #user_no = user_id
THEN
#row_number + 1
ELSE
1
END AS num,
#user_no:=user_id userid, event_id
FROM
EventUser,
(SELECT #user_no:=0,#row_number:=0) as t
group by user_id,event_id
having num < 3;
More information in this link.

SQL - get latest records from table where field is unique

I have a table of data as follows
id status conversation_id message_id date_created
1 1 1 72 2012-01-01 00:00:00
2 2 1 87 2012-03-03 00:00:00
3 2 2 95 2012-05-05 00:00:00
I want to get all the rows from the table in date_created DESC order, but only one row per conversation_id. So in the case of the example data above, I would want to get the rows with id 2 and 3.
Any advice is much appreciated.
SELECT t.id, t.status, t.conversation_id, t.message_id, t.date_created
FROM YourTable t
INNER JOIN (SELECT conversation_id, MAX(date_created) AS MaxDate
FROM YourTable
GROUP BY conversation_id) q
ON t.conversation_id = q.conversation_id
AND t.date_created = q.MaxDate
ORDER BY t.date_created DESC;
See SQL Fiddle
SELECT T.*
FROM T
WHERE NOT EXISTS (
SELECT *
FROM T AS _T
WHERE _T.conversation_id = T.conversation_id
AND (
_T.date_created > T.date_created
OR
_T.date_created = T.date_created AND _T.id > T.id)
)
ORDER BY T.date_created DESC
gets
ID STATUS CONVERSATION_ID MESSAGE_ID DATE_CREATED
3 2 2 95 May, 05 2012
2 2 1 87 March, 03 2012
Creation and inserts borrowed from #Incidently:
CREATE TABLE T
(id int, status int, conversation_id int, message_id int, date_created datetime);
insert into T (id, status, conversation_id, message_id, date_created)
values(1, 1, 1, 72, '2012-01-01 00:00:00');
insert into T (id, status, conversation_id, message_id, date_created)
values(2, 2, 1, 87, '2012-03-03 00:00:00');
insert into T (id, status, conversation_id, message_id, date_created)
values(3, 2 , 2 , 95 , '2012-05-05 00:00:00');
Supposing the id is the primary key then this solves the problem pointed by #Incidentally in #Joe's answer:
select T.*
from
T
inner join (
select conversation_id, max(id) as id
from T
group by conversation_id
) s on s.id = T.id
order by T.date_created desc, T.conversation_id
;
+------+--------+-----------------+------------+---------------------+
| id | status | conversation_id | message_id | date_created |
+------+--------+-----------------+------------+---------------------+
| 3 | 2 | 2 | 95 | 2012-05-05 00:00:00 |
| 2 | 2 | 1 | 87 | 2012-03-03 00:00:00 |
+------+--------+-----------------+------------+---------------------+