Calculating aggregated number of days in each month in sql - mysql

I've got a table with multiple columns and two of the columns are start_date and end_date.
I need to calculate the number of days in each month. Let's assume I have following data in my table
id | start_date | end_date
1 04.01.2016 15.02.2016
2 07.01.2016 22.01.2016
3 16.05.2016 11.07.2016
I want an output as follows
Month | numberOfTravelDays
January 51
February 15
May 15
June 31
July 11
This output I am expecting is the number of total travel days each month has been utilized. I am having trouble constructing the sql query for this. Can someone assist me on this?
This is what I have for now. And it's not doing the job. The below query also filters only this year's records(but ignore that).
select MONTH(start_date) as month,
COUNT(DATEDIFF(start_date, end_date)) as numberOfTravelDays
from travel
where YEAR(start_date) = YEAR(CURDATE())
group by MONTH(start_date),
MONTH(end_date)

Use a derived table:
select monstart,
sum(datediff(least(m.monend, t.end_date) + interval 1 day,
greatest(m.monstart, t.start_date)
)
) as days_worked
from travel t join
(select date('2016-01-01') as monstart, date('2016-01-31') as monend union all
select date('2016-02-01') as monstart, date('2016-02-29') as monend union all
. . .
) m
on t.end_date >= m.monstart and t.start_date <= m.monend
group by monstart;

Related

MySQL query for records that existed at any point each week

I have a table with created_at and deleted_at timestamps. I need to know, for each week, how many records existed at any point that week:
week
records
2022-01
4
2022-02
5
...
...
Essentially, records that were created before the end of the week and deleted after the beginning of the week.
I've tried various variations of the following but it's under-reporting and I can't work out why:
SELECT
DATE_FORMAT(created_at, '%Y-%U') AS week,
COUNT(*)
FROM records
WHERE
deleted_at > DATE_SUB(deleted_at, INTERVAL (WEEKDAY(deleted_at)+1) DAY)
AND created_at < DATE_ADD(created_at, INTERVAL 7 - WEEKDAY(created_at) DAY)
GROUP BY week
ORDER BY week
Any help would be massively appreciated!
I would create a table wktable that looks like so (for the last 5 weeks of last year):
yrweek | wkstart | wkstart
-------+------------+------------
202249 | 2022-11-27 | 2022-12-03
202250 | 2022-12-04 | 2022-12-10
202251 | 2022-12-11 | 2022-12-17
202252 | 2022-12-18 | 2022-12-24
202253 | 2022-12-25 | 2022-12-31
To get there, find a way to create 365 consecutive integers, make all the dates of 2022 out of that, and group them by year-week.
This is an example:
CREATE TABLE wk AS
WITH units(units) AS (
SELECT 0 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
)
,tens AS(SELECT units * 10 AS tens FROM units )
,hundreds AS(SELECT tens * 10 AS hundreds FROM tens )
,
i(i) AS (
SELECT hundreds +tens +units
FROM units
CROSS JOIN tens
CROSS JOIN hundreds
)
,
dt(dt) AS (
SELECT
DATE_ADD(DATE '2022-01-01', INTERVAL i DAY)
FROM i
WHERE i < 365
)
SELECT
YEAR(dt)*100 + WEEK(dt) AS yrweek
, MIN(dt) AS wkstart
, MAX(dt) AS wkend
FROM dt
GROUP BY yrweek
ORDER BY yrweek;
With that table, go:
SELECT
yrweek
, COUNT(*) AS records
FROM wk
JOIN input_table ON wk.wkstart < input_table.deleted_at
AND wk.wkend > input_table.created_at
GROUP BY
yrweek
;
I first build a list with the records, their open count, and the closed count
SELECT
created_at,
deleted_at,
(SELECT COUNT(*)
from records r2
where r2.created_at <= r1.created_at ) as new,
(SELECT COUNT(*)
from records r2
where r2.deleted_at <= r1.created_at) as closed
FROM records r1
ORDER BY r1.created_at;
After that it's just adding a GROUP BY:
SELECT
date_format(created_at,'%Y-%U') as week,
MAX((SELECT COUNT(*)
from records r2
where r2.created_at <= r1.created_at )) as new,
MAX((SELECT COUNT(*)
from records r2
where r2.deleted_at <= r1.created_at)) as closed
FROM records r1
GROUP BY week
ORDER BY week;
see: DBFIDDLE
NOTE: Because I use random times, the results will change when re-run. A sample output is:
week
new
closed
2022-00
31
0
2022-01
298
64
2022-02
570
212
2022-03
800
421

