count by hours in between with start and end time data - mysql

In table, data is in Timestamp format, but I shared it in Time(start_at), Time(end_at) format.
Table structure:
id, start_at, end_at
1, 03:00:00, 06:00:00
2, 02:00:00, 05:00:00
3, 01:00:00, 08:00:00
4, 08:00:00, 13:00:00
5, 09:00:00, 21:00:00
6, 13:00:00, 16:00:00
6, 15:00:00, 19:00:00
For result we need to count ids which were active in between the start_at, end_at time.
hours, count
0, 0
1, 1
2, 2
3, 3
4, 3
5, 2
6, 1
7, 1
8, 1
9, 2
10, 2
11, 2
12, 2
13, 3
14, 2
15, 3
16, 2
17, 2
18, 2
19, 1
20, 1
21, 0
22, 0
23, 0

Either
WITH RECURSIVE
cte AS (
SELECT 0 `hour`
UNION ALL
SELECT `hour` + 1 FROM cte WHERE `hour` < 23
)
SELECT cte.`hour`, COUNT(test.id) `count`
FROM cte
LEFT JOIN test ON cte.`hour` >= HOUR(test.start_at)
AND cte.`hour` < HOUR(test.end_at)
GROUP BY 1
ORDER BY 1;
or
WITH RECURSIVE
cte AS (
SELECT CAST('00:00:00' AS TIME) `hour`
UNION ALL
SELECT `hour` + INTERVAL 1 HOUR FROM cte WHERE `hour` < '23:00:00'
)
SELECT cte.`hour`, COUNT(test.id) `count`
FROM cte
LEFT JOIN test ON cte.`hour` >= test.start_at
AND cte.`hour` < test.end_at
GROUP BY 1
ORDER BY 1;
The 1st query returns hours column in time format whereas the 2nd one returns numeric value for this column. Select the variant which is safe for you.
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5a77b6e3158be06c7a551cb7e64673de

Related

Mysql calculate sum of daily hours worked from clockin and clockout, when shift spans across midnight, or multiple days

