I want to fill in the dates between today and 30 days ago in mysql.
eg:
Date Value
2015-08-05 1
2015-08-04 2
2015-08-03 0
......
2015-07-05 1
Below is mysql:
SELECT IFNULL(SUM(units.price), 0) as price, DATE(units.solddate) as date, DAY(units.solddate) as day,
(
select a.Date
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
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) as a
cross join (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) as b
cross join (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) as c
) a
where a.Date >= DATE_SUB(NOW(), INTERVAL 30 day)
) as onemonth
FROM (tables)
GROUP BY date
However, I got this error: #1242 - Subquery returns more than 1 row I understood this error.
Please advice. Thank you.
This is a solution by correcting your approach
SELECT
`dates`.`date` ,SUM( COALESCE( `units`.`price` ,0 ) ) AS `price`
FROM
(
-- Start of query for making dates
SELECT
`dates`.`date`
,CONCAT( `dates`.`date` ,' 00:00:00' ) AS `day_start_datetime`
,CONCAT( `dates`.`date` ,' 23:59:59' ) AS `day_end_datetime`
FROM
(
SELECT
DATE_SUB( CURDATE( ) ,INTERVAL `intervals`.`days` DAY ) AS `date`
FROM
(
SELECT
( `hundreds_place`.`num` * 100 )
+( `tens_place`.`num` * 10 )
+( `ones_place`.`num` * 1 ) AS `days`
FROM ( SELECT 0 AS `num` UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) AS `ones_place`
JOIN ( SELECT 0 AS `num` UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) AS `tens_place`
JOIN ( SELECT 0 AS `num` UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) AS `hundreds_place`
) `intervals`
HAVING
`date` >= DATE_SUB( CURDATE() ,INTERVAL 30 DAY )
) `dates`
-- End of query for making dates
) `dates`
LEFT OUTER JOIN `units`
ON `units`.`sold_date` BETWEEN `day_start_datetime` AND `day_end_datetime`
WHERE
1
GROUP BY
`dates`.`date`
ORDER BY
`dates`.`date`
After getting better understanding of what you are trying to achieve , I have put together this MySQL query .
I have tested it and it worked for me .
SELECT
`dates_table`.`date`
-- ,`dates_table`.`day`
,SUM( COALESCE( `units`.`price` ,0 ) ) AS `price`
FROM
-- Start of query for making dates_table
(
SELECT
DATE( DATE_SUB( CURDATE( ) ,INTERVAL `intervals_table`.`days` DAY ) ) AS `date`
,DAY( DATE_SUB( CURDATE( ) ,INTERVAL `intervals_table`.`days` DAY ) ) AS `day`
FROM
(
SELECT 0 AS `days`
UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12
UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16
UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 UNION SELECT 20
UNION SELECT 21 UNION SELECT 22 UNION SELECT 23 UNION SELECT 24
UNION SELECT 25 UNION SELECT 26 UNION SELECT 27 UNION SELECT 28
UNION SELECT 29 UNION SELECT 30 UNION SELECT 31
) `intervals_table`
HAVING
`date` >= DATE_SUB( CURDATE( ) ,INTERVAL 30 DAY )
)
-- End of query for making dates_table
`dates_table`
LEFT OUTER JOIN `units` ON
`units`.`sold_date` >= CONCAT( `dates_table`.`date` ,' 00:00:00' )
AND `units`.`sold_date` <= CONCAT( `dates_table`.`date` ,' 23:59:59' )
WHERE
1
GROUP BY
`dates_table`.`date`
ORDER BY
`dates_table`.`date`
The units table I have used for testing is :
CREATE TABLE IF NOT EXISTS `units` (
`id` int(11) NOT NULL AUTO_INCREMENT
,`price` int(11) NOT NULL
,`sold_date` datetime NOT NULL
,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `units` (`id`, `price`, `sold_date`) VALUES
(1, 10, '2015-08-02 00:00:00'),
(2, 11, '2015-08-03 00:00:00'),
(3, 14, '2015-08-03 00:00:00'),
(4, 3, '2015-08-04 00:00:00');
Result
date price
---------- -----
2015-07-07 0
2015-07-08 0
2015-07-09 0
2015-07-10 0
2015-07-11 0
2015-07-12 0
2015-07-13 0
2015-07-14 0
2015-07-15 0
2015-07-16 0
2015-07-17 0
2015-07-18 0
2015-07-19 0
2015-07-20 0
2015-07-21 0
2015-07-22 0
2015-07-23 0
2015-07-24 0
2015-07-25 0
2015-07-26 0
2015-07-27 0
2015-07-28 0
2015-07-29 0
2015-07-30 0
2015-07-31 0
2015-08-01 0
2015-08-02 10
2015-08-03 25
2015-08-04 3
2015-08-05 0
Related
I have 2 following queries return the same columns:
first query is slightly modified one I found here in different post returning all day of the current month:
select null as id,
a.day, 0 as was_present,
0 as was_late,
0 as left_earlier,
null as student_id
from (
select
last_day(NOW())
- INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as day
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
) as a
cross join
(
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
) as b
cross join
(
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
) as c
) as a
where a.day between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW()
order by a.day
while the second is returning attendance data from the real table like this:
select *
from student_attendance
where student_id = ?1
and (date between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW())
Sample data from second query look like this:
2 2019-10-01 1 0 0 5
3 2019-10-02 1 0 0 5
4 2019-10-03 1 0 0 5
5 2019-10-04 1 0 0 5
Now my question is how to combine them together to return only unique days of the month (1 record per day) where the real record from second query replaces fake record in first query when it exist. I tried to use union but it return duplicates.
Again, using union doesn't work, here is what it returns:
2019-10-01 0 0 0
2 2019-10-01 1 0 0 5
2019-10-02 0 0 0
3 2019-10-02 1 0 0 5
4 2019-10-03 1 0 0 5
2019-10-03 0 0 0
2019-10-04 0 0 0
5 2019-10-04 1 0 0 5
2019-10-05 0 0 0
You could use a temporary table then do a LEFT JOIN to it:
CREATE TEMPORARY TABLE days
select a.day
from (
select last_day(NOW()) - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as
day
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) as a
cross join (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) as b
cross join (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) as c
) a where a.day between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW() order by
a.day;
SELECT
id, days.`day`, was_present, was_late, left_earlier, student_id
FROM days
LEFT JOIN student_attendance sa ON days.`day` = sa.`day`;
try with the UNION between the second query and the rows of the first query with date not present in the second query, something like this:
select *
from student_attendance
where student_id = ?1 and (date between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW())
UNION
select null as id,
a.day, 0 as was_present,
0 as was_late,
0 as left_earlier,
null as student_id
from (
select last_day(NOW()) - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as day
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
) as a
cross join
(
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
) as b
cross join
(
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
) as c
) as a
where a.day between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW()
and a.day NOT IN (
select date
from student_attendance
where student_id = ?1 and (date between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW())
)
order by a.day
My company policy is that we count the Saturdays before 15 dates as working days and after 15 days as company holiday kindly tell me the function to count working days of the month skip all Sundays and Saturdays before 15 date in Query
You can use below code to count the working days as per your requirement -
CREATE FUNCTION FN_CNT_Working_days(StartDate DATE,
EndDate DATE)
RETURNS INT
BEGIN
DECLARE WORKING_DAYS INT;
SELECT
(DATEDIFF(EndDate, DATE(CONCAT(YEAR(EndDate), '-', MONTH(EndDate), '-', 16))) + 1)
-(FLOOR(DATEDIFF(EndDate, DATE(CONCAT(YEAR(EndDate), '-', MONTH(EndDate), '-', 16)))/7) * 2)
-(CASE WHEN DAYNAME(DATE(CONCAT(YEAR(EndDate), '-', MONTH(EndDate), '-', 16))) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DAYNAME(EndDate) = 'Saturday' THEN 1 ELSE 0 END)
+
(DATEDIFF(DATE(CONCAT(YEAR(EndDate), '-', MONTH(EndDate), '-', 15)), StartDate) + 1)
-(FLOOR(DATEDIFF(DATE(CONCAT(YEAR(EndDate), '-', MONTH(EndDate), '-', 15)), StartDate)/7))
-(CASE WHEN DAYNAME(StartDate) = 'Sunday' THEN 1 ELSE 0 END)
INTO WORKING_DAYS;
RETURN (WORKING_DAYS);
END;
Here is the fiddle. Also you need to take care of holidays along with this code.
You can try either one method below or can use the query which was shared by #Ankit.
If you have a table like calendar it will be useful in many ways.
METHOD: 1
If you have a Calendar table you can run the below query,
SELECT
SUM(CASE WHEN WEEKDAY(cal.cal_date) <> 6 AND DAY(cal.cal_date) < 16 THEN 1
WHEN WEEKDAY(cal.cal_date) NOT IN (5, 6) AND DAY(cal.cal_date) > 15 THEN 1 END) AS 'working days'
FROM
calendar cal
WHERE
cal.cal_date BETWEEN '2019-10-01' AND '2019-10-31';
METHOD: 2
If creating a TABLE OR VIEW is restricted can generate a range of dates using the below query
SELECT
SUM(CASE WHEN WEEKDAY(cal.cal_date) <> 6 AND DAY(cal.cal_date) < 16 THEN 1
WHEN WEEKDAY(cal.cal_date) NOT IN (5, 6) AND DAY(cal.cal_date) > 15 THEN 1 END) AS 'working days'
FROM
(SELECT
('1970-01-01' + INTERVAL (((((`t4`.`t4` * 10000) + (`t3`.`t3` * 1000)) + (`t2`.`t2` * 100)) + (`t1`.`t1` * 10)) + `t0`.`t0`) DAY) AS `cal_date`
FROM
((((((SELECT 0 AS `t0`) UNION ALL SELECT 1 AS `1` UNION ALL SELECT 2 AS `2` UNION ALL SELECT 3 AS `3` UNION ALL SELECT 4 AS `4` UNION ALL SELECT 5 AS `5` UNION ALL SELECT 6 AS `6` UNION ALL SELECT 7 AS `7` UNION ALL SELECT 8 AS `8` UNION ALL SELECT 9 AS `9`) `t0`
JOIN (SELECT 0 AS `t1` UNION ALL SELECT 1 AS `1` UNION ALL SELECT 2 AS `2` UNION ALL SELECT 3 AS `3` UNION ALL SELECT 4 AS `4` UNION ALL SELECT 5 AS `5` UNION ALL SELECT 6 AS `6` UNION ALL SELECT 7 AS `7` UNION ALL SELECT 8 AS `8` UNION ALL SELECT 9 AS `9`) `t1`)
JOIN (SELECT 0 AS `t2` UNION ALL SELECT 1 AS `1` UNION ALL SELECT 2 AS `2` UNION ALL SELECT 3 AS `3` UNION ALL SELECT 4 AS `4` UNION ALL SELECT 5 AS `5` UNION ALL SELECT 6 AS `6` UNION ALL SELECT 7 AS `7` UNION ALL SELECT 8 AS `8` UNION ALL SELECT 9 AS `9`) `t2`)
JOIN (SELECT 0 AS `t3` UNION ALL SELECT 1 AS `1` UNION ALL SELECT 2 AS `2` UNION ALL SELECT 3 AS `3` UNION ALL SELECT 4 AS `4` UNION ALL SELECT 5 AS `5` UNION ALL SELECT 6 AS `6` UNION ALL SELECT 7 AS `7` UNION ALL SELECT 8 AS `8` UNION ALL SELECT 9 AS `9`) `t3`)
JOIN (SELECT 0 AS `t4` UNION ALL SELECT 1 AS `1` UNION ALL SELECT 2 AS `2` UNION ALL SELECT 3 AS `3` UNION ALL SELECT 4 AS `4` UNION ALL SELECT 5 AS `5` UNION ALL SELECT 6 AS `6` UNION ALL SELECT 7 AS `7` UNION ALL SELECT 8 AS `8` UNION ALL SELECT 9 AS `9`) `t4`)) AS cal
WHERE
cal.cal_date BETWEEN '2019-10-01' AND '2019-10-31';
Fiddle here
This works without calendar table and by month. And you only need to change the date value #curdt variable. It can be any dates within the month as long as its not less or more than the real calendar dates e.g '2019-07-00' or '2019-07-32' - this won't work.
#1 : Setting variable.
SET #curdt := '2019-07-01'; #only need to change the date here.
SET #startdate := LAST_DAY(#curdt-INTERVAL 1 MONTH)+INTERVAL 1 DAY;
SET #lastdate := LAST_DAY(#curdt);
#2 : Count workdays.
SELECT SUM(CASE WHEN DAY(dt) <= 15 AND WEEKDAY(dt)=6 THEN 0
WHEN DAY(dt) > 15 AND WEEKDAY(dt) IN (5,6) THEN 0
ELSE 1 END) AS Workdays
FROM
(SELECT dt
FROM (
#custom calendar
SELECT CONCAT_WS('-',curmy,CONCAT(n2,n1)) dt
FROM
(SELECT 0 AS n1 UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 ) a CROSS JOIN
(SELECT 0 n2 UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3) b CROSS JOIN
(SELECT LEFT(#curdt,7) curmy) c
) zz
WHERE dt BETWEEN #startdate AND #lastdate) XX
GROUP BY MONTH(dt);
Just tried a new query and might just add in here for future reference:
SET #curdate := CURDATE()-INTERVAL 1 MONTH;
SELECT * FROM
(SELECT CONCAT_WS('-',df,CONCAT(b,c)) dm FROM
(SELECT DATE_FORMAT(#curdate, '%Y-%m') df) a CROSS JOIN
(SELECT 0 b UNION SELECT 1 b UNION SELECT 2 UNION SELECT 3) b CROSS JOIN
(SELECT 0 c UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9) c) d WHERE DAY(dm) BETWEEN 1 AND DAY(LAST_DAY(dm))
ORDER BY dm;
This method is for MariaDB - using it's sequence engine:
From MariaDB 10.1, the Sequence engine is installed by default.
SELECT DATE_FORMAT(CONCAT_WS('-',a.seq,b.seq,c.seq), '%Y-%m-%d') dates
FROM seq_2020_to_2021 a -- year range
CROSS JOIN seq_1_to_12 b -- month range
CROSS JOIN seq_1_to_31 c -- day range
HAVING dates IS NOT NULL; -- excluding invalid dates returned as NULL
I have a table Order:
id total_amount date_order
1 12.000 2018-09-01
2 10.000 2018-09-01
3 5.000 2018-09-03
4 2.000 2018-09-05
I have query SUM data group by date:
select SUM(total_amount) as 'total', DATE(date_order) as 'date'
from Order
where date_order >= '2018-09-01' and date_order <= '2018-09-06'
group by (date_order)
It shows:
total date
22.000 2018-09-01
5.000 2018-09-03
2.000 2018-09-05
Because data in 2018-09-02 and 2018-09-04 have no data so It's not show in result. But I want query to show table with expect result:
total date
22.000 2018-09-01
0 2018-09-02
5.000 2018-09-03
0 2018-09-04
2.000 2018-09-05
Every can help me write query to show like expect result ?
Create another table to join on.
It could be a table of dates, such as all date from 2000-01-01 until 2099-12-31...
SELECT
dt.`date`,
SUM(total_amount) as `total`
FROM
yourDatesTable AS dt
LEFT JOIN
Order AS o
ON o.`date_order` = dt.`date`
WHERE
dt.`date` >= '2018-09-01'
AND dt.`date` < '2018-09-08'
GROUP BY
dt.`date`
ORDER BY
dt.`date`
Or it could be a numbers table with values from -1023 to +1024 (or some other useful range)...
SELECT
n.id,
'2018-09-01' + INTERVAL n.id DAYS,
SUM(total_amount) as `total`
FROM
yourNumbersTable AS n
LEFT JOIN
Order AS o
ON o.`date_order` = '2018-09-01' + INTERVAL n.id DAYS
WHERE
n.id >= 0
AND n.id < 8
GROUP BY
n.id
ORDER BY
n.id
generate date and join with ordere table
select SUM(O.total_amount) as total, DATE(gen_date) as oreder_date
from
(
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2018-09-01' and '2018-09-30'
) as t1 left join `Order` O on t1.gen_date= DATE(O.date_order)
where gen_date >= '2018-09-01' and gen_date <= '2018-09-06'
group by oreder_date
Situation (simplified):
tableA
id | date | val
------------------------------
0 2018-02-19 00:01:00 | 10
1 2018-02-19 00:02:00 | 10
2 2018-02-19 00:03:00 | 10
.. 2018-02-19 23:59:00 | 10
I need to do a query that return for each hour the SUM of the column val.
This is the query
SELECT `AllHours`.`hour` , COALESCE(SUM(`A`.`val`),0) AS `A`.`total`
FROM `tableA` AS `A`
RIGHT JOIN (
SELECT 0 AS `hour`
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
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18
UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21
UNION ALL SELECT 22 UNION ALL SELECT 23
) AS `AllHours` ON HOUR(`A`.`date`) = `AllHours`.`hour`
WHERE `A`.`date` BETWEEN '2018-02-19 00:00:00' AND '2018-02-19 23:59:59' OR `A`.`date` IS NULL
GROUP BY `AllHours`.`hour`
ORDER BY `AllHours`.`hour`
Result
The query works, but hour 11 is missing.
Important note
I need all the hour, also if there aren't data. Otherwise i wouldn't have added the RIGHT JOIN and it would have been enough a GROUP BY HOUR(date).
Consider using a numbers table - see links below. This will give you the desired output for now:
Testdata:
CREATE TABLE T (`id` int, `dt` datetime, `val` int);
INSERT INTO T (`id`, `dt`, `val`)
VALUES
(0, '2018-02-19 00:01:00', 10),
(1, '2018-02-19 00:02:00', 10),
(2, '2018-02-19 00:03:00', 10),
(4, '2018-02-19 01:01:00', 10),
(5, '2018-02-19 01:02:00', 10),
(6, '2018-02-19 02:03:00', 10)
;
Sql:
select
lpad(cast(HH as char(2)),2,'0') as hour,
sum(val) as sumVal from
(
select
EXTRACT(HOUR from dt) AS HH,
val
from T
WHERE dt >= '2018-02-19 0:0:0' and dt < '2018-02-20 0:0:0'
UNION ALL SELECT 0,0
UNION ALL SELECT 1,0 UNION ALL SELECT 2,0 UNION ALL SELECT 3,0
UNION ALL SELECT 4,0 UNION ALL SELECT 5,0 UNION ALL SELECT 6,0
UNION ALL SELECT 7,0 UNION ALL SELECT 8,0 UNION ALL SELECT 9,0
UNION ALL SELECT 10,0 UNION ALL SELECT 11,0 UNION ALL SELECT 12,0
UNION ALL SELECT 13,0 UNION ALL SELECT 14,0 UNION ALL SELECT 15,0
UNION ALL SELECT 16,0 UNION ALL SELECT 17,0 UNION ALL SELECT 18,0
UNION ALL SELECT 19,0 UNION ALL SELECT 20,0 UNION ALL SELECT 21,0
UNION ALL SELECT 22,0 UNION ALL SELECT 23,0
) as m
group by lpad(cast(HH as char(2)),2,'0')
gets you an output of:
hour sumVal
00 30
01 20
02 10
03 0
04 0
05 0
06 0
07 0
08 0
09 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0
21 0
22 0
23 0
If you need this more often, create a numbers table and use the join syntax you already have. Without a numbers table you can as well union all without join.
SO-Readups:
MYSQL: Sequential Number Table
Creating a "Numbers Table" in mysql
With a numbers table the big union all above could be rewritten to
UNION ALL SELECT num, 0 from numbers where num between 0 and 23
or you could use a join on it and your coalesce syntax.
try it:
SELECT
`AllHours`.`tmp_hour`,
COALESCE(SUM(`val`),0) AS `total`
FROM
tableA
RIGHT JOIN (
SELECT 0 AS `tmp_hour`
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
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18
UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21
UNION ALL SELECT 22 UNION ALL SELECT 23
) AS `AllHours` ON DATE_FORMAT(dt, '%H') = `AllHours`.`tmp_hour`
WHERE `dt` BETWEEN '2018-02-19 00:00:00' AND '2018-02-19 23:59:59' OR `dt` IS NULL
GROUP BY `AllHours`.`tmp_hour`
ORDER BY `AllHours`.`tmp_hour`
I've a table named userActivity where each active period is recorded.
Here's the table structure:
Table: userActivity
ID user_id start_time end_time
When a user comes into online the start time is recorded and whenever the online status is changed the end time is recorded in the corresponding row.
Now I've to generate a report where a day wise available time of the users will be shown.
Sample Input:
ID user_id start_time end_time
'1' '1' '2016-02-28 10:00:00' '2016-02-28 19:00:00'
'2' '1' '2016-02-28 22:00:00' '2016-02-29 10:00:00'
'3' '1' '2016-03-02 10:00:00' '2016-03-02 19:00:00'
'4' '1' '2016-03-02 22:00:00' '2016-03-06 19:00:00'
Expected output:
Date AvailableTime(Hours)
2016-02-28 11
2016-02-29 10
2016-03-02 11
2016-03-03 24
2016-03-04 24
2016-03-05 24
2016-03-06 19
So far what I've tried:
SELECT
DATE_FORMAT(start_time,"%Y-%m-%d") `date`,
TIMESTAMPDIFF(HOUR,start_time,end_time) availableTime
FROM useractivity
GROUP BY `date`
Got output:
Date availableTime(Hours)
2016-02-28 9
2016-03-02 9
Here's the SQL FIDDLE
Note:
Please ignore the user_id for the time being. I can solve it in application level but I want to deal with it in MySQL.
The time interval can start one day and end more than one day later
In a word, the available time is just the projection in the day axis (from start time and end time). If the start time doesn't project into the same day as end time then the start time would be considered the start_time of that particular day where the end time projects into.
Pictorial View:
So available time will be calculated from this screenshot as follows:
28 Feb = (t2-t1) + (t4- t3)
29 Feb = (t5 - t4)
02 Mar = (t7 - t6)
You could use a date table to cross join to get the real start and end time in the day you want to split from the log time.
CREATE TABLE `dates` (
`date` date ,
`start_time` timestamp ,
`end_time` timestamp
);
INSERT INTO `dates` VALUES('20160228','2016-02-28 00:00:00', '2016-02-29 00:00:00');
INSERT INTO `dates` VALUES('20160229','2016-02-29 00:00:00', '2016-03-01 00:00:00');
INSERT INTO `dates` VALUES('20160301','2016-03-01 00:00:00', '2016-03-02 00:00:00');
INSERT INTO `dates` VALUES('20160302','2016-03-02 00:00:00', '2016-03-03 00:00:00');
INSERT INTO `dates` VALUES('20160303','2016-03-03 00:00:00', '2016-03-04 00:00:00');
INSERT INTO `dates` VALUES('20160304','2016-03-04 00:00:00', '2016-03-05 00:00:00');
INSERT INTO `dates` VALUES('20160305','2016-03-05 00:00:00', '2016-03-06 00:00:00');
INSERT INTO `dates` VALUES('20160306','2016-03-06 00:00:00', '2016-03-07 00:00:00');
SELECT
u.*,
d.date,
case when u.start_time<= d.start_time then d.start_time
else u.start_time end as `start_time_in_the_day`,
case when u.end_time> d.end_time then d.end_time
else u.end_time end as `end_time_in_the_day`
FROM useractivity u
INNER JOIN dates d
ON u.start_time< d.end_time
and u.end_time>= d.start_time
Then you just need to sum the hours between end_time_in_the_day and start_time_in_the_day.
SELECT
user_id,
date,
sum(TIMESTAMPDIFF(HOUR,start_time_in_the_day,end_time_in_the_day)) as `availableTime`
FROM(
SELECT
u.*,
d.date,
case when u.start_time<= d.start_time then d.start_time
else u.start_time end as `start_time_in_the_day`,
case when u.end_time> d.end_time then d.end_time
else u.end_time end as `end_time_in_the_day`
FROM useractivity u
INNER JOIN dates d
ON u.start_time< d.end_time
and u.end_time>= d.start_time) as t
group by user_id,date
My SqlFiddle here.
And I think use TIMESTAMPDIFF(SECOND... instead TIMESTAMPDIFF(HOUR... would be better.
I have created a query help of UNION ALL
SELECT sub_query.`date`, SUM(sub_query.available_time) FROM (
SELECT
DATE_FORMAT(start_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time))= 0,
TIMESTAMPDIFF(HOUR,start_time,end_time),0) AS available_time
FROM useractivity
UNION ALL
SELECT
DATE_FORMAT(start_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0,
TIMESTAMPDIFF(HOUR,start_time, date_add(date(start_time),interval 24 hour)),0) AS available_time
FROM useractivity
UNION ALL
SELECT
DATE_FORMAT(end_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0,
TIMESTAMPDIFF(HOUR,date(end_time), end_time) , 0) AS available_time
FROM useractivity
) AS sub_query
GROUP BY sub_query.`date`
UNION
SELECT SELECTed_date `date`, 24 FROM
(SELECT adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) SELECTed_date FROM
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) v
WHERE SELECTed_date between (SELECT min(date(start_time)) FROM useractivity) and (SELECT max(date(end_time)) FROM useractivity)
AND SELECTed_date NOT IN(
SELECT miss_date FROM (
SELECT date(start_time) AS miss_date FROM useractivity
UNION
SELECT date(end_time) AS miss_date FROM useractivity
) AS miss
) ORDER BY `date`;
SQLFiddle
I've modified #Vipin Jain's query to fulfill the requirement:
SELECT sub_query.`date`, SUM(sub_query.available_time) FROM (
SELECT
DATE_FORMAT(start_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time))= 0,
TIMESTAMPDIFF(HOUR,start_time,end_time),0) AS available_time
FROM useractivity
UNION ALL
SELECT
DATE_FORMAT(start_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0,
TIMESTAMPDIFF(HOUR,start_time, date_add(date(start_time),interval 24 hour)),0) AS available_time
FROM useractivity
UNION ALL
SELECT
DATE_FORMAT(end_time,"%Y-%m-%d") `date`,
IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0,
TIMESTAMPDIFF(HOUR,date(end_time), end_time) , 0) AS available_time
FROM useractivity
) AS sub_query
GROUP BY sub_query.`date`
UNION
SELECT SELECTed_date `date`, 24 FROM
(SELECT adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) SELECTed_date FROM
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) v
WHERE SELECTed_date between (SELECT min(date(start_time)) FROM useractivity) and (SELECT max(date(end_time)) FROM useractivity)
AND SELECTed_date NOT IN(
SELECT miss_date FROM (
SELECT date(start_time) AS miss_date FROM useractivity
UNION
SELECT date(end_time) AS miss_date FROM useractivity
) AS miss
)
AND EXISTS (SELECT 1 FROM useractivity WHERE SELECTed_date BETWEEN start_time AND end_time)
ORDER BY `date`;