Count id for each day in a month

I have a database in mysql for a hospital where the columns are: id, entry_date, exit_date (the last two columns are the hospital patient entry and exit).
I would like to count the number of patients on each day of a given month
The code to count the number of ids for a given day is relatively simple (as described), but the count for each day of an entire month i do not know how to do.
Day 2019-09-01: x patients
Day 2019-09-02: y patients
Day 2019-09-03: z patients
.
.
.
x + y + z + ... = total patients on each day for all days of september
SELECT Count(id) AS patientsday
FROM saps
WHERE entry_date <= '2019-05-02'
AND ( exit_date > '2019-05-02'
OR exit_date IS NULL )
AND hospital = 'X'
First, assuming every day there is at least one patient entering this hospital, I would write a temporary table containing all the possibles dates called all_dates.
Second, I would create a temporary table joining the table you have with all_dates. In this case, the idea is to duplicate the id. For each day the patient was inside the hospital you will have the id related to this day on your table. For example, before your table looked like this:
id entry_date exit_date
1 2019-01-01 2019-01-05
2 2019-01-03 2019-01-04
3 2019-01-10 2019-01-15
With the joined table, your table will look like this:
id possible_dates
1 2019-01-01
1 2019-01-02
1 2019-01-03
1 2019-01-04
1 2019-01-05
2 2019-01-03
2 2019-01-04
3 2019-01-10
3 2019-01-11
3 2019-01-12
3 2019-01-13
3 2019-01-14
3 2019-01-15
Finally, all you have to do is count how many ids you have per day.
Here is the full query for this solution:
WITH all_dates AS (
SELECT distinct entry_date as possible_dates
FROM your_table_name
),
patients_per_day AS (
SELECT id
, possible_dates
FROM all_dates ad
LEFT JOIN your_table_name di
ON ad.possible_dates BETWEEN di.entry_date AND di.exit_date
)
SELECT possible_dates, COUNT(ID)
FROM patients_per_day
GROUP BY 1
Another possible solution, following almost the same strategy, changing only the conditons of the join is the query bellow:
WITH all_dates AS (
SELECT distinct entry_date as possible_dates
FROM your_table_name
),
date_intervals AS (
SELECT id
, entry_date
, exit_date
, datediff(entry_date, exite_date) as date_diference
FROM your_table_name
),
patients_per_day AS (
SELECT id
, possible_dates
FROM all_dates ad
LEFT JOIN your_table_name di
ON datediff(ad.possible_dates,di.entry_date)<= di.date_diference
)
SELECT possible_dates, COUNT(ID)
FROM patients_per_day
GROUP BY 1
This will break it down for number of entries for all dates. You can modify the SELECT to add a specific month and/or year.
SELECT
CONCAT(YEAR, '-', MONTH, '-', DAY) AS THE_DATE,
ENTRIES
FROM (
SELECT
DATE_FORMAT(entry_date, '%m') AS MONTH,
DATE_FORMAT(entry_date, '%d') AS DAY,
DATE_FORMAT(entry_date, '%Y') AS YEAR,
COUNT(*) AS ENTRIES
FROM
saps
GROUP BY
MONTH,
DAY,
YEAR
) AS ENTRIES
ORDER BY
THE_DATE DESC

group by date containing certain date only

