I have this table which lists the delivery days available for an area. Also given are the order date and count of stores with the same order date and delivery day.
CREATE TABLE table1
(`order_date` date, `area` varchar(20), `delivery_days` varchar(20), `stores` int)
;
INSERT INTO table1
(`order_date`, `area`, `delivery_days`, `stores`)
VALUES
('2020-11-22', 'A', '1,3,5', 17),
('2020-11-25', 'A', '1,3,5', 4),
('2020-11-26', 'B', '2,4,6', 9),
('2020-11-28','B', '2,4,6', 6);
You can read the available delivery days as (1 for Mon, 2 for Tue, 3 for Wed... 6 for Sat).
With that said, if stores from area A ordered on 2020-11-22, Sunday, the nearest delivery day is the next day, 1 Monday. And if stores from the same area ordered on 2020-11-25, Wednesday, the nearest delivery day is 2 days from said date, which is 5 Friday.
order_date area delivery_days stores
2020-11-22 A 1,3,5 17
2020-11-25 A 1,3,5 4
2020-11-26 B 2,4,6 9
2020-11-28 B 2,4,6 6
What I would like to do is summarize this information into a table that looks like this-- where each column counts the number of stores that fall into the delivery category then gets the percentage relative to the total count of stores.
area total_stores next_day_delivery percent 2_day_delivery percent 3_day_delivery percent
A 21 17 81 4 19
B 15 9 60 6 40
How can I do this? Here's the sqlfiddle: http://sqlfiddle.com/#!9/784e1/1
Related
I have two tables in my schema. The first contains a list of recurring appointments - default_appointments. The second table is actual_appointments - these can be generated from the defaults or individually created so not linked to any default entry.
Example:
default_appointments
id
day_of_week
user_id
appointment_start_time
appointment_end_time
1
1
1
10:00:00
16:00:00
2
4
1
11:30:00
17:30:00
3
6
5
09:00:00
17:00:00
actual_appointments
id
default_appointment_id
user_id
appointment_start
appointment_end
1
1
1
2021-09-13 10:00:00
2021-09-13 16:00:00
2
NULL
1
2021-09-13 11:30:00
2021-09-13 13:30:00
3
6
5
2021-09-18 09:00:00
2021-09-18 17:00:00
I'm looking to calculate the total minutes that were scheduled in against the total that were actually created/generated. So ultimately I'd end up with a query result with this data:
user_id
appointment_date
total_planned_minutes
total_actual_minutes
1
2021-09-13
360
480
1
2021-09-16
360
0
5
2021-09-18
480
480
What would be the best approach here? Hopefully the above makes sense.
Edit
OK so the default_appointments table contains all appointments that are "standard" and are automatically generated. These are what appointments "should" happen every week. So e.g. ID 1, this appointment should occur between 10am and 4pm every Monday. ID 2 should occur between 11:30am an 5:30pm every Thursday.
The actual_appointments table contains a list of all of the appointments which did actually occur. Basically what happens is a default_appointment will automatically generate itself an instance in the actual_appointments table when initially set up. The corresponding default_appointment_id indicates that it links to a default and has not been changed - therefore the times on both will remain the same. The user is free to change these appointments that have been generated by a default, resulting in setting the default_appointment_id to NULL * - or -* can add new appointments unrelated to a default.
So, if on a Monday (day_of_week = 1) I should normally have a default appointment at 10am - 4pm, the total minutes I should have planned based on the defaults are 360 minutes, regardless of what's in the actual_appointments table, I should be planned for those 360 minutes every Monday without fail. If in the system I say - well actually, I didn't have an appointment from 10am - 4pm and instead change it to 10am - 2pm, actual_appointments table will then contain the actual time for the day, and the actual minutes appointed would be 240 minutes.
What I need is to group each of these by the date and user to understand how much time the user had planned for appointments in the default_appointments table vs how much they actually appointed.
Adjusted based on new detail in the question.
Note: I used day_of_week values compatible with default MySQL behavior, where Monday = 2.
The first CTE term (args) provides the search parameters, start date and number of days. The second CTE term (drange) calculates the dates in the range to allow generation of the scheduled appointments within that range.
allrows combines the scheduled and actual appointments via UNION to prepare for aggregation. There are other ways to set this up.
Finally, we aggregate the results per user_id and date.
The test case:
Working Test Case (Updated)
WITH RECURSIVE args (startdate, days) AS (
SELECT DATE('2021-09-13'), 7
)
, drange (adate, days) AS (
SELECT startdate, days-1 FROM args UNION ALL
SELECT adate + INTERVAL '1' DAY, days-1 FROM drange WHERE days > 0
)
, allrows AS (
SELECT da.user_id
, dr.adate
, ROUND(TIME_TO_SEC(TIMEDIFF(da.appointment_end_time, da.appointment_start_time))/60, 0) AS planned
, 0 AS actual
FROM drange AS dr
JOIN default_appointments AS da
ON da.day_of_week = dayofweek(adate)
UNION
SELECT user_id
, DATE(appointment_start) AS xdate
, 0 AS planned
, TIMESTAMPDIFF(MINUTE, appointment_start, appointment_end)
FROM drange AS dr
JOIN actual_appointments aa
ON DATE(appointment_start) = dr.adate
)
SELECT user_id, adate
, SUM(planned) AS planned
, SUM(actual) AS actual
FROM allrows
GROUP BY adate, user_id
;
Result:
+---------+------------+---------+--------+
| user_id | adate | planned | actual |
+---------+------------+---------+--------+
| 1 | 2021-09-13 | 360 | 480 |
| 1 | 2021-09-16 | 360 | 0 |
| 5 | 2021-09-18 | 480 | 480 |
+---------+------------+---------+--------+
My requirement is to compute the total months and then broken months separately between 2 dates (ie first date from table and second date is current date). If broken months total count is > 15 then account it as one month experience and if its les than 15 don't account that as 1 month experience.
Assume I have a date on table as 25/11/2018 and current date is 06/01/2019;
the full month in between is December, so 1 month experience; and broken months are November and January, so now I have to count the dates which is 6 days in Nov and 6 days in Jan, so 12 days and is <= (lte) 15 so total experience will be rounded to 1 month experience
I referred multiple questions related to calculating date difference in MYSQL from stackoverflow, but couldn't find any possible options. The inbuilt functions in MYSQL TIMESTAMPDIFF, TIMEDIFF, PERIOD_DIFF, DATE_DIFF are not giving my required result as their alogrithms are different from my calculation requirement.
Any clue on how to perform this calculation in MYSQL and arrive its result as part of the SQL statement will be helpful to me. Once this value is arrived, in the same SQL, that value will be validated to be within a given value range.
Including sample table structure & value:
table_name = "user"
id | name | join_date
---------------------
1| Sam | 25-11-2017
2| Moe | 03-04-2017
3| Tim | 04-07-2018
4| Sal | 30-01-2017
5| Joe | 13-08-2018
I wanted to find out the users from above table whose experience is calculated in months based on the aforementioned logic. If those months are between either of following ranges, then those users are fetched for further processing.
table_name: "allowed_exp_range"
starting_exp_months | end_exp_months
-------------------------------------
0 | 6
9 | 24
For ex: Sam's experience till date (10-12-2018) based on my calculation is 12+1 month = 13 months. Since 13 is between 9 & 24, Sam's record is one of the expected output.
I think this query will do what you want. It uses
(YEAR(CURDATE())*12+MONTH(CURDATE()))
- (YEAR(STR_TO_DATE(join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(join_date, '%d-%m-%Y'))) -
- 1
to get the number of whole months of experience for the user,
DAY(LAST_DAY(STR_TO_DATE(join_date, '%d-%m-%Y')))
- DAY(STR_TO_DATE(join_date, '%d-%m-%Y'))
+ 1
to get the number of days in the first month, and
DAY(CURDATE())
to get the number of days in the current month. The two day counts are summed and if the total is > 15, 1 is added to the number of whole months e.g.
SELECT id
, name
, (YEAR(CURDATE())*12+MONTH(CURDATE())) - (YEAR(STR_TO_DATE(join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(join_date, '%d-%m-%Y'))) - 1 -- whole months
+ CASE WHEN DAY(LAST_DAY(STR_TO_DATE(join_date, '%d-%m-%Y'))) - DAY(STR_TO_DATE(join_date, '%d-%m-%Y')) + 1 + DAY(CURDATE()) > 15 THEN 1 ELSE 0 END -- broken month
AS months
FROM user
We can use this expression as a JOIN condition between user and allowed_exp_range to find all users who have experience within a given range:
SELECT u.id
, u.name
, a.starting_exp_months
, a.end_exp_months
FROM user u
JOIN allowed_exp_range a
ON (YEAR(CURDATE())*12+MONTH(CURDATE())) - (YEAR(STR_TO_DATE(u.join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(u.join_date, '%d-%m-%Y'))) - 1
+ CASE WHEN DAY(LAST_DAY(STR_TO_DATE(u.join_date, '%d-%m-%Y'))) - DAY(STR_TO_DATE(u.join_date, '%d-%m-%Y')) + 1 + DAY(CURDATE()) > 15 THEN 1 ELSE 0 END
BETWEEN a.starting_exp_months AND a.end_exp_months
Output (for your sample data, includes all users as they all fit into one of the experience ranges):
id name starting_exp_months end_exp_months
1 Sam 9 24
2 Moe 9 24
3 Tim 0 6
4 Sal 9 24
5 Joe 0 6
I've created a small demo on dbfiddle which demonstrates the steps in arriving at the result.
I am working on a space booking system and is trying to find out if the space is available for a given date range. So I have 2 tables -
space_master
id space_address start_time end_time
space_availability
space_id days_of_week
Note -
(a) start_time and end_time are DATETIME fields in MYSQL
(b) days_of_week are numbers where Sunday is represented by 1 and so on
(c) one space can be available on multiple days (example follows)
space_master
id space_address start_time end_time
1 Florida 2012-03-18 10:21:00 2012-03-29 4:21:00
2 London 2012-04-21 09:00:00 2012-06-18 10:00:00
space_availability
space_id days_of_week
1 1
1 2
2 4
2 5
2 6
This means the first space (with id 1) is available between 2012-03-18 10:21:00 and 2012-03-29 4:21:00 but only on Sunday and Monday. Now I am trying to write a function that will take booking_start_time and booking_end_time (all DATETIME in MYSQL) as input then scan the available spaces table and return the availble bookings. Something like this -
getBooking(2012-03-19 10:21:00, 2012-03-19 15:21:00) - returns the space with id 1 (as 19th March 2012 was a Monday and hence available)
getBooking(2012-03-19 10:21:00, 2012-03-20 15:21:00) - returns nothing since 20th March is a Tuesday on which the space is not available.
Any idea how to do this? I was first trying to do this in a single query. But is that even possible?
EDIT: the sqlfiddle link follows -
http://sqlfiddle.com/#!9/8a6e1a
I have a table like this
id plan_id cancel_date paid_date
9 2 2015-08-05 2014-09-13
10 2 2015-09-08 2015-09-03
10 3 NULL 2015-09-10
11 3 NULL 2015-09-13
14 3 2015-09-28 2015-09-14
And I would like to select ids where there is a less than 30 days difference between cancel_date and paid_date (for a given plan), and they didn't acquired a new plan in less than 30 days.
In this case, this would mean returning id 14 only.
Update:
Whenever a user buy a new plan, we insert it to the table, with a different paid_date (paid_date is the date that the plan was acquired the first time).
I've got two tables:
Table Transmission
------------------------------
Id GroupID Amount Timestamp
1 1 5 2015-05-20 00:00:00
2 1 4 2015-05-19 00:00:00
3 1 10 2015-04-20 00:00:00
4 1 7 2015-04-19 00:00:00
5 1 9 2015-03-20 00:00:00
6 1 2 2015-03-18 00:00:00
Table Group
---------
Id DateCreated BillStart BillStop
1 2015-03-15 2015-05-15 2015-06-14
BillStart and BillStop have a trigger on them that runs daily, if the current Date is greater than BillStop, both BillStart/BillStop increase by a month (so these are effectively the current range we are looking at).
I already have a SQL view that can sum up a range of the transmission entries where the timestamp is between BillStart and BillStop, what I am looking to do is have another view that would effectively hold the sums of transmissions of past months. i.e. if a BillStart/BillStop goes from 2015-05-15 to 2015-06-14, then the view would know to group past transmissions from the ranges of 2015-4-15 to 20-15-14, 2015-3-15 to 2015-4-14, and so on, up until the last range containing the groups creation date.
Ideally the view would look like
-------------------------------------
GroupId Sum BillStart BillStop
1 9 2015-05-15 2015-06-14
1 17 2015-04-15 2015-05-14
1 11 2015-03-15 2015-04-14
Is there a better way to do this?
Another option I was considering was a table for GroupBillRanges that would entail:
GroupBillRange
--------------
Id GroupId BillStart BillStop
1 1 2015-03-15 2015-04-14
2 1 2015-04-15 2015-05-14
3 1 2015-05-15 2015-06-14
And this would be added when each bill range gets updated by the month. With this I would just be able to match all Transmission.GroupId to GroupDateRange.GroupId
Yes, you would need to create the "GroupBillRange" table. Otherwise you lose any record of what the Bill Start/Stop dates were on the old groups. (Sure, you could assume that they are always from the 15th to the 14th. But the one time that they are not, you will have problems.) Once you do that, a query like the following should give you what you're looking for, I believe. (Oh, and you have this question tagged as both SQL Server and MySQL. So I created this script on SQL Server 2008 r2.)
SET NOCOUNT ON;
DECLARE #Transmission TABLE (
ID int,
GroupID int,
Amount int,
TransmissionDate SmallDateTime );
INSERT #Transmission VALUES (1, 1, 5,'2015/05/20');
INSERT #Transmission VALUES (2, 1, 4,'2015/05/19');
INSERT #Transmission VALUES (3, 1,10,'2015/04/20');
INSERT #Transmission VALUES (4, 1, 7,'2015/04/19');
INSERT #Transmission VALUES (5, 1, 9,'2015/03/20');
INSERT #Transmission VALUES (6, 1, 2,'2015/03/18');
DECLARE #TableGroupRange TABLE (
ID int,
GroupID int,
BillStart SmallDateTime,
BillStop SmallDateTime );
INSERT #TableGroupRange VALUES (1, 1, '2015/03/15', '2015/04/14' )
INSERT #TableGroupRange VALUES (2, 1, '2015/04/15', '2015/05/14' )
INSERT #TableGroupRange VALUES (3, 1, '2015/05/15', '2015/06/14' )
SET NOCOUNT OFF;
SELECT TG.GroupID, SUM(T.Amount) as SumAmount, TG.BillStart, TG.BillStop
FROM #TableGroupRange TG
LEFT JOIN #Transmission T ON T.GroupID = TG.GroupID AND T.TransmissionDate BETWEEN TG.BillStart AND TG.BillStop
GROUP BY TG.GroupID, TG.BillStart, TG.BillStop
ORDER BY TG.GroupID, TG.BillStart, TG.BillStop