I have the following table:
id
date
type
001
2022-01-01
A
001
2022-01-03
B
001
2022-01-02
B
001
2022-02-02
A
002
2022-01-01
A
002
2022-01-03
B
004
2022-01-01
A
004
2022-01-03
B
And I need to sort the dates decending, group by ID and Type and get the time between the dates by ID and Type, either in seconds or months days
id
date
type
time diff
001
2022-01-01
A
0
001
2022-01-02
B
1
001
2022-01-03
B
1
001
2022-02-02
A
31
002
2022-01-01
A
0
002
2022-01-03
B
2
004
2022-01-01
A
0
004
2022-01-03
B
2
We can use DATEDIFF() here along with the LAG() analytic function:
SELECT id, date, type,
DATEDIFF(date, LAG(date, 1, date) OVER (PARTITION BY id ORDER BY date)) AS diff
FROM yourTable
ORDER BY id, date;
If you are using MySQL 8.0,window functions are strongly recommended. Otherwise,you might have to go a long way. Here is the query written and tested in workbench using 5.7 :
select tb1.id,tb1.date,tb1.type,ifnull(datediff(tb1.date,tb2.date),0) as 'time diff'
from
(select id,date,type, #row_id:=#row_id+1 as row_id
from
(select id,date,type
from test
group by id,date,type
order by id,date)t1,
(select #row_id:=0) t
) tb1
left join
(select id,date,type, #row_num:=#row_num+1 as row_num
from
(select id,date,type
from test
group by id,date,type
order by id,date)t2,
(select #row_num:=0) t
) tb2
on tb1.id=tb2.id and tb1.row_id-tb2.row_num=1
order by tb1.id,tb1.date
;
Related
I have a database that looks like this:
ID
Sale_Date(YYYY-MM-DD)
Total_Volume
123
2022-01-01
0
123
2022-01-02
2
123
2022-01-03
5
456
2022-04-06
38
456
2022-04-07
40
456
2022-04-08
45
I want to get a daily sale column from Total Volume. which is just by subtracting the total volume on date x with total volume on date x-1 for each id.
ID
Sale_Date(YYYY-MM-DD)
Total_Volume
Daily_Sale
123
2022-01-01
0
0
123
2022-01-02
2
2
123
2022-01-03
5
3
456
2022-04-06
38
38
456
2022-04-07
40
2
456
2022-04-08
45
5
My initial attempt was using a rank function and self join but that didnt turn out correct.
with x as (
select
distinct t1.ID,
t1.Sale_Date,
t1.Total_volume,
rank() over (partition by ID order by Sale_Date) as ranker
from t t1 order by t1.Sale_Date)
select t2.ID, t2.ranker, t2.Sale_date, t1.Total_volume, t1.Total_volume - t2.Total_volume as Daily_sale
from x t1, x t2 where t1.ID = t2.ID and t2.ranker = t1.ranker-1 order by t1.ID;
You should use:
the LAG window function to retrieve last "Sale_Date" value
the COALESCE function to replace NULL with "Total Volume" for each first rows
Then subtract Total_Volume from the previous value of Total_Volume and coalesce if the value of the LAG is NULL.
SELECT *,
COALESCE(`Total_Volume`
-LAG(`Total_Volume`) OVER(PARTITION BY `ID`
ORDER BY `Sale_Date(YYYY-MM-DD)`), `Total_Volume`) AS `Daily_Sale`
FROM tab
Check the demo here.
How to do this in Mysql to get all users even no records or absent on that selected date range?
attendance_tbl
ID
user_id
time_in
time_out
created_at
1
001
2022-01-01 08:00:00
2022-01-01 17:00:00
2022-01-03 08:00:00
2
002
2022-01-01 08:15:24
2022-01-01 17:00:00
2022-01-03 08:15:24
3
003
2022-01-02 08:44:55
2022-01-02 17:00:00
2022-01-04 08:44:55
4
004
2022-01-03 08:40:22
2022-01-03 17:00:00
2022-01-04 08:40:22
users_tbl
ID
user_id
f_name
1
001
John Doe
2
002
Jane Doe
3
003
Ronal Black
4
004
Lucy White
Expected Output Daterange : from 2022-01-01 to 2022-01-03
Will get all the Users Fullname
ID
user_id
Date
f_name
time_in
time_out
created_at
1
001
Jan 1 2022
John Doe
2022-01-01 08:00:00
2022-01-01 17:00:00
2022-01-03 08:00:00
2
002
Jan 1 2022
Jane Doe
2022-01-01 08:15:24
2022-01-01 08:15:24
2022-01-03 08:00:00
3
003
Jan 1 2022
Ronal Black
4
004
Jan 1 2022
Lucy White
5
001
Jan 2 2022
John Doe
6
002
Jan 2 2022
Jane Doe
7
003
Jan 2 2022
Ronal Black
2022-01-02 17:00:00
2022-01-02 17:00:00
2022-01-02 17:00:00
8
004
Jan 2 2022
Lucy White
9
001
Jan 3 2022
John Doe
10
002
Jan 3 2022
Jane Doe
11
003
Jan 3 2022
Ronal Black
12
004
Jan 3 2022
Lucy White
2022-01-04 17:00:00
2022-01-04 17:00:00
2022-01-04 17:00:00
Given that you want to include the absent data, we need to start by getting the date range for the desired period. Using a user variable to store and increment a counter value is a performant way of doing this -
SELECT
'2022-01-01' + INTERVAL #row_number DAY `date`,
#row_number := #row_number + 1
FROM `attendance_tbl`, (SELECT #row_number := 0) AS `x`
LIMIT 31 /* 31 days in January */
If you have a table with a contiguous integer sequence (auto-incremented PK without deletes), you could use that instead -
SELECT '2022-01-01' + INTERVAL (`id` - 1) DAY `date`
FROM `attendance_tbl`
WHERE `id` <= 31 /* 31 days in January */
ORDER BY `id` ASC
We then add a cross join to build the full set of dates and users -
SELECT *
FROM (
SELECT '2022-01-01' + INTERVAL (`id` - 1) DAY `date`
FROM `attendance_tbl`
WHERE `id` <= 31
ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`
By cross joining between these two tables we get the cartesian product (all combinations of the two sets). We then just take it a step further by using a left join to the attendance data -
SELECT
`u`.`user_id`,
DATE_FORMAT(`d`.`date`, '%b %e %Y') `date`,
`u`.`f_name`,
`a`.`time_in`,
`a`.`time_out`
FROM (
SELECT
'2022-01-01' + INTERVAL (`id` - 1) DAY `date`,
(SELECT TIMESTAMP(`date`, '00:00:00')) `begin`,
(SELECT TIMESTAMP(`date`, '23:59:59')) `end`
FROM `attendance_tbl`
WHERE `id` <= 31
ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`
LEFT JOIN `attendance_tbl` `a`
ON `u`.`user_id` = `a`.`user_id`
AND `a`.`time_in` BETWEEN `d`.`begin` AND `d`.`end`
ORDER BY `d`.`date`, `u`.`user_id`
If your attendance_tbl can have more than 1 row per user per day then you will need to add GROUP BY d.date, u.user_id and aggregate_functions in the select list.
I have added begin and end to the derived table. This is to allow for index use for the join. This is not important while the attendance_tbl is small but will matter more as the table grows. Adding an index on (user_id, time_in) will make a huge difference to performance in the longer term.
Here's a db<>fiddle for you to play with.
To run this from PHP using PDO you could do something like this -
<?php
$pdo = new PDO($dsn, $user, $password);
$sql = "SELECT
`u`.`user_id`,
DATE_FORMAT(`d`.`date`, '%b %e %Y') `date`,
`u`.`f_name`,
`a`.`time_in`,
`a`.`time_out`
FROM (
SELECT
:START_DATE + INTERVAL (`id` - 1) DAY `date`,
(SELECT TIMESTAMP(`date`, '00:00:00')) `begin`,
(SELECT TIMESTAMP(`date`, '23:59:59')) `end`
FROM `attendance_tbl`
WHERE `id` <= :DAYS_RANGE
ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`
LEFT JOIN `attendance_tbl` `a`
ON `u`.`user_id` = `a`.`user_id`
AND `a`.`time_in` BETWEEN `d`.`begin` AND `d`.`end`
ORDER BY `d`.`date`, `u`.`user_id`";
$stmt = $pdo->prepare($sql);
$startDate = new DateTime('2022-01-01');
$endDate = new DateTime('2022-02-01');
$interval = $startDate->diff($endDate, true);
$daysRange = $interval->days + 1;
// Execute the statement
$stmt->execute([
':START_DATE' => $startDate->format('Y-m-d'),
':DAYS_RANGE' => $daysRange]
);
$attendance = $stmt->fetchAll(PDO::FETCH_OBJ);
Check this. In here I call the attendance_tbl twice, one for creating a list of date and users and the other for fetching the data (time in and time out). And by using BETWEEN as #nnichols suggested to filter the selected range you prefer which I just realized earlier.
select u.`user_id`, date(a.time_in) as `date`, u.`f_name`, b.`time_in`, b.`time_out`, b.created_at from attendance_tbl a
join users_tbl u
left join attendance_tbl b on b.`user_id`=u.`user_id` and date(b.`time_in`)=date(a.`time_in`)
WHERE DATE(a.time_in) BETWEEN '2022-01-01' AND '2022-01-31'
GROUP BY `date`, u.user_id;
RESULT
user_id date f_name time_in time_out created_at
------- ---------- ----------- ------------------- ------------------- ---------------------
001 2022-01-01 John Doe 2022-01-01 08:00:00 2022-01-01 17:00:00 2022-01-03 08:00:00
002 2022-01-01 Jane Doe 2022-01-01 08:15:24 2022-01-01 17:00:00 2022-01-03 08:15:24
003 2022-01-01 Ronal Black (NULL) (NULL) (NULL)
004 2022-01-01 Lucy White (NULL) (NULL) (NULL)
001 2022-01-02 John Doe (NULL) (NULL) (NULL)
002 2022-01-02 Jane Doe (NULL) (NULL) (NULL)
003 2022-01-02 Ronal Black 2022-01-02 08:44:55 2022-01-02 17:00:00 2022-01-04 08:44:55
004 2022-01-02 Lucy White (NULL) (NULL) (NULL)
001 2022-01-03 John Doe (NULL) (NULL) (NULL)
002 2022-01-03 Jane Doe (NULL) (NULL) (NULL)
003 2022-01-03 Ronal Black (NULL) (NULL) (NULL)
004 2022-01-03 Lucy White 2022-01-03 08:40:22 2022-01-03 17:00:00 2022-01-04 08:40:22
For the ID column just create a table with AUTO_INCREMENT id and insert your selected data.
To format your date (if you really really need to) like the one in your example result, just change the DATE(a.time_in) to DATE_format(a.time_in, '%b %d %Y').
SQL Fiddle Example
Somewhat new to SQL and I'm running into a bit of issue with a project. I have a table like this:
ID
subscription_ID
renewal_date
1
11
2022-01-01 00:00:00
2
11
2022-01-02 00:00:00
3
12
2022-01-01 00:00:00
4
12
2022-01-01 12:00:00
5
13
2022-01-01 12:00:00
6
13
2022-01-03 12:00:00
My goal is to return rows where the subscription_ID matches and the start_date is within or equal to a certain # of days (hours would work as well). For instance, I'd like rows where subscription_ID matches and the start_date is within or equal to 1 day such that my results from the table above would be:
ID
subscription_ID
renewal_date
1
11
2022-01-01 00:00:00
2
11
2022-01-02 00:00:00
3
12
2022-01-01 00:00:00
4
12
2022-01-01 12:00:00
Any assistance would be greatly appreciated--thanks!
If I understand correctly maybe you are trying something like:
select t.*
from test_tbl t
join ( SELECT subscription_id
, MAX(diff) max_diff
FROM
( SELECT x.subscription_id
, DATEDIFF(MIN(y.start_date),x.start_date) diff
FROM test_tbl x
JOIN test_tbl y ON y.subscription_id = x.subscription_id
AND y.start_date > x.start_date
GROUP BY x.subscription_id , x.start_date
) z
GROUP BY subscription_id
) as t1 on t.subscription_id=t1.subscription_id
where t1.max_diff<=1;
Result:
id subscription_id start_date
1 11 2022-01-01 00:00:00
2 11 2022-01-02 00:00:00
3 12 2022-01-01 00:00:00
4 12 2022-01-01 12:00:00
The subquery returns:
subscription_id max_diff
11 1
12 0
13 2
which is used on the where condition.
Demo
I have 2 tables with structure as
Emp Table
id name
001 Smith
002 Jerry
Leave
sr.no reason from_date to_date request_by status
1 PL 2011-12-11 2011-12-15 001 Declined
2 PL 2011-11-13 2011-11-13 001 Approved
3 PL 2011-10-02 2011-10-05 002 Declined
Now I have written this query
select DATEDIFF(Leave.from_date,Leave.to_date)as cdate,
Emp.id as emp
from Leave left join Emp
on Leave.request_by=Emp.id
gives me difference between these 2 dates like...
cdate emp
-4 001
0 001
-3 002
The first thing about this output difference between '2011-12-11 & 2011-12-15 ' need to be 5 as for 5 consecutive days employee is absent. That we achieve it.
But I need this cdate in date format like('%Y%m%d') and + if date difference is say -4 then 4 records should be displayed for that.
So I want to write a query which gives output like this......
cdate emp
2011-12-11 001
2011-12-12 001
2011-12-13 001
2011-12-14 001
2011-12-15 001
2011-11-13 001
2011-10-02 002
2011-10-03 002
2011-10-04 002
2011-10-05 002
So can anybody tell me what how should I need to write my query to get this output?
Try this query -
CREATE TABLE temp_days(d INT(11));
INSERT INTO temp_days VALUES
(0),(1),(2),(3),(4),(5),
(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15); -- maximum day difference, add more days here
SELECT l.from_date + INTERVAL td.d DAY cdate, e.id emp
FROM
`leave` l
LEFT JOIN Emp e
ON l.request_by = e.id
JOIN temp_days td
ON DATEDIFF(l.to_date, l.from_date) >= td.d
ORDER BY
e.id
I have a table that looks like this:
posid sales eid
1 20 001
1 20 002
1 30 001
2 30 001
1 30 002
2 30 001
1 30 002
2 20 002
2 10 002
I want to write a query that would give me sum of sales for each employee on particular pos. the result needs to be like this.
pos id emp id sales
1 001 50
1 002 80
2 001 60
2 002 30
How would I do this?
Use group by:
select t.posid
, t.eid
, sum(t.sales) as sales_by_posid
from mytable t
group by t.posid, t.eid
order by sales_by_posid desc
SELECT
posID, empID, sum(sales)
FROM your_table
GROUP BY posID, empID
ORDER BY posID, empID
Here's a group by tutorial: http://www.tizag.com/mysqlTutorial/mysqlgroupby.php