I have a table with userID, clockin(1)/Clockout(0), dateTime for few employees. in/out shows when someone is on (1) or off(0) clock.
Job shift can span across midnight, as in punch in before midnight, and punchout in the morning. (Eg: Date 21st in table)
Shift can last more than 24 hours (hypothetically) (Eg : Date 24)
Punchin and punchout can happen multiple times within 24 hrs as well(Eg : Date 22).
I would like to get the sum of hours worked per day for any given user_id but within midnight to midnight, even though the shift might span across midnight. Timestamps are shown all with :30:00 for clarity. Only one user_id is shown, but this table can have info from multiple users, so user_id will be used in the where clause.
[id] [User_id] [Date_time] [in_out]
1 1 2022-08-20 09:30:00 1
2 1 2022-08-20 21:30:00 0
3 1 2022-08-21 20:30:00 1
4 1 2022-08-22 08:30:00 0
5 1 2022-08-22 09:30:00 1
6 1 2022-08-22 14:30:00 0
7 1 2022-08-23 12:30:00 1
8 1 2022-08-25 09:30:00 0
9 1 2022-08-25 12:30:00 1
So The desired query result would be something like below. The formatting does not matter. Total time per day in seconds or minutes or anything will work.
[Day] [hours_worked]
2022-08-20 12:00:00
2022-08-21 03:30:00
2022-08-22 13:00:00
2022-08-23 11:30:00
2022-08-24 24:00:00
2022-08-25 09:30:00
I started with the code from Get total hours worked in a day mysql This works well when punch-in happens before punch outs in a day, and does not handle midnights. Just trying to adapt to the specific case. Any help much appreciated.
To do this in MySQL 5.6, I can only think of a not so nice query, but let's create the data first
CREATE TABLE events
(`id` int, `User_id` int, `Date_time` datetime, `in_out` int);
INSERT INTO events
(`id`, `User_id`, `Date_time`, `in_out`)
VALUES
(1, 1, '2022-08-20 09:30:00', 1),
(2, 1, '2022-08-20 21:30:00', 0),
(3, 1, '2022-08-21 20:30:00', 1),
(4, 1, '2022-08-22 08:30:00', 0),
(5, 1, '2022-08-22 09:30:00', 1),
(6, 1, '2022-08-22 14:30:00', 0),
(7, 1, '2022-08-23 12:30:00', 1),
(8, 1, '2022-08-25 09:30:00', 0),
(9, 1, '2022-08-25 12:30:00', 1);
Based on https://stackoverflow.com/a/60173743/19657183, one can get the dates for every single day between the first and last event date. Then, you can JOIN the result with the events to figure out the ones which overlap. From that you can calculate the time differences and sum them up grouped by day:
SELECT User_id, start_of_day,
sec_to_time(sum(timestampdiff(SECOND, CAST(GREATEST(cast(start_of_day as datetime), prev_date_time) AS datetime),
CAST(LEAST(start_of_next_day, Date_time) AS datetime)))) AS diff
FROM (
SELECT * FROM (
SELECT id, User_id,
CASE WHEN #puid IS NULL or #puid <> User_id THEN NULL ELSE #pdt END AS prev_date_time, #pdt := Date_time AS Date_time,
CASE WHEN #puid IS NULL or #puid <> User_id THEN NULL ELSE #pio END AS prev_in_out, #pio := in_out in_out,
#puid := User_id
FROM (SELECT * FROM events ORDER BY User_id, Date_time) e,
(SELECT #pdt := '1970-01-01 00:00:00', #pio := NULL, #puid := NULL) init ) tr
WHERE prev_in_out = 1 and in_out = 0) event_ranges
JOIN (
SELECT #d start_of_day,
#d := date_add(#d, interval 1 day) start_of_next_day
FROM (SELECT #d := date(min(Date_time)) FROM events) min_d,
(SELECT x1.N + x10.N*10 + x100.N*100 + x1000.N*1000
FROM (SELECT 0 AS N 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) x1,
(SELECT 0 AS N 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) x10,
(SELECT 0 AS N 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) x100,
(SELECT 0 AS N 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) x1000
WHERE x1.N + x10.N*10 + x100.N*100 + x1000.N*1000 <= (SELECT date(max(Date_time)) - date(min(Date_time)) FROM events)) days_off) day_ranges
ON prev_date_time < start_of_next_day AND Date_time >= start_of_day
GROUP BY User_id,start_of_day;
I encountered a problem using sqlfiddle.com: it returned 00:00:00 if e.g. the time difference was exactly 24 hours (didn't matter if I used timediff or sec_to_time). I haven't seen this problem neither on MySQL 8 nor in db-fiddle.com (using MySQL 5.6). So, it might be, that you've to work around this problem.
EDIT: rewrote completely to solve the problem in MySQL 5.6 as requested by the OP.
EDIT #2: Updated the query to take sorting and grouping into account.
EDIT #3: changed initial assignment of the variables.

Matching all values in NOT IN clause

Is there a way to ensure all values in an NOT IN clause are matched?
Example:
I have a table DateValue with 2 columns Date and Value
ID======== DATE ============VALUE
1========2015-01-01=========== 12
2========2015-01-01=========== 13
3========2015-01-01=========== 15
4========2015-01-01=========== 16
5========2015-01-02========== 15
6========2015-01-04=========== 15
7========2015-01-05=========== 16
8========2015-01-06=========== 12
9========2015-01-06=========== 13
10========2015-01-06=========== 15
How I select all row where VALUE not = 12, 13 and 15 in same day
=> That mean I will return:
5========2015-01-02========== 15
6========2015-01-04=========== 15
7========2015-01-05=========== 16
In a first step you can find the dates when VALUE has all the values (12, 13 and 15):
SELECT `DATE`
FROM DateValue
WHERE `VALUE` IN (12, 13, 15)
GROUP BY `DATE`
HAVING COUNT(*) = 3
You can use this as a subquery to get the values you need:
SELECT *
FROM DateValue
WHERE `DATE` NOT IN (
SELECT `DATE`
FROM DateValue
WHERE `VALUE` IN (12, 13, 15)
GROUP BY `DATE`
HAVING COUNT(*) = 3
) a
I found the solution:
SELECT *
FROM `DateValue`
WHERE `Date` NOT IN (
SELECT DISTINCT `Date`
FROM `result`
WHERE VALUE IN (12,13,15)
)
Who have better solution (Don't use sub-query)?

Get result in single query rather then three different query

Table structure and sample data
CREATE TABLE IF NOT EXISTS `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(11) NOT NULL,
`restaurant_id` int(11) NOT NULL,
`bill_id` int(11) NOT NULL,
`source_id` int(1) NOT NULL,
`order_medium_id` int(11) NOT NULL,
`purchase_method` varchar(255) NOT NULL,
`totalamount` int(11) NOT NULL,
`delivery_charg` int(11) NOT NULL,
`discount` int(11) NOT NULL,
`vat` int(11) NOT NULL,
`total_price` int(11) NOT NULL DEFAULT '0',
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `customer_id` (`customer_id`),
KEY `source_id` (`source_id`),
KEY `restaurant_id` (`restaurant_id`),
KEY `bill_id` (`bill_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=22 ;
--
-- Dumping data for table `orders`
--
INSERT INTO `orders` (`id`, `customer_id`, `restaurant_id`, `bill_id`, `source_id`, `order_medium_id`, `purchase_method`, `totalamount`, `delivery_charg`, `discount`, `vat`, `total_price`, `date_created`) VALUES
(1, 1, 1, 1, 1, 0, 'cash', 1600, 0, 0, 0, 1600, '2016-05-29 13:05:40'),
(2, 2, 1, 2, 2, 1, 'cash', 1820, 0, 0, 0, 1820, '2016-06-27 07:21:25'),
(4, 1, 1, 3, 3, 0, 'cash', 1770, 0, 0, 0, 1770, '2016-05-31 13:05:56'),
(5, 3, 1, 4, 2, 1, 'cash', 1300, 0, 0, 0, 1300, '2016-06-27 07:21:31'),
(6, 1, 1, 5, 1, 0, 'cash', 950, 0, 0, 0, 950, '2016-06-02 13:06:15'),
(7, 1, 1, 6, 1, 0, 'cash', 1640, 0, 0, 0, 1640, '2016-06-03 13:06:24'),
(8, 1, 1, 7, 2, 2, 'cash', 1600, 0, 0, 0, 1600, '2016-06-27 07:21:36'),
(9, 1, 1, 8, 2, 2, 'cash', 1575, 0, 0, 0, 1575, '2016-06-27 07:21:40'),
(10, 1, 1, 9, 3, 0, 'cash', 1125, 0, 0, 0, 1125, '2016-06-06 13:06:48'),
(11, 1, 1, 10, 2, 3, 'cash', 1920, 0, 0, 0, 1920, '2016-06-27 07:21:51');
Requirement :
I want to segment records as per customer as following.
Get Rating on the basis of last purchase by customer
1. customers who ordered in last 2 week then give ratingflag 5
2. customers who ordered between 2 weeks to 4 week then give ratingflag 3
3. customers who ordered between 4 weeks to 8 week then give ratingflag 2
and so on.
Get Rating on the basis of number of order by customer
1. Customer who ordered more then 5 in a month then give rating 5
2. Customer who ordered less then 5 and more then in a month then 4 give rating 4
and so on.
Get Rating on the basis of total transaction by customer
1. Customer who ordered more then 5000 rs in a month then give rating 5
2. Customer who ordered less then 5000 rs and more then in a month then 4000 give rating 4
and so on.
Customer should be unique. We write three different query for getting records according to requirement.
I tried following . Is there any way to get result in single query. I would appreciate if you could help me with better approach of doing the same :
1.) Query for last purchase
select o.customer_id,
(case when max(date_created) >= date_sub(now(), interval 2 week) then 5
when max(date_created) >= date_sub(now(), interval 4 week) then 4
when max(date_created) >= date_sub(now(), interval 8 week) then 3
when max(date_created) >= date_sub(now(), interval 10 week) then 2
when max(date_created) >= date_sub(now(), interval 12 week) then 1
end) as rating
from orders o where o.restaurant_id = 1
group by o.customer_id;
Output
customer_id rating
1 5
2 5
5 5
2.) Query for number of order
select o.customer_id,
(case when count(bill_id) >= 6 then 5
when count(bill_id) >= 4 and count(bill_id) < 6 then 4
when count(bill_id) >= 3 and count(bill_id) < 4 then 3
when count(bill_id) >= 2 and count(bill_id) < 3 then 2
when count(bill_id) >= 1 then 1
end) as rating
from orders o where o.restaurant_id = 1
group by o.customer_id
Output
customer_id rating
1 5
2 1
5 1
3.) Query for total transaction by customer
select o.customer_id,
(case when sum(total_price) >= 5000 then 5
when sum(total_price) >= 3000 and sum(total_price) < 5000 then 4
when sum(total_price) >= 2000 and sum(total_price) < 3000 then 3
when sum(total_price) >= 1000 and sum(total_price) < 2000 then 2
when sum(total_price) < 1000 then 1
end) as rating
from orders o where o.restaurant_id = 1
group by o.customer_id
Output
customer_id rating
1 5
2 2
5 2
Expected Output
customer_id R1 R2 R3
1 5 5 5
2 5 1 2
3 5 1 2
select o.customer_id,
(case when max(date_created) >= date_sub(now(), interval 2 week) then 5
when max(date_created) >= date_sub(now(), interval 4 week) then 4
when max(date_created) >= date_sub(now(), interval 8 week) then 3
when max(date_created) >= date_sub(now(), interval 10 week) then 2
when max(date_created) >= date_sub(now(), interval 12 week) then 1
end) as rating1,
(case when count(bill_id) >= 6 then 5
when count(bill_id) >= 4 and count(bill_id) < 6 then 4
when count(bill_id) >= 3 and count(bill_id) < 4 then 3
when count(bill_id) >= 2 and count(bill_id) < 3 then 2
when count(bill_id) >= 1 then 1
end) as rating2,
(case when sum(total_price) >= 5000 then 5
when sum(total_price) >= 3000 and sum(total_price) < 5000 then 4
when sum(total_price) >= 2000 and sum(total_price) < 3000 then 3
when sum(total_price) >= 1000 and sum(total_price) < 2000 then 2
when sum(total_price) < 1000 then 1
end) as rating3
from orders o where o.restaurant_id = 1
group by o.customer_id
Try this. It is faster than above answer. No need to use joins. Check this http://sqlfiddle.com/#!9/192b0/5
You can use join on these different resultsets of your queries. http://sqlfiddle.com/#!9/192b0/3
SELECT * FROM (
select o.customer_id,
(case when max(date_created) >= date_sub(now(), interval 2 week) then 5
when max(date_created) >= date_sub(now(), interval 4 week) then 4
when max(date_created) >= date_sub(now(), interval 8 week) then 3
when max(date_created) >= date_sub(now(), interval 10 week) then 2
when max(date_created) >= date_sub(now(), interval 12 week) then 1
end) as R1
from orders o where o.restaurant_id = 1
group by o.customer_id) AS lastPurchase
LEFT JOIN
(
select o.customer_id,
(case when count(bill_id) >= 6 then 5
when count(bill_id) >= 4 and count(bill_id) < 6 then 4
when count(bill_id) >= 3 and count(bill_id) < 4 then 3
when count(bill_id) >= 2 and count(bill_id) < 3 then 2
when count(bill_id) >= 1 then 1
end) as R2
from orders o where o.restaurant_id = 1
group by o.customer_id
) AS orderQuery USING(customer_id)
LEFT JOIN
(
select o.customer_id,
(case when sum(total_price) >= 5000 then 5
when sum(total_price) >= 3000 and sum(total_price) < 5000 then 4
when sum(total_price) >= 2000 and sum(total_price) < 3000 then 3
when sum(total_price) >= 1000 and sum(total_price) < 2000 then 2
when sum(total_price) < 1000 then 1
end) as R3
from orders o where o.restaurant_id = 1
group by o.customer_id
) AS totalTransactions USING(customer_id)

MySQL statement - select max from a group

hi i have the following table, and I want to select the max(count(*)) of plugged for each month. sqlfiddle.com/#!2/13036/1
select * from broadcast
profile, plugged, company, tstamp
1, 2, 1, 2013-10-01 08:20:00
1, 3, 1, 2013-10-01 08:20:00
2, 1, 1, 2013-10-01 08:20:00
2, 3, 1, 2013-10-01 08:20:00
3, 1, 1, 2013-10-01 08:20:00
3, 1, 1, 2013-09-01 08:20:00
so if I do something like the following:
select plugged,
count(*),
extract(month from tstamp),
extract(year from tstamp)
from broadcast
where company=1
group by plugged,
extract(month from tstamp),
extract(year from tstamp)
order by count(*) desc;
output:
plugged, count(*), extract(month from tstamp), extract(year from tstamp)
3, 2, 10, 2013
1, 2, 10, 2013
2, 1, 10, 2013
1, 1, 9, 2013
desired output:
plugged, count(*), extract(month from tstamp), extract(year from tstamp)
3, 2, 10, 2013
1, 2, 10, 2013
1, 1, 9, 2013
which is right... but I only want the max(count(*)) (for example first row only in this case). There may be scenarios where there are 2 rows with the max count, but for each MONTH/YEAR i only want to return the max count row(s)...do I need an inner select statement or something?
try this
select plugged, max(counts) counts, month , year
from
(select plugged ,count(*) as counts ,extract(month from tstamp) month , extract(year from tstamp) year from broadcast where company=1
group by plugged,month ,year order by counts desc ) as x ;

How to get the unique set of count for rows with different values for a certain hour

select id, hour, count from stats;
1, 0, 2
2, 0, 20
3, 1, 10
4, 1, 20
5, 2, 10
5, 2, 30
I would want the output (hour, count) to render as
0, 22
1, 30
2, 40
How do I perform a unique count for the hour interval?
select hour, sum(count)
from stats
group by hour
You can make use of the group by clause:
SELECT hour, count(id)
GROUP BY hour;
So for this dataset:
1, 0, 2
2, 0, 20
3, 1, 10
4, 1, 20
5, 2, 10
5, 2, 30
you will get
0, 2
1, 2
2, 2
If you want to get the sum for that hour use SUM()
SELECT hour, sum(`count`)
GROUP BY hour;
NOTE: Try not to use the word count as a field name because it is also a key word in mysql
You need to use SUM() function to achieve that:
SELECT hour, sum(`count`) AS `TotalCOUNT`
FROM stats
GROUP BY hour
See this SQLFiddle
I would like to go for
SELECT hour,sum(count) FROM stats
GROUP by hour;
Here is the reference for group by clause
http://www.techonthenet.com/sql/group_by.php