CROSS JOIN of list of dates and another table - mysql

I have a query for getting data from a certain table by date range and grouping by week. My CROSS JOIN intends to fill in a default value for each week where there are no results for the date range.
I can then execute this query.
SELECT
SUM(invoice.amount) AS "invoice.amount",
CONCAT(DATE_FORMAT(invoice.updated_at, '%b %d'), ' - ', DATE_FORMAT(DATE_ADD(invoice.updated_at, INTERVAL 7 DAY), '%b %d')) AS "invoice.updated_at"
FROM invoice
CROSS JOIN (
SELECT selected_date
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 '2018-01-01' AND '2018-01-31'
GROUP BY selected_date, YEAR(selected_date), WEEK(selected_date)
) calendar
WHERE invoice.updated_at >= '2018-01-01'
AND invoice.updated_at <= '2018-01-31'
AND invoice.status = "PAID"
GROUP BY calendar.selected_date, invoice.id, invoice.amount, YEAR(invoice.updated_at), WEEK(invoice.updated_at)
Assume I have these records in the database:
+----+------------+------------+------------+
| id | amount | status | updated_at |
+----+------------+------------+------------+
| 1 | 1000 | PAID | 2018-01-01 |
| 2 | 2000 | PAID | 2018-01-01 |
| 3 | 100 | PAID | 2018-01-07 |
| 4 | 50 | PAID | 2018-01-11 |
+----+------------+------------+------------+
I expect to see these results, one record for every week of January:
+--------+-------------------+
| amount | updated_at |
+--------+-------------------+
| 3100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 0 | Jan 16 - Jan 22 |
| 0 | Jan 23 - Jan 30 |
| 0 | Jan 31 - Jan 31 |
+--------+-------------------+
However, I get like 50 of these random duplicated results, the least of which contains the joined filler weeks since there are no 0 amounts:
+--------+----------------+
| amount | updated_at |
+--------+----------------+
| 1000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 1000 | Jan 1 - Jan 7 |
| 1000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| ... | ... |
| ... | ... |
| ... | ... |
+--------+----------------+
What gives?

GROUP BY calendar.selected_date, invoice.id, invoice.amount,
You have too many columns specified in the group by, particularly invoice.amount
Instead, try with:
GROUP BY
CONCAT(DATE_FORMAT(invoice.updated_at, '%b %d'), ' - ', DATE_FORMAT(DATE_ADD(invoice.updated_at, INTERVAL 7 DAY), '%b %d'))
I cannot be sure, but I think your date range needs adjustment as well, the following will guarantee you get everything for January 2018:
WHERE invoice.updated_at >= '2018-01-01'
AND invoice.updated_at < '2018-02-01'

Related

MySQL record between fixed date range

