How can I get the current week in a generic query, currently I can get a date range but I would like to get the current week in a dynamic way.
This is what I have:
WITH mycte AS
(
SELECT CAST('2011-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2021-12-31'
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
Based on todays date I want to get something like:
11-04-2013 11-05-2013 11-06-2013 11-07-2013 11-08-2013 11-09-2013 11-10-2013
One way of doing it in SQL Server
WITH weekdays AS
(
SELECT 0 day
UNION ALL
SELECT day + 1 FROM weekdays WHERE day < 6
)
SELECT DATEADD(DAY, day, DATEADD(DAY, 2-DATEPART(WEEKDAY, CONVERT (date, GETDATE())), CONVERT (date, GETDATE()))) date
FROM weekdays
Output:
| DATE |
|------------|
| 2013-11-04 |
| 2013-11-05 |
| 2013-11-06 |
| 2013-11-07 |
| 2013-11-08 |
| 2013-11-09 |
| 2013-11-10 |
Here is SQLFiddle demo
In MySQL
SELECT CURDATE() + INTERVAL 1 - DAYOFWEEK(CURDATE()) DAY + INTERVAL day DAY date
FROM
(
SELECT 1 day UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7
) w
Here is SQLFiddle demo
Related
If I have a start and stop time for a booking, how can I calculate the number of bookings there are each minute? I made a simplified version of my database table looks like here:
Start time | End time | booking |
--------------------------------------------------
2020-09-01 10:00 | 2020-09-01 10:10 | Booking 1 |
2020-09-01 10:00 | 2020-09-01 10:05 | Booking 2 |
2020-09-01 10:05 | 2020-09-01 10:10 | Booking 3 |
2020-09-01 10:09 | 2020-09-01 10:10 | Booking 4 |
I want to have the bookings between a given time interval like 10:02 - 10:09. It should be something like this as result:
Desired result
Time | count
-----------
10:02 | 2 |
10:03 | 2 |
10:04 | 2 |
10:05 | 3 |
10:06 | 2 |
10:07 | 2 |
10:08 | 2 |
10:09 | 3 |
Question
How can this be achieved? Today I export it to python however I think it should be possible to achieve directly in SQL.
You can use a recursive CTE directly on your data:
with recursive cte as (
select start_time, end_time
from t
union all
select start_time + interval 1 minute, end_time
from cte
where start_time < end_time
)
select start_time, count(*)
from cte
group by start_time
order by start_time;
Here is a db<>fiddle.
EDIT:
In earlier versions of MySQL, it helps to have a tally table. You can create one on the fly, using something like:
(select #rn := #rn + 1 as n
from t cross join
(select #rn := 0) params
) tally
You need enough numbers for your maximum span, but then you can do:
select t.start_time + interval tally.n hour, count(*)
from t join
(select #rn := #rn + 1 as n
from t cross join
(select #rn := -1) params -- so it starts from 0
limit 100
) tally
on t.start_time + interval tally.n hour <= t.end_time
group by t.start_time + interval tally.n hour;
You can use a recursive query to generate the timestamp range, then unpivot the table and join:
with recursive dates (ts) as(
select '2020-09-01'
union all
select ts + interval 1 minute
from dates
where ts + itnerval 1 minute < '2020-09-02'
)
select d.ts, sum(t.cnt) over(order by d.ts) cnt
from dates d
left join (
select start_time ts, 1 cnt from mytable
union all select end_time, -1 from mytable
) t on t.ts <= d.ts
If you are going to run this repeatedly and/or against large time periods, you would better materialize the date ranges in a calendar table rather than use a recursive query. The calendar table has one row per minute over a large period of dates - assuming a table called date_calendar, you would do:
select d.ts, sum(t.cnt) over(order by d.ts) cnt
from date_calendar d
left join (
select start_time ts, 1 cnt from mytable
union all select end_time, -1 from mytable
) t on t.ts <= d.ts
where d.ts >= '2020-09-01' and d.ts < '2020-09-02'
I have a table of absences with 3 columns id, begin_dt, end_dt. I need to give a count of how many id's has at least one day of absence in that month. So for example there is a row as follow:
id begin_dt end_dt
1 01/01/2020 02/02/2020
2 02/02/2020 02/02/2020
my result has to be
month count
01-2020 1
02-2020 2
I thought with a group by on DATE_FORMAT(SYSDATE(), '%Y-%m'), but I don't know how to manage the fact that we had to look for the whole period begin_dt till end_dt
you can find a working creation of table of this example here: https://www.db-fiddle.com/f/rYBsxQzTjjQ9nGBEmeAX6W/0
Schema (MySQL v5.7)
CREATE TABLE absence (
`id` VARCHAR(6),
`begin_dt` DATETIME,
`end_dt` DATETIME
);
INSERT INTO absence
(`id`, `begin_dt`, `end_dt`)
VALUES
('1', DATE('2019-01-01'), DATE('2019-02-02')),
('2', DATE('2019-02-02'), DATE('2019-02-02'));
Query #1
select * from absence;
| id | begin_dt | end_dt |
| --- | ------------------- | ------------------- |
| 1 | 2019-01-01 00:00:00 | 2019-02-02 00:00:00 |
| 2 | 2019-02-02 00:00:00 | 2019-02-02 00:00:00 |
View on DB Fiddle
SELECT DATE_FORMAT(startofmonth, '%Y-%m-01') year_and_month,
COUNT(*) absent_person_count
FROM absence
JOIN ( SELECT DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m-01') startofmonth,
DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m-01') + INTERVAL 1 MONTH - INTERVAL 1 DAY endofmonth
FROM ( SELECT MIN(begin_dt) dt
FROM absence ) startdate,
( SELECT 0 n UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 ) numbers,
( SELECT DATE_FORMAT(MIN(begin_dt), '%Y-%m') mindate,
DATE_FORMAT(MAX(end_dt), '%Y-%m') maxdate
FROM absence ) datesrange
WHERE DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m') BETWEEN mindate AND maxdate ) dateslist
ON begin_dt <= endofmonth
AND end_dt >= startofmonth
GROUP BY year_and_month;
fiddle
I have one tables with two date columns (Date_open and Date_closed). All I want to do it to count occurrences per day. So to see how many were opened and closed each day. We look at the last 7 days from today. The problem is that some dates are not present in either of the columns and I can not find a way to either link tables with sub query (example code 1) or get coalesce work (example code 2)?
The table looks like that:
+------+------------+-------------+------+
| code | Date_open | Date_closed | Prio |
+------+------------+-------------+------+
| 1 | 2018-01-08 | 2018-01-08 | A |
| 2 | 2018-01-01 | 2018-01-08 | B |
| 3 | 2018-01-06 | 2018-01-07 | C |
| 4 | 2018-01-06 | 2018-01-06 | A |
| 5 | 2018-01-04 | 2018-01-06 | B |
| 6 | 2018-01-03 | 2018-01-01 | C |
| 7 | 2018-01-03 | 2018-01-02 | C |
| 8 | 2018-01-03 | 2018-01-02 | C |
+------+------------+-------------+------+
And the results I want are as follows:
Date OpenNo CloseNo
2018-01-01 1 1
2018-01-02 2
2018-01-03 3
2018-01-04 1
2018-01-05
2018-01-06 2 2
2018-01-07 1
2018-01-08 1 2
The first code I tried was:
SELECT *
FROM
(SELECT t1.Date_open,
COUNT(t1.Date_open) AS 'OpenNo'
FROM
Tbl AS t1
GROUP BY t1.Date_open)
AS A
JOIN
(SELECT t2.Date_closed,
COUNT(t2.Date_closed) AS 'CloseNo'
FROM
Tbl AS t2
GROUP BY t2.Date_closed)
AS B ON A.Date_open = B.Date_closed;
This code works as long as there is data for each day.
The second code I tried was:
SELECT
COALESCE (Date_open, Date_closed) AS Date1,
COUNT(Date_closed) AS ClosedNo,
COUNT(Date_open) AS OpenNo
FROM tbl
GROUP BY Date1;
Both do not work. Any ideas please?
Below is the code to create tbl.
create table Tbl(
code int(10) primary key,
Date_open DATE not null,
Date_closed DATE not null,
Prio varchar(10));
insert into Tbl values (1,'2018-01-08','2018-01-08' ,'A');
insert into Tbl values (2,'2018-01-01','2018-01-08' ,'B');
insert into Tbl values (3,'2018-01-06','2018-01-07' ,'C');
insert into Tbl values (4,'2018-01-06','2018-01-06' ,'A');
insert into Tbl values (5,'2018-01-04','2018-01-06' ,'B');
insert into Tbl values (6,'2018-01-03','2018-01-01' ,'C');
insert into Tbl values (7,'2018-01-03','2018-01-02' ,'C');
insert into Tbl values (8,'2018-01-03','2018-01-02' ,'C');
You may use a calendar table, and then left join to your current table twice to generate the counts for each date:
SELECT
d.dt,
COALESCE(t1.open_cnt, 0) AS OpenNo,
COALESCE(t2.closed_cnt, 0) AS CloseNo
FROM
(
SELECT '2018-01-01' AS dt UNION ALL
SELECT '2018-01-02' UNION ALL
SELECT '2018-01-03' UNION ALL
SELECT '2018-01-04' UNION ALL
SELECT '2018-01-05' UNION ALL
SELECT '2018-01-06' UNION ALL
SELECT '2018-01-07' UNION ALL
SELECT '2018-01-08'
) d
LEFT JOIN
(
SELECT Date_open, COUNT(*) AS open_cnt
FROM Tbl
GROUP BY Date_open
) t1
ON d.dt = t1.Date_open
LEFT JOIN
(
SELECT Date_closed, COUNT(*) AS closed_cnt
FROM Tbl
GROUP BY Date_closed
) t2
ON d.dt = t2.Date_closed
GROUP BY
d.dt
ORDER BY
d.dt;
Demo
The reason I aggregate the open and closed date counts in separate subqueries is that if were to try to just do a straight join across all tables involved, we would have to deal with double counting.
Edit:
If you wanted to just use the current date and seven days immediately preceding it, then here is a CTE which would do that:
WITH dates (
SELECT CURDATE() AS dt UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 2 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 3 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 4 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 5 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 6 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(), INTERVAL 7 DAY)
)
You could inline the above into my original query which is aliased as d, and it should work.
Coalesce can be confusing - it returns the first non-null value from the list you provide to it.
I don't know if this question requires a super-complex answer.
To get the count of open and closed for each unique date, the following could work.
SELECT
COALESCE (Date_open, Date_closed) AS Date1,
SUM(IF(Date_closed != null,1,0)) AS ClosedNo,
SUM(IF(Date_open != null,1,0)) AS OpenNo
FROM tbl
GROUP BY Date1;
For example now is August, I want a select query with result like:
+-----------+
| September |
+-----------+
| October |
+-----------+
| November |
+-----------+
| December |
+-----------+
| January |
+-----------+
| February |
+-----------+
| March |
+-----------+
| April |
+-----------+
| May |
+-----------+
| June |
+-----------+
| July |
+-----------+
| August |
+-----------+
And this order will change in next month.
Sorry if it is duplicate, I don't know what is keyword to search.
You can get the name of the month of a date with monthname() and use date_add() to subtract months from a date. That is, you can build a query using UNION ALL and tableless SELECTs getting the name of the month to get the desired result.
SELECT monthname(date_add(curdate(), interval -11 month))
UNION ALL
SELECT monthname(date_add(curdate(), interval -10 month))
...
UNION ALL
SELECT monthname(date_add(curdate(), interval -1 month))
UNION ALL
SELECT monthname(curdate());
Edit:
To make this a little more dynamic, you could once create a number table.
CREATE TABLE integers
(i integer);
INSERT INTO integers
(i)
VALUES ...
(-11),
(-10),
...
(-1),
(0),
...;
You can then select the desired range from that table, to build the list of months.
SELECT monthname(date_add(curdate(), interval i month))
FROM integers
WHERE i >= -11
AND i <= 0
ORDER BY i;
Create the month rows on-the-fly with UNION ALL or create a table and insert the records there. Couple month name and month number. Use the number for sorting. If you want to start with next month, compare with the current month:
select name
from
(
select 1 as num, monthname('2000-01-01') as name
union all
select 2 as num, monthname('2000-02-01') as name
union all
select 3 as num, monthname('2000-03-01') as name
union all
select 4 as num, monthname('2000-04-01') as name
union all
select 5 as num, monthname('2000-05-01') as name
union all
select 6 as num, monthname('2000-06-01') as name
union all
select 7 as num, monthname('2000-07-01') as name
union all
select 8 as num, monthname('2000-08-01') as name
union all
select 9 as num, monthname('2000-09-01') as name
union all
select 10 as num, monthname('2000-10-01') as name
union all
select 11 as num, monthname('2000-11-01') as name
union all
select 12 as num, monthname('2000-12-01') as name
) months
order by num <= month(current_date), num;
Rextester demo: http://rextester.com/NOYU32731
(This gives you the month names according to the language setting in the database. So the guy in France will see the month names in the same language as the girl in Spain when using this query.)
If for example, I have a table that looks like this:
+----+--------+---------------------+
| id | volume | createdAt |
+----+--------+---------------------+
| 1 | 0.11 | 2018-01-26 13:56:01 |
| 2 | 0.34 | 2018-01-28 14:22:12 |
| 3 | 0.22 | 2018-03-11 11:01:12 |
| 4 | 0.19 | 2018-04-13 12:12:12 |
| 5 | 0.12 | 2014-04-21 19:12:11 |
+----+--------+---------------------+
I want to perform a query that can accept starting point and then loop through a given number of days, and then group by that date range.
For instance, I'd like the result to look like this:
+------------+------------+--------+
| enddate | startdate | volume |
+------------+------------+--------+
| 2018-04-25 | 2018-04-12 | 0.31 |
| 2018-04-11 | 2018-03-29 | 0.00 |
| 2018-03-28 | 2018-03-15 | 0.00 |
| 2018-03-14 | 2018-03-01 | 0.22 |
| 2018-02-28 | 2018-02-15 | 0.00 |
| 2018-02-14 | 2018-02-01 | 0.00 |
| 2018-01-31 | 2018-01-18 | 0.45 |
| ... | ... | ... |
+------------+------------+--------+
In essence, I want to be able to input a start_date e.g 2018-04-25, a time_interval e.g. 14, like in the illustration above and then the query will sum the volumes in that time range.
I know how to use INTERVAL with the DATE_SUB() and the DATE_ADD() functions but I cannot figure out how to perform the loop I think is necessary.
Please help.
For the given data you can determine time based groupings using the datediff and floor functions:
floor(datediff(createdat, date '2018-04-25')/14) grp
From the group number you can determine the periods stardate and enddate:
date_add(date '2018-04-25', interval (grp*14) day) startdate
date_add(date '2018-04-25', interval ((grp+1)*14) day) enddate
Which represent a half open range with startdate being inclusive and enddate being exclusive.
Putting these together in a usable query:
select startdate, enddate, sum(volume)
from (select t1.*
, date_add(date '2018-04-25', interval (grp*14) day) startdate
, date_add(date '2018-04-25', interval ((grp+1)*14) day) enddate
from (select t.*
, datediff(t.createdat, date '2018-04-25') diff
, floor(datediff(t.createdat, date '2018-04-25')/14) grp
from table1 t) t1) t2
group by startdate, enddate
order by startdate desc;
Unfortunately this does not get the empty periods. To get the empty periods you need a way to generate rows. However, MySQL doesn't have a simple way to generate rows (at least not until MySQL 8 where common table expressions and recursive SQL are added), but there are database objects that already have a large number of rows, such as the information_schema.columns view which likely has sufficient rows for your needs, and if it doesn't, a cross join or two will easily multiply the number of records generated. That paired with a variable that increments for each row returned will provide the needed groups:
select #rn:=#rn+1 rn
, stop
, date_add(date '2018-04-25', interval (#rn*14) day) startdate
, date_add(date '2018-04-25', interval ((#rn+1)*14) day) enddate
from information_schema.columns c
, (select #rn:=min(floor(datediff(createdat, date '2018-04-25')/14))-1
, max(floor(datediff(createdat, date '2018-04-25')/14)) stop
from table1) limits
where #rn < stop;
Outer joining this with the original data and grouping by the period dates yields:
select startdate
, enddate
, sum(volume) volume
from table1
right join (
select #rn:=#rn+1 rn
, stop
, date_add(date '2018-04-25', interval (#rn*14) day) startdate
, date_add(date '2018-04-25', interval ((#rn+1)*14) day) enddate
from information_schema.columns c
-- , information_schema.columns d -- if needed add another cartesian join
, (select #rn:=min(floor(datediff(createdat, date '2018-04-25')/14))-1
, max(floor(datediff(createdat, date '2018-04-25')/14)) stop
from table1) limits
where #rn < stop) periods
on startdate <= createdat
and createdat < enddate
group by startdate, enddate
order by startdate desc;
Take a look at the SQL Fiddle to see this in action
All you need to do it determine start_date(which is the parameter you pass) and end_date from your entire table and loop through them by adding time interval.
Have a look at below stored routine:
CREATE DEFINER=`root`#`localhost` PROCEDURE `getTotalVolumeByDateRange`(start_time timestamp, time_interval int)
BEGIN
DECLARE max_date date;
DECLARE min_date date;
DECLARE temp_end_date date;
SET min_date = DATE(start_time);
SELECT DATE(MAX(createdAt)) FROM VolumeData INTO max_date;
-- SELECT max_date, min_date;
CREATE TEMPORARY TABLE tempRangedVolumeData(
start_date date,
end_date date,
Volume decimal(5,2)
);
while min_date <= max_date DO
SET temp_end_date = DATE_ADD(min_date, Interval time_interval DAY);
INSERT INTO tempRangedVolumeData(start_date, end_date, Volume)
SELECT min_date, temp_end_date, SUM(Volume)
FROM VolumeData
WHERE DATE(CreatedAt) BETWEEN min_date and temp_end_date;
SET min_date = DATE_ADD(min_date, Interval time_interval+1 DAY);
end while;
select
start_date,
end_date,
coalesce(Volume,0) as Volume
from tempRangedVolumeData;
drop table tempRangedVolumeData;
END
I hope this helps. Please comment if i am missing any edge case.