I have tried looking at some similar examples like group by date range and weekdays etc but I couldnt fix it on my query.
as per my sample data screenshot, I need to only return
sum(salesamount)/sum(salescount) for week 1
and
sum(salesamount)/sum(salescount) for week 2.
Each of the week contain 5 days (in this example is wednesday - sunday).
My Attempt:
select salesstartdate, date_add(salesstartdate, interval 5 day) as gdate,
salesamount, salescount, sum(salesamount)/sum(salescount) as ATV
from testing
group by gdate;
My desired output is:
Week 1 15.34173913
Week 2 15.80365088
Calculation to get week 1 is (3507.1+3639.97+5258.77+8417.04+5994.48)/(285+273+344+478+368)
Calculation to get week 2 is the same as above except the date would now be from 8 to 12 of June.
You can do it with a subquery. In order to first group your result set properly and then execute aggregation on it:
SELECT
concat('WEEK', ' ', weekno) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
(
SELECT
salesstartdate,
salesamount,
salescount,
WEEKOFYEAR(salesstartdate) as weekno -- get the week number of the current year
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
) as weeks
GROUP BY
weekno
I have used 2 MySQL functions here:
WEEKOFYEAR()
WEEKDAY()
Output:
WEEK 23 | 2016-06-08 | 2016-06-12 | 15.8040
WEEK 24 | 2016-06-16 | 2016-06-19 | 15.9323
and without subquery as well:
SELECT
concat('WEEK', ' ', WEEKOFYEAR(salesstartdate)) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
GROUP BY
WEEKOFYEAR(salesstartdate)
You can do this way
select SUBDATE(salesstartdate, WEEKDAY(salesstartdate)) as week_range
, sum(salesamount)/sum(salescount)
from testing
where salesstartdate between SUBDATE(salesstartdate, WEEKDAY(salesstartdate))
and date_add(SUBDATE(salesstartdate, WEEKDAY(salesstartdate)), interval 5 day))
Group by week_range

MySQL calculate gain, loss and net gain over a period of time

I have a table something like this:
id | Customer | date
-----------------------------------------
1 | Customer2 | 2013-08-01 00:00:00
-----------------------------------------
2 | Customer1 | 2013-07-15 00:00:00
-----------------------------------------
3 | Customer1 | 2013-07-01 00:00:00
-----------------------------------------
. | ... | ...
-----------------------------------------
n | CustomerN | 2012-03-01 00:00:00
I want to calculate the "gained" customers for each month, the "lost" customers for each month and the Net Gain for each month, even if done in separate tables / views.
How can I do that?
EDIT
Ok, let me demonstrate what I've done so far.
To select Gained customers for any month, I've tried to select customers from Bookings table where the following not exist:
select Customer
from Bookings
where not exists
(select Customer
from Bookings
where
(Bookings.date BETWEEN
DATE_FORMAT(DATE_SUB(Bookings.date, INTERVAL 1 MONTH), '%Y-%m-01 00:00:00')
AND DATE_FORMAT(Bookings.date, '%Y-%m-01 00:00:00'
)
) AND Bookings.date >= STR_TO_DATE('2010-11-01 00:00:00', '%Y-%m-%d 00:00:00'))
This supposedly gets the customers that existed in the "selected" month but not in the previous one. "2010-11-01" is the date of the start of bookings + 1 month.
To select Lost customers for any month, I've tried to select customers from Bookings table where the following not exist:
select Customer
from Booking
where not exists
(select Customer
from Bookings
where
(Bookings.date BETWEEN
DATE_FORMAT(Bookings.date, '%Y-%m-01 00:00:00')
AND Bookings.date
)
AND Bookings.date >= STR_TO_DATE('2010-11-01 00:00:00', '%Y-%m-%d 00:00:00'
)
)
This supposedly gets the customers that existed in a previous month but not in the "selected" one.
For the "Loss" SQL query I got empty result! For the "Gain" I got thousands of rows but not sure if that's accurate.
You can use COUNT DISTINCT to count your customers, and WHERE YEAR(Date) = [year] AND MONTH(Date) = [month] to get the month.
The total number of customers in Sept 2013:
SELECT COUNT(DISTINCT Customer) AS MonthTotalCustomers FROM table
WHERE YEAR(date) = 2013 AND MONTH(date) = 9
The customers gained in Sept 2013:
SELECT COUNT(DISTINCT Customer) AS MonthGainedCustomers FROM table
WHERE YEAR(date) = 2013 AND MONTH(date) = 9
AND Customer NOT IN
(SELECT Customer FROM table
WHERE date < '2013-09-01')
Figuring out the lost customers is more difficult. I would need to know by what criteria you consider them to be 'lost.' If you just mean that they were around in August 2013 but they were not around in September 2013:
SELECT COUNT(DISTINCT Customer) AS MonthLostCustomers FROM table
WHERE YEAR(date) = 2013 AND MONTH(date) = 8
AND Customer NOT IN
(SELECT Customer FROM table
WHERE YEAR(date) = 2013 AND MONTH(date) = 9)
I hope from these examples you can extrapolate what you're looking for.

Count/group rows based on date including missing

I have a bunch of rows in my db that signify orders, i.e.
id | date
---------------------
1 | 2013-09-01
2 | 2013-09-01
3 | 2013-09-02
4 | 2013-09-04
5 | 2013-09-04
What I'd like is to display the count of rows per day, including missing days, so the output would be:
2013-09-01 | 2
2013-09-02 | 1
2013-09-03 | 0
2013-09-04 | 2
I've seen examples of having 2 tables, one with the records and the other with dates, but I'd ideally like to have a single table for this.
I can currently find the rows that have a record, but not days that do not.
Does anyone have a n idea on how to do this?
Thanks
If you want to get data for last 7 days, you can generate your pseudo-table via UNION, like:
SELECT
COUNT(t.id),
fixed_days.fixed_date
FROM t
RIGHT JOIN
(SELECT CURDATE() as fixed_date
UNION ALL SELECT CURDATE() - INTERVAL 1 day
UNION ALL SELECT CURDATE() - INTERVAL 2 day
UNION ALL SELECT CURDATE() - INTERVAL 3 day
UNION ALL SELECT CURDATE() - INTERVAL 4 day
UNION ALL SELECT CURDATE() - INTERVAL 5 day
UNION ALL SELECT CURDATE() - INTERVAL 6 day) AS fixed_days
ON t.`date` = `fixed_days`.`fixed_date`
GROUP BY
`fixed_days`.`fixed_date`
-see this fiddle demo. Note, that if your fields are DATETIME date type, then you'll need to applyy DATE() first:
SELECT
COUNT(t.id),
fixed_days.fixed_date
FROM t
RIGHT JOIN
(SELECT CURDATE() as fixed_date
UNION ALL SELECT CURDATE() - INTERVAL 1 day
UNION ALL SELECT CURDATE() - INTERVAL 2 day
UNION ALL SELECT CURDATE() - INTERVAL 3 day
UNION ALL SELECT CURDATE() - INTERVAL 4 day
UNION ALL SELECT CURDATE() - INTERVAL 5 day
UNION ALL SELECT CURDATE() - INTERVAL 6 day) AS fixed_days
ON DATE(t.`date`) = `fixed_days`.`fixed_date`
GROUP BY
`fixed_days`.`fixed_date`
Try Something like this!
SELECT
c1,
GROUP_CONCAT(c2 ORDER BY c2) AS 'C2 values'
FROM table
GROUP BY c1;
To retrieve a list of c1 values for which there exist specific values in another column c2, you need an IN clause specifying the c2 values and a HAVING clause specifying the required number of different items in the list ...
SELECT c1
FROM table
WHERE c2 IN (1,2,3,4)
GROUP BY c1
HAVING COUNT(DISTINCT c2)=4;
For more help by this related question
Counting all rows with specific columns and grouping by week
Use the following stored procedure it will allow you to get results for more than 7 days, just pass the number of days you want
DELIMITER $$
CREATE DEFINER=`server`#`%` PROCEDURE `test`(d INT)
BEGIN
CREATE TEMPORARY TABLE dates
(
f_day DATETIME
);
WHILE d > 0 DO
INSERT INTO dates SELECT DATE(NOW())-d;
SET d = d - 1;
END WHILE;
SELECT
IF(isnull(`id`),0,`id`),
`fixed_days`.`f_day`
FROM t
RIGHT JOIN
dates AS `fixed_days`
ON t.`date` = `fixed_days`.`f_day`
GROUP BY
`fixed_days`.`f_day`;
DROP TABLE dates;
END