I have an small application which was build with CodeIgniter 3 and need to perform a report which will be converted to Chart.js. The report should be in yearly basis but at given specific date every month. The requirement are all data count must be from 4th to 3rd monthly. Like this:
For example January Report would be from 4th January to 3rd February, 4th February to 3rd March,... and so on.
I have created a MySQL query but I'm stuck on how to get the date too date. My Query are as follows:
SELECT DATE_FORMAT(odd_date_created, '%Y') as 'year',
DATE_FORMAT(odd_date_created, '%m') as 'month',
COUNT(odd_id) as 'total', status
FROM odd_data
WHERE status = $id and
GROUP BY DATE_FORMAT(odd_date_created, '%Y%m'), status
I'm new to MySQl. Could somebody help me on this. I'm stuck where should I put the date to date query.
Firstly I want to caution you not to use "between" with the following when you come to join your data, use this method instead data.date >= r.period_start_dt and data.date < r.period_end_dt
Secondly I am assuming your data does have dates or timestamps and that will fall between the calculated ranges that follow:
set #year :=2017;
select
*
from (
select
start_dt + INTERVAL m.n MONTH period_start_dt
, start_dt + INTERVAL m.n + 1 MONTH period_end_dt
from (
select str_to_date(concat(#year,'-01-04'),'%Y-%m-%d') start_dt ) seed
cross join (select 0 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 union all
select 10 union all
select 11
) m
) r
## LEFT JOIN YOUR DATA
## ON data.date >= r.period_start_dt and data.date < r.period_end_dt
Example ranges: (produce you own at this demo: http://rextester.com/CHTKSA95303 )
nb dd.mm.yyyy (.de format)
+----+---------------------+---------------------+
| | period_start_dt | period_end_dt |
+----+---------------------+---------------------+
| 1 | 04.01.2017 00:00:00 | 04.02.2017 00:00:00 |
| 2 | 04.02.2017 00:00:00 | 04.03.2017 00:00:00 |
| 3 | 04.03.2017 00:00:00 | 04.04.2017 00:00:00 |
| 4 | 04.04.2017 00:00:00 | 04.05.2017 00:00:00 |
| 5 | 04.05.2017 00:00:00 | 04.06.2017 00:00:00 |
| 6 | 04.06.2017 00:00:00 | 04.07.2017 00:00:00 |
| 7 | 04.07.2017 00:00:00 | 04.08.2017 00:00:00 |
| 8 | 04.08.2017 00:00:00 | 04.09.2017 00:00:00 |
| 9 | 04.09.2017 00:00:00 | 04.10.2017 00:00:00 |
| 10 | 04.10.2017 00:00:00 | 04.11.2017 00:00:00 |
| 11 | 04.11.2017 00:00:00 | 04.12.2017 00:00:00 |
| 12 | 04.12.2017 00:00:00 | 04.01.2018 00:00:00 |
+----+---------------------+---------------------+
Given the specification, I think I would tempted to cheat it... subtract 3 days from the date. Doing that, Jan 4 backs up to Jan 1, Feb 3 backs up to Jan 31... so those all end up as January.
SELECT DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%Y') AS `year`
, DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%m') AS `month`
, ...
FROM ...
GROUP
BY DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%Y')
, DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%m')
This falls apart if there's oddball ranges... if it's not always the 4th and 3rd.

SQL Track Open Appointment

I am a beginner of SQL, I am working on a query that generates the Open Appointment Time Slot for each Provider (Employee). Can anyone help to with the following query? Thank you very much!
Schedule table sample Data:
ScheduleNum | ProvNum (Provider) | SchedDate | StartTime | EndTime
1 | 3 | 7/1/2017 | 8:00AM | 9:00 AM
2 | 3 | 7/1/2017 | 10:00AM | 11:00 AM
3 | 4 | 7/1/2017 | 8:00AM | 9:00 AM
4 | 4 | 7/1/2017 | 12:00PM | 1:00 PM
5 | 4 | 7/1/2017 | 3:00PM | 4:00 PM
Open Appointment Query Table Expected Result:
ProvNum (Provider) | SchedDate | avail_start_dt_time| avail_end_dt_time
3 | 7/1/2017 | 9:00 AM | 10:00 AM
4 | 7/1/2017 | 9:00 AM | 12:00 PM
4 | 7/1/2017 | 1:00 PM | 3:00 PM
Query that I am working on.
SELECT
avail.schedulenum
, avail.provnum
, avail.start_at as avail_start_dt_time
, avail.end_at as avail_end_dt_time
, avail.on_dt as just_date_no_time
, avail.starttime as prov_start_time
, avail.endtime as prov_end_time
FROM (
SELECT
dts.num, dts.on_dt, dts.start_at, dts.end_at, s.provnum, s.starttime, s.stoptime
FROM (
/* generates 1000, 10 minute "slots", starting at a variable date */
SELECT
schedulenum + 1 as num
, DATE_ADD(INTERVAL (slots.num * 10) MINUTE) start_at
, DATE_ADD(INTERVAL ((slots.num+ 1) * 10) MINUTE) end_at
FROM (
/* generates 1000 rows 0 to 999 */
SELECT hundreds.digit * 100 + tens.digit * 10 + ones.digit AS num
FROM (
SELECT 0 AS digit 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
) ones
CROSS JOIN (
SELECT 0 AS digit 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
) tens
CROSS JOIN (
SELECT 0 AS digit 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
) hundreds
) slots
) dts
INNER JOIN schedule s ON s.starttime < dts.end_at AND s.stoptime > dts.start_at
) avail
WHERE schedulenum IS NULL

sql get all dates between two table dates

another SQL challenge!
I want to write a MySQL query that gets all days between two dates in one record.
opening_times
id | begin | end
1 | 10:00:00 | 17:00:00
2 | 10:00:00 | 18:00:00
3 | 10:00:00 | 19:00:00
opening_periods
id | opening_time_id | begin | end
1 | 3 | 2016-03-26 | 2016-03-28
2 | 2 | 2016-03-29 | 2016-04-01
3 | 1 | 2016-04-02 | 2016-04-03
I want to have this output:
date | begin | end
2016-03-26 | 10:00:00 | 19:00:00
2016-03-27 | 10:00:00 | 19:00:00
2016-03-28 | 10:00:00 | 19:00:00
2016-03-29 | 10:00:00 | 18:00:00
2016-03-30 | 10:00:00 | 18:00:00
2016-03-31 | 10:00:00 | 18:00:00
2016-04-01 | 10:00:00 | 18:00:00
2016-04-02 | 10:00:00 | 17:00:00
2016-04-03 | 10:00:00 | 17:00:00
Should I use a subquery for this?
Thx for pointing me in the right direction!
You can solve it in mysql with a complex query.
First you need to build a subquery that will generate an integer sequence, like in this answer:
SELECT #row := #row + 1 as rown FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(SELECT #row:=-1) x
This will be used to generate so many rows for each entry as many days you have in your dates interval.
SELECT DATEDIFF(`end`,`begin`) as number_of_days FROM `opening_periods`
And all put together will look like this:
SELECT DATE_ADD( o.`begin`, INTERVAL days day) as date_field, t.begin, t.end
FROM `opening_periods` o INNER JOIN (
SELECT id, rown as days
FROM `opening_periods`,
(SELECT #row := #row + 1 as rown FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(SELECT #row:=-1) x) numbers_table
WHERE rown <= DATEDIFF(`end`,`begin`)) r
ON o.id = r.id
INNER JOIN `opening_times` t ON o.`opening_time_id` = t.id
ORDER BY o.id
Here is a fiddle: http://rextester.com/AKDRI84101

SQL/Mysql query available dates and times in database

I need some help querying my calendar/dates table
Scenario: I have a "calendar" table with dates and times (see below), users will set their available dates, usually day by day with available time slots for each day. So my table looks like this:
+------+------------+---------------------+---------------------+
| ID | user_id | start_date | end_date |
+------+------------+---------------------+---------------------+
| 1 | 1 | 2016-09-01 08:00:00 | 2016-09-01 16:00:00 |
| 2 | 1 | 2016-09-03 00:00:00 | 2016-09-03 23:59:59 |
| 3 | 1 | 2016-09-04 00:00:00 | 2016-09-04 16:00:00 |
| 4 | 1 | 2016-09-05 08:00:00 | 2016-09-05 16:00:00 |
| 5 | 2 | 2016-09-05 08:00:00 | 2016-09-05 16:00:00 |
| 6 | 2 | 2016-09-07 08:00:00 | 2016-09-07 16:00:00 |
| 7 | 2 | 2016-09-08 08:00:00 | 2016-09-08 16:00:00 |
| 8 | 2 | 2016-09-08 18:00:00 | 2016-09-08 22:00:00 |
+------+------------+---------------------+---------------------+
We have 2 users here so I want the following:
If I search for start_date = 2016-09-05 08:00:00 and end_date = 2016-09-05 16:00:00 it should return user 1 and 2. Since both of them has an entry with these dates. Same goes as well if start_date = 2016-09-05 09:00:00 and end_date = 2016-09-05 15:00:00, this should as well return both users since the time im searching for is between the time slots as shown in the example.
Second scenario is a little bit more tricky, If user search for start_date = 2016-09-03 08:00:00 and end_date = 2016-09-04 16:00:00 i want the query to check the following:
see if the user is available each day at these times.
so in this case, is the user available on 2016-09-03 between 08:00:00 and 16:00:00 and as well on 2016-09-04 between 08:00:00 and 16:00:00.
In the example over this should return user 1.
Im open for suggestion on re-designed my schema if needed.
Hope some can help me with this.
DEMO include some aditional code comment, and show how the query evolve . Also I add another row for user_id = 2 to show how only match one of the two days in the range.
SELECT U.`user_id`
FROM (
select a.selectDate,
CONCAT(a.selectDate, ' ', time(#s_date)) as start_time,
CONCAT(a.selectDate, ' ', time(#s_date)) as end_time
from (
select '1900-01-01' + INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) + (10000 * e.a)) DAY as selectDate
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
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 d
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 e
) a
CROSS JOIN (SELECT #s_date := '2016-09-03 08:00:00', #e_date := '2016-09-04 16:00:00') par
-- CROSS JOIN (SELECT #s_date := '2016-09-05 08:00:00', #e_date := '2016-09-05 16:00:00') par
-- CROSS JOIN (SELECT #s_date := '2016-09-05 09:00:00', #e_date := '2016-09-05 15:00:00') par
WHERE selectDate BETWEEN date(#s_date)
AND date(#e_date)
) D
CROSS JOIN (SELECT DISTINCT `user_id` FROM Table1) U
LEFT JOIN Table1 T
ON U.`user_id` = T.`user_id`
AND D.start_time <= T.`end_date`
AND D.end_time >= T.`start_date`
GROUP BY U.`user_id`
HAVING COUNT(U.`user_id`) = COUNT(T.`user_id`);
OUTPUT
Step 1: create a list of dates, in this case 273 years
Step 2: select all dates between the range define in the parameters, also include the time window to each date.
Step 3: join all together to see what dates have user in that time window
Step 4: select only user with a time window for all dates

How to get TIMEs of intervals between 2 given fields in MySQL

I have to following table, where start_time and end_time are TIME fields:
____________________________________
| id | day | start_time | end_time |
|____|_____|____________|____________|
| 1 | 1 | 8:00 | 12:00 |
I'd like to obtain the start time of every interval of 60 minutes between start_time and end_time, as:
_______
| start |
|_______|
| 8:00 |
| 9:00 |
| 10:00 |
| 11:00 |
Is that possible? Thanks
If you have a table that has integers, this is easier. If the field were datetime, you could do it like this (for up to 7 hours):
select date_add(t.start_time, interval n.n hour)
from t join
(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
) n
on date_add(t.start_time, interval n.n hour) <= t.end_time
With time, you can do it by converting to seconds and doing the arithmetic there:
select sec_to_time((floor(time_to_sec(t.start_time - 1)/3600)+1) + n.n*3600)
from t join
(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
) n
on (floor(time_to_sec(t.start_time - 1)/3600)+1) + n.n*3600 <= t.end_time