Duplicate the result set of a MySQL Query - mysql

I want to average out values of a specific query based on the weekday of the date in the table, and duplicate it for the other days. I know that this explanation was confusing, see the example below.
Eg:- Table Structure -
ID, Value, Date
Expected Result-
Avg(value), Weekname(day)
12.23 , Mon
12.23 , Tue
12.23 , Wed
12.23 , Thu
12.23 , Fri
34.56 , Sat
56.34 , Sun
Ie, values for Monday - Friday is the same.
Current Result
Avg(value), Weekname(day)
12.23 , Mon
34.56 , Sat
56.34 , Sun
The query looks like this -
select avg(value)
,daynameofweek(date)
from table
group by (CASE WHEN DAYOFWEEK(DATE) NOT BETWEEN 2 and 6 THEN DAYOFWEEK(DATE) END)
Thanks for the help, in Advance.
Edit - Adding example data set
ID Value Date
1 2.500 2017-01-01
2 0.674 2017-01-02
3 2.743 2017-01-03
4 1.460 2017-01-04
5 1.457 2017-01-05
6 1.791 2017-01-06
7 1.896 2017-01-07
8 2.015 2017-01-08
9 2.224 2017-01-09
10 1.635 2017-01-10
11 1.100 2017-01-11
12 0.441 2017-01-12
13 0.809 2017-01-13
14 1.508 2017-01-14
Expected Result
Avg(Value) daynameofweek(date)
1.625 Monday
1.625 Tuesday
1.625 Wednesday
1.625 Thursday
1.625 Friday
1.300 Saturday
1.702 Sunday

You are showing that you end up with 'MON' for Monday to Friday, but this is arbitrarily chosen by the DBMS and you could just as well end up with, say, 'WED', because this day too is in the grouped-by range. So start with getting a determinate value here. Then join with a made-up day table:
select
days.dayname,
data.avg_value
from
(
select
case when dayofweek(date) between 2 and 6 then 2 else dayofweek(date) end as day
avg(value) as avg_value
from table
group by case when dayofweek(date) between 2 and 6 then 2 else dayofweek(date) end
) data
join
(
select 1 as day, 'SUN' as dayname, 7 as sortkey union all
select 2 as day, 'MON' as dayname, 1 as sortkey union all
select 2 as day, 'TUE' as dayname, 2 as sortkey union all
select 2 as day, 'WED' as dayname, 3 as sortkey union all
select 2 as day, 'THU' as dayname, 4 as sortkey union all
select 2 as day, 'FRI' as dayname, 5 as sortkey union all
select 7 as day, 'SAT' as dayname, 6 as sortkey
) days on days.day = data.day
order by data.sortkey;
(If I remember correctly, MySQL allows to group by the alias name (group by day), which is not standard compliant. Use this, if you like it better.)

Related

MySQL Select data from table with dates between in reverse of interval 7 days

I have a MySQL requirement to select data from a table based on a start date and end date and group it by weekly also selecting the data in reverse order by date. Assume that, I have chosen the start date as 1st November and the end date as 04 December. Now, I would like to fetch the data as 04 December to 28 November, 27 November to 20 November, 19 November to 12 November and so on and sum the value count for that week.
Given an example table,
id
value
created_at
1
10
2021-10-11
2
13
2021-10-17
3
11
2021-10-25
4
8
2021-11-01
5
1
2021-11-10
6
4
2021-11-18
7
34
2021-11-25
8
17
2021-12-04
Now the result should be like 2021-12-04 to 2021-11-28 as one week, following the same in reverse order and summing the column value data for that week. I have tried in the query to add the interval of 7 days after the end date but it didn't work.
SELECT count(value) AS total, MIN(R.created_at)
FROM data_table AS D
WHERE D.created_at BETWEEN '2021-11-01' AND '2021-12-04' - INTERVAL 7 DAY ORDER BY D.created_at;
And it's also possible to have the last week may have lesser than 7 days.
Expected output:
end_interval
start_interval
total
2021-12-04
2021-11-27
17
2021-11-27
2021-11-20
34
2021-11-20
2021-11-13
4
2021-11-13
2021-11-06
1
2021-11-06
2021-10-30
8
2021-10-30
2021-10-25
11
Note that the last week is only 5 days depending upon the selected from and end dates.
One option to address this problem is to
generate a calendar of all your intervals, beginning from last date till first date, with a split of your choice, using a recursive query
joining back the calendar with the original table
capping start_interval at your start_date value
aggregating values for each interval
You can have three variables to be set, to customize your date intervals and position:
SET #start_date = DATE('2021-10-25');
SET #end_date = DATE('2021-12-04');
SET #interval_days = 7;
Then use the following query, as already described:
WITH RECURSIVE cte AS (
SELECT #end_date AS end_interval,
DATE_SUB(#end_date, INTERVAL #interval_days DAY) AS start_interval
UNION ALL
SELECT start_interval AS end_interval,
GREATEST(DATE(#start_date), DATE_SUB(start_interval, INTERVAL #interval_days DAY)) AS start_interval
FROM cte
WHERE start_interval > #start_date
)
SELECT end_interval, start_interval, SUM(_value) AS total
FROM cte
LEFT JOIN tab
ON tab.created_at BETWEEN start_interval AND end_interval
GROUP BY end_interval, start_interval
Check the demo here.

MySQL query to find hour of the day when the number of housing society visits is highest

Housing Society Visit Data
Id
Contact
Entry_time
Exit_time
Duration of Stay
1
8080808080
26/07/2021 08:00:05
26/07/2021 08:23:06
181
2
9692596925
26/07/2021 08:12:49
26/07/2021 08:14:44
115
3
7099270992
26/07/2021 11:02:49
26/07/2021 11:14:44
715
4
8900289002
26/07/2021 16:12:49
26/07/2021 16:14:44
115
5
9089590895
26/07/2021 15:12:49
26/07/2021 15:14:44
115
6
8765087650
26/07/2021 19:12:49
26/07/2021 19:14:44
115
7
7862178621
26/07/2021 18:12:49
26/07/2021 18:14:44
115
Visit data is available for many years and can contain millions of rows, so the solution should have a low time complexity.
Expected output: 8-9 AM (since highest number of visits (2) are made during that hour).
As I read the question this would help you very much on the way.
There are a couple of things you can do to improve this tho:
Create separate table with hours to avoid the unions (or do it with numbers table);
Store times separatly, the TIME() method slows the query.
.
SELECT
hours.`hour`,
COUNT(occurrence.perfId) AS occurrences
FROM (
SELECT '00:00' AS `hour`
UNION SELECT '01:00'
UNION SELECT '02:00'
UNION SELECT '03:00'
UNION SELECT '04:00'
UNION SELECT '05:00'
UNION SELECT '06:00'
UNION SELECT '07:00'
UNION SELECT '08:00'
UNION SELECT '09:00'
UNION SELECT '10:00'
UNION SELECT '11:00'
UNION SELECT '12:00'
UNION SELECT '13:00'
UNION SELECT '14:00'
UNION SELECT '15:00'
UNION SELECT '16:00'
UNION SELECT '17:00'
UNION SELECT '18:00'
UNION SELECT '19:00'
UNION SELECT '20:00'
UNION SELECT '21:00'
UNION SELECT '22:00'
UNION SELECT '23:00'
) hours
LEFT JOIN YOUR_TABLE occurrence ON hours.`hour` BETWEEN TIME(occurrence.Entry_time) AND TIME(occurrence.Exit_time)
GROUP BY hours.`hour`
ORDER BY occurrences DESC
Would produce (Data from a test database):
[hour] [occurrences]
10:00 73554
11:00 67492
09:00 65679
08:00 63886
13:00 63565
12:00 62525
07:00 61500
14:00 53095
15:00 49017
16:00 41955
17:00 31991
18:00 21251
06:00 17591
19:00 13717
20:00 8532
21:00 4421
22:00 2050
23:00 818
05:00 796
04:00 561
01:00 175
03:00 123
02:00 120
00:00 23
To get the hour only just wrap with a query and select the first:
SELECT hour FROM (
# Paste query from above here...
) hourData
LIMIT 1;
I was able to get the required output using the following query:
SELECT HOUR(Entry_time) as hr FROM table_name GROUP BY HOUR(Entry_time) ORDER BY COUNT(*) DESC LIMIT 1;
Thanks to the solution by #nicholascarey: SQL to determine peak volume and hour for all days

Combine several rows with timestamps into one row with start and end timestamps

I'm trying to pull some SQL reports from an inventory system backend.
The table I'm trying to report on has an 'action' column (for tracking check in or out) and a 'timestamp' column.
I'm trying to reformat those into rows for each user/asset combo that has start and end times.
Sample data:
current:
row_id** user asset out_or_in timestamp
1 a 4 out 8:40
2 c 7 out 9:20
3 a 4 in 9:55
4 b 4 out 10:00
5 c 6 out 12:15
6 a 3 out 12:30
7 b 4 in 13:10
8 a 3 in 14:05
9 b 4 out 15:00
10 b 4 in 16:20
desired
row id** user asset out_time in_time
1 a 4 8:40 9:55
2 c 7 9:20
3 b 4 10:00 13:10
4 c 6 12:15
5 a 3 12:30 14:05
6 b 4 15:00 16:20
** not necessary, just included for table correctness
It gets more complicated by the fact that each user-asset pair can be repeated and would need to show as two rows.
Thoughts?
MySql 8.0 using row_number() window function to match out/in
select usr,asset
, max(case event when 'out' then ts end) out_time
, max(case event when 'in' then ts end) in_time
from (
select f.*,
-- first 'out' matches first 'in' and so on
row_number() over(partition by usr, asset, event order by ts) as grp
from foo f
) t
group by usr, asset, grp
order by out_time
Db fiddle
Here's a query for MySql 8 that makes use of a MIN OVER to calculate the in_time.
SELECT q.user, q.asset, q.out_time, q.in_time
FROM
(
SELECT t.user, t.asset, t.out_or_in,
CASE WHEN t.out_or_in = 'out' THEN t.`timestamp` END AS out_time,
MIN(CASE WHEN t.out_or_in = 'in' THEN t.`timestamp` END) OVER (PARTITION BY t.user, t.asset ORDER BY t.`timestamp` ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) AS in_time
FROM YourTable t
) q
WHERE q.out_time IS NOT NULL
ORDER BY q.out_time;
Test on db<>fiddle here
Result:
user asset out_time in_time
a 4 08:40:00 09:55:00
c 7 09:20:00
b 4 10:00:00 13:10:00
c 6 12:15:00
a 4 12:30:00 14:05:00

Query for valid records between two days and count by each day of latest month

The table ShopOrder's columns include:
id shopid starttime endtime
1 123 2018-04-27 2018-04-28
2 234 2018-04-23 2018-04-30
3 189 2018-05-01 2018-05-30
4 321 2018-05-01 2018-05-29
I wan't to query for valid shop counts between two days and count by each day of latest month,the valid shop counts means the starttime<= $curDate <= endtime,and curDate is a variable of the each day of the leatest month.
Today is 2018-04-27,so the query result should be:
day count
2018-04-27 2
2018-04-26 1
2018-04-25 1
2018-04-24 1
2018-04-23 1
2018-04-22 0
2018-04-21 0
……………………………………
2018-03-26 0
how can i achieve this use MySQL?
Converting between start and end dates and a range of individual dates is a challenge in SQL because you probably don't have a table which contains a row for each day in the current month to join on.
You can fill a table with dates using logic from one of the answers in How to populate a table with a range of dates?.
Or, since the range you want to produce is quite short, you can just create it manually in your query. It isn't completely clear what you mean by "the latest month" since your example ranges from 26 April to 27 March, but if the last 30 days is reasonable enough, you can use UNION to create this list.
Comparing date ranges discusses how to test whether a date is in a particular range or not, so putting the two things together gives you something like
SELECT
DATE_SUB(DATE(NOW()), INTERVAL days_ago.days DAY) day,
COUNT(ShopOrder.id) count
FROM
(SELECT 0 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)
AS days_ago
LEFT JOIN ShopOrder
ON DATE_SUB(DATE(NOW()), INTERVAL days_ago.days DAY) <= ShopOrder.endtime
AND DATE_SUB(DATE(NOW()), INTERVAL days_ago.days DAY) >= ShopOrder.starttime
GROUP BY days_ago.days;

MySQL find the mode of multiple subsets

I have a database like this:
custNum date purchase dayOfWeek
333 2001-01-01 23.23 1
333 2001-03-04 34.56 5
345 2008-02-02 22.55 3
345 2008-04-05 12.35 6
... ... ... ...
I'm trying to get the mode (most frequently occuring value) for the dayOfWeek column for each customer. Basically it would be the day of the week each customer shops the most on. Like:
custNum max(count(dayofweek(date)))
333 5
345 3
356 2
388 7
... ...
Any help would be great thanks.
select custNum, dayOfWeek
from tableName t
group by custNum, dayOfWeek
having dayOfWeek = (
select dayOfWeek
from tableName
where custNum = t.custNum
group by dayOfWeek
order by count(*) desc, dayOfWeek
limit 1
)