I'm calculating
the number of days a reservation took place over every month (for every month since the first record)
A total price based on the total # of days and rate.
INSERT INTO `reservations`
(`id`, `user_id`, `property_id`, `actual_check_in`,`actual_check_out`)
VALUES
(5148, 1, 2, '2014-01-01', '2014-01-10'),
(5149, 1, 2, '2014-02-03', '2014-02-10'),
(5151, 1, 2, '2014-02-02', '2014-02-15'),
(5153, 1, 2, '2014-03-05', '2014-03-10'),
(5153, 1, 2, '2014-02-20', '2014-03-30'),
SELECT
YEAR(month.d),
MONTHNAME(month.d),
r.property_id,
SUM(
DATEDIFF(LEAST(actual_check_out, LAST_DAY(month.d)), GREATEST(actual_check_in, month.d))
) AS days,
SUM(days*p.rate),
MIN(r.actual_check_in) as firstDate,
MAX(r.actual_check_out) as lastDate
FROM reservations as r
LEFT JOIN property as p on r.property_id=p.id
RIGHT JOIN (
select
DATE_FORMAT(m1, '%Y%m%d') as d
from
(
select
(firstDate - INTERVAL DAYOFMONTH(firstDate)-1 DAY)
+INTERVAL m MONTH as m1
from
(
select #rownum:=#rownum+1 as m from
(select 1 union select 2 union select 3 union select 4) t1,
(select 1 union select 2 union select 3 union select 4) t2,
(select 1 union select 2 union select 3 union select 4) t3,
(select 1 union select 2 union select 3 union select 4) t4,
(select #rownum:=-1) t0
) d1
) d2
where m1<=lastDate
order by m1
) AS month ON
actual_check_in <= LAST_DAY(month.d)
AND month.d <= actual_check_out
GROUP BY user_id, month.d
Troubles I'm having:
getting MySQL to accept a variable for firstDate & lastDate in the joined query
I want to sum the monthly number of days together, for reservations by the same user, for the same month. I'm trying to turn the proper parts into a subquery to calculate that but having trouble..
http://sqlfiddle.com/#!9/71e34/1
I would like to have the results like (if the property rate is 150/day):
DATE | USER | #Days | Total Rate
--------------------------------------
01/2014 | 1 | 9 | 1350
01/2014 | 2 | 0 | 0
02/2014 | 1 | 30 | 4500
02/2014 | 2 | 0 | 0
03/2014 | 1 | 35 | 5250
03/2014 | 2 | 0 | 0
04/2014 | 1 | 0 | 0
04/2014 | 2 | 0 | 0
* # days can be more than the # of days in a month because there might be multiple reservations existing during that month
UPDATE---- This almost solved the problem, but I'm having trouble in the second large select statement to actually calculate the prices properly. The query is only taking in to account the first property rate, and not selecting them as per the join statement. Any help?
select
r.user_id,
DATE_FORMAT(m1, '%b %Y') as date,
(SELECT
SUM(
DATEDIFF(LEAST(actual_check_out, LAST_DAY(m1)), GREATEST(actual_check_in, m1))
) AS numdays
FROM reservations
where actual_check_in <= LAST_DAY(m1)
AND m1 <= actual_check_out
AND user_id=r.user_id
GROUP BY m1) as days,
(SELECT
SUM(
DATEDIFF(LEAST(r.actual_check_out, LAST_DAY(m1)), GREATEST(r.actual_check_in, m1))
) *p.rate
FROM reservations as r
left join property as p
on r.property_id=p.id
where actual_check_in <= LAST_DAY(m1)
AND m1 <= actual_check_out
AND user_id=r.user_id
GROUP BY m1) as price
from (
select ('2015-01-01' - INTERVAL DAYOFMONTH('2015-01-01')-1 DAY) +INTERVAL m MONTH as m1 from (
select #rownum:=#rownum+1 as m from
(select 1 union select 2 union select 3 union select 4) t1,
(select 1 union select 2 union select 3 union select 4) t2,
(select 1 union select 2 union select 3 union select 4) t3,
(select 1 union select 2 union select 3 union select 4) t4,
(select #rownum:=-1) t0
) d1
) d2
cross join reservations as r
where m1<=CURDATE() group by user_id, m1 order by m1
http://sqlfiddle.com/#!9/36035/21
Still not sure of your request, but the query below may point you to right direction:
SELECT DATE_FORMAT(r.actual_check_in, '%m/%Y') AS mnth, r.user_id,
DATEDIFF(MAX(r.actual_check_out),MIN(r.actual_check_in)) AS days,
DATEDIFF(MAX(r.actual_check_out),MIN(r.actual_check_in))*p.rate AS totalRate
FROM reservations r
JOIN property p ON r.property_id=p.id
GROUP BY DATE_FORMAT(r.actual_check_in, '%m/%Y'), r.user_id;
This returns data like below:
mnth user_id days totalRate
------- ------- ------ -----------
01/2014 1 9 1350
02/2014 1 56 8400
03/2014 1 5 750
http://sqlfiddle.com/#!9/36035/36
select
r.user_id as userId,
DATE_FORMAT(m1, '%b %Y') as date,
(SELECT
SUM(
DATEDIFF(LEAST(actual_check_out, LAST_DAY(m1)), GREATEST(actual_check_in, m1))
) AS numdays
FROM reservations
where actual_check_in <= LAST_DAY(m1)
AND m1 <= actual_check_out
AND user_id=userId
GROUP BY m1) as days,
(SELECT
sum(DATEDIFF(LEAST(r.actual_check_out, LAST_DAY(m1)), GREATEST(r.actual_check_in, m1))*p.rate)
FROM reservations as r
left join property as p
on r.property_id=p.id
where r.actual_check_in <= LAST_DAY(m1)
AND m1 <= r.actual_check_out
AND r.user_id=userId
GROUP BY m1) as price
from (
select ('2015-01-01' - INTERVAL DAYOFMONTH('2015-01-01')-1 DAY) +INTERVAL m MONTH as m1 from (
select #rownum:=#rownum+1 as m from
(select 1 union select 2 union select 3 union select 4) t1,
(select 1 union select 2 union select 3 union select 4) t2,
(select 1 union select 2 union select 3 union select 4) t3,
(select 1 union select 2 union select 3 union select 4) t4,
(select #rownum:=-1) t0
) d1
) d2
cross join reservations as r
where m1<=CURDATE() group by user_id, m1 order by m1
Related
This question already has answers here:
generate days from date range
(30 answers)
Closed 3 years ago.
I have a following table with columns:
id | number | created_at
1 | A11 | 2020-01-01 06:08:19
2 | A21 | 2020-01-04 06:08:19
How do I query all the data in a date range from specific date and count all data per day?
I tried something like that :
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') AS the_date , COUNT(*) AS count
FROM `transactions`
WHERE created_at BETWEEN DATE_FORMAT('2020-01-01', '%Y-%m-%d') AND DATE_FORMAT('2020-01-04', '%Y-%m-%d')
GROUP BY the_date
Then i got data like that :
the_date | count
2020-01-01 | 1
2020-01-04 | 1
I want to achieve
the_date | count
2020-01-01 | 1
2020-01-02 | 0
2020-01-03 | 0
2020-01-04 | 1
if your version is below mysql 8.0 then you can use this script :
step1 : create a sequence N rows table :
create table sequence(id int);
create procedure insert_data_proc(in v_i int)
begin
declare i int default 0;
while i < v_i
do
insert into sequence values (i);
set i = i + 1;
end while;
end;
call insert_data_proc(1000);
drop procedure insert_data_proc;
step2 : query the table and left join your table's by mindate,maxdate,datediff
select
t1.created_at the_date
,case when count is null then 0 else count end as count
from (
select date_add(t2.mincreated_at , interval id day) created_at
from sequence t1
left join (
select datediff(max(created_at),min(created_at)) diff
,date(min(created_at) ) mincreated_at
,date(max(created_at) ) maxcreated_at
from transactions
) t2 on 1=1
where t1.id < t2.diff+1
) t1
left join (
select date(created_at) created_at,count(1) count
from transactions
group by date(created_at)
) t2 on t1.created_at = t2.created_at
order by the_date
note : if your data's days over 1000 day then you only need to increase the SP value.
[Online Demo Link MySQL 5.7 | db<>fiddle](https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=57d3e60bb2b918e8b6d2d8f3d5e63a6c
)
you can use like this :
SET #date_min = '2019-01-01';
SET #date_max = '2019-01-04';
SELECT
date_generator.date as the_date,
IFNULL(COUNT(transactions.id), 0) as count
from (
select DATE_ADD(#date_min, INTERVAL (#i:=#i+1)-1 DAY) as `date`
from information_schema.columns,(SELECT #i:=0) gen_sub
where DATE_ADD(#date_min,INTERVAL #i DAY) BETWEEN #date_min AND #date_max
) date_generator
left join transactions on DATE(created_at) = date_generator.date
GROUP BY date;
so here I am creating a temporary table date_generator will dates in between of given date range and join to with your main table (transactions).
output as expected:
the_date | count
2020-01-01 | 1
2020-01-02 | 0
2020-01-03 | 0
2020-01-04 | 1
I will give a suggestion for you to do this,
1 Solution
Create temporary table and add the dates and then join with the transactions table
create temporary table tempcalander
as
select * from
(select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) dates 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 dates between '2020-01-01' and '2020-01-04';
SELECT DATE_FORMAT(dates, '%Y-%m-%d') AS the_date , COUNT(created_at) AS count
FROM transactions t right join tempcalander c on t.created_at = c.dates
WHERE dates BETWEEN DATE_FORMAT('2020-01-01', '%Y-%m-%d') AND DATE_FORMAT('2020-01-04', '%Y-%m-%d')
GROUP BY the_date
2 Solution
you can create a separate table to add your dates.
CREATE TABLE calendar
(
dates date PRIMARY KEY
) ;
Then add you dates to this table,
INSERT INTO
calendar (dates)
VALUES
('2020-01-01'),
('2020-01-02'),
('2020-01-03'),
('2020-01-04'),
('2020-01-05'),
('2020-01-06') ;
after you can join the the transactions table with the calendar table and get the output
SELECT DATE_FORMAT(dates, '%Y-%m-%d') AS the_date , COUNT(created_at) AS count
FROM transactions t right join calendar c on t.created_at = c.dates
WHERE dates BETWEEN DATE_FORMAT('2020-01-01', '%Y-%m-%d') AND DATE_FORMAT('2020-01-04', '%Y-%m-%d')
GROUP BY the_date
I have database
From tabel userlist
NO NAMA DATE
1 THIS 2019-01-17 18:40:45
2 IS 2019-01-17 18:40:45
3 NAME 2019-02-17 18:40:45
From tabel usertext
ID TEXT CREATE
1 THIS 2019-01-18 18:40:45
2 IS 2019-02-21 18:40:45
3 TEXT 2019-03-19 18:40:45
how to return like this with sql query
Month Name Text
Jan 2 1
Feb 1 1
Mar 0 1
i already try this
SELECT MONTHNAME(userlist.date) as Month, COUNT(userlist.no) as name, COUNT(usertext.id) as text FROM userlist, usertext WHERE MONTH(userlist.date) < 12 AND YEAR(userlist.date) = YEAR(CURRENT_TIMESTAMP) GROUP BY MONTHNAME(date)
and return like this
Month Name Text
Jan 1 1
Feb 1 1
Mar 1 1
Union, then aggregate.
SELECT Month, SUM(usr) as Name, SUM(txt) as Text
FROM
(
SELECT MONTH(t.date) AS monthnum, MONTHNAME(t.date) AS Month, 1 AS usr, 0 AS txt
FROM userlist t
WHERE YEAR(t.date) = YEAR(CURRENT_TIMESTAMP)
AND MONTH(t.date) < 12
UNION ALL
SELECT MONTH(t.create) AS monthnum, MONTHNAME(t.create) AS Month, 0 AS usr, 1 AS txt
FROM usertext t
WHERE YEAR(t.create) = YEAR(CURRENT_TIMESTAMP)
AND MONTH(t.create) < 12
) q
GROUP BY monthnum, Month
ORDER BY monthnum
And below a little piece of sql that generates 12 months. It could be an extra UNION ALL in the inner query, to fill gaps for the missing months.
select n as monthnum, monthname(100*n+1) as month, 0 as usr, 0 as txt
from (
select 1 as n 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 union all select 12
) q
You could try joining the subquery for count
select t1.my_month month, t2.count_name, t3.count_text
from(
select month(create ) my_month
from userlist
union
select month(create)
from usertext
) t1
left join (
select month(create) month, count(*) count_name
from userlist
group by month(create)
) t2 on t2.month = t1.my_month
left join (
select month(create) month, count(*) count_text
from usertext
group by month(create)
) t3 on t3.month = t1.my_month
I want to count the number of actions per day in my dataset.
date action_id
2010-01-01 id00
2010-01-03 id01
2010-01-05 id02
This is just a sample, but the point is that my data does not include actions for every day and I want to include days where there are zero actions in my result.
My plan is to do this.
with dates as (
select [sequence of dates from 2010-01-01 to 2010-02-01] as day)
select day, coalesce(count(distinct action_id), 0) as actions
from dates
left join my_table
on dates.date = my_table.date
How do I create the sequence of dates?
You example shows a CTE. So, you can use a recursive CTE:
with recursive dates as (
select date('2010-01-01') as day
union all
select day + interval 1 day
from dates
where day < '2010-02-01'
)
select d.day, count(distinct t.action_id) as actions
from dates d left join
my_table t
on d.day = my_table.date
group by d.day;
Note that COUNT() never returns NULL, so COALESCE() is unnecessary.
In older versions, you can use a calendar table or generate the data on the fly. Assuming your table has enough rows:
select d.day, count(distinct t.action_id) as actions
from (select date('2010-01-01') + interval (#rn := #rn + 1) - 1 day as day
from my_table cross join
(select #rn := 0) params
limit 31
) d left join
my_table t
on d.day = my_table.date
group by d.day;
it seems just you need group by and count
select date, count(distinct action_id) as action
from my_table left join
dates on dates.date = my_table.date
group by date
with dates as
(
select a.Date
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) ) DAY as Date
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
) a
where a.Date between '<start_date>' and '<end_date>' )
select day, count(distinct action_id) as actions
from dates
left join my_table
on dates.date = my_table.date
I have a table Order:
id total_amount date_order
1 12.000 2018-09-01
2 10.000 2018-09-01
3 5.000 2018-09-03
4 2.000 2018-09-05
I have query SUM data group by date:
select SUM(total_amount) as 'total', DATE(date_order) as 'date'
from Order
where date_order >= '2018-09-01' and date_order <= '2018-09-06'
group by (date_order)
It shows:
total date
22.000 2018-09-01
5.000 2018-09-03
2.000 2018-09-05
Because data in 2018-09-02 and 2018-09-04 have no data so It's not show in result. But I want query to show table with expect result:
total date
22.000 2018-09-01
0 2018-09-02
5.000 2018-09-03
0 2018-09-04
2.000 2018-09-05
Every can help me write query to show like expect result ?
Create another table to join on.
It could be a table of dates, such as all date from 2000-01-01 until 2099-12-31...
SELECT
dt.`date`,
SUM(total_amount) as `total`
FROM
yourDatesTable AS dt
LEFT JOIN
Order AS o
ON o.`date_order` = dt.`date`
WHERE
dt.`date` >= '2018-09-01'
AND dt.`date` < '2018-09-08'
GROUP BY
dt.`date`
ORDER BY
dt.`date`
Or it could be a numbers table with values from -1023 to +1024 (or some other useful range)...
SELECT
n.id,
'2018-09-01' + INTERVAL n.id DAYS,
SUM(total_amount) as `total`
FROM
yourNumbersTable AS n
LEFT JOIN
Order AS o
ON o.`date_order` = '2018-09-01' + INTERVAL n.id DAYS
WHERE
n.id >= 0
AND n.id < 8
GROUP BY
n.id
ORDER BY
n.id
generate date and join with ordere table
select SUM(O.total_amount) as total, DATE(gen_date) as oreder_date
from
(
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 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 t1 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 t2 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 t3 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 t4 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 gen_date between '2018-09-01' and '2018-09-30'
) as t1 left join `Order` O on t1.gen_date= DATE(O.date_order)
where gen_date >= '2018-09-01' and gen_date <= '2018-09-06'
group by oreder_date
Previously I was doing following to get per day count from reports table.
SELECT COUNT(*) AS count_all, tracked_on
FROM `reports`
WHERE (domain_id = 939 AND tracked_on >= '2014-01-01' AND tracked_on <= '2014-12-31')
GROUP BY tracked_on
ORDER BY tracked_on ASC;
Obviously this wont give me 0 count for missing dates.
Then I finally found a optimum solution to generate date-series between given date range.
But the next challenge am facing is to join it with my reports table and get the count grouped by date.
select count(*), all_dates.Date as the_date, domain_id
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
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
) all_dates
inner JOIN reports r
on all_dates.Date >= '2014-01-01'
and all_dates.Date <= '2014-12-31'
where all_dates.Date between '2014-01-01' and '2014-12-31' AND domain_id = 939 GROUP BY the_date order by the_date ASC ;
The result am getting is
count(*) the_date domain_id
46 2014-01-01 939
46 2014-01-02 939
46 2014-01-03 939
46 2014-01-04 939
46 2014-01-05 939
46 2014-01-06 939
46 2014-01-07 939
46 2014-01-08 939
46 2014-01-09 939
46 2014-01-10 939
46 2014-01-11 939
46 2014-01-12 939
46 2014-01-13 939
46 2014-01-14 939
...
Whereas am looking to fill in the missing dates with 0
something like
count(*) the_date domain_id
12 2014-01-01 939
23 2014-01-02 939
46 2014-01-03 939
0 2014-01-04 939
0 2014-01-05 939
99 2014-01-06 939
1 2014-01-07 939
5 2014-01-08 939
...
Another try that I gave was:
select count(*), all_dates.Date as the_date, domain_id
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
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
) all_dates
inner JOIN reports r
on all_dates.Date = r.tracked_on
where all_dates.Date between '2014-01-01' and '2014-12-31' AND domain_id = 939 GROUP BY the_date order by the_date ASC ;
Results:
count(*) the_date domain_id
38 2014-09-03 939
8 2014-09-04 939
Minimal data with above queries: http://sqlfiddle.com/#!2/dee3e/6
You need an OUTER JOIN to arrive at every day between a start and an end because if you use an INNER JOIN it will restrict the output to just the dates that are joined (i.e. just those dates in the report table).
In addition, when you use an OUTER JOIN you must take care that conditions in the where clause don't cause an implicit inner join; for example AND domain_id = 1 if use in the where clause would suppress any row that did not have that condition met, but when used as a join condition it only restricts the rows of the report table.
SELECT
COUNT(r.domain_id)
, all_dates.Date AS the_date
, domain_id
FROM (
SELECT DATE_ADD(curdate(), INTERVAL 2 MONTH) - INTERVAL (a.a + (10 * b.a) ) DAY as Date
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
) all_dates
LEFT OUTER JOIN reports r
ON all_dates.Date = r.tracked_on
AND domain_id = 1
WHERE all_dates.Date BETWEEN '2014-09-01' AND '2014-09-30'
GROUP BY
the_date
ORDER BY
the_date ASC;
I have also changed the all_dates derived table, by using DATE_ADD() to push the starting point into the future, and I have reduced the it's size. Both of these are options and can be tweaked as you see fit.
Demo at SQLfiddle
to arrive at a domain_id for every row (as shown in your question) you would need to use someting like the following; Note you could use IFNULL() which is MySQL specific but I have used COALESCE() which is more generic SQL. However use of an #parameter as shown here is MySQL specific anyway.
SET #domain := 1;
SELECT
COUNT(r.domain_id)
, all_dates.Date AS the_date
, coalesce(domain_id,#domain) AS domain_id
FROM (
SELECT DATE_ADD(curdate(), INTERVAL 2 month) - INTERVAL (a.a + (10 * b.a) ) DAY as Date
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
) all_dates
LEFT JOIN reports r
ON all_dates.Date = r.tracked_on
AND domain_id = #domain
WHERE all_dates.Date BETWEEN '2014-09-01' AND '2014-09-30'
GROUP BY
the_date
ORDER BY
the_date ASC;
See this at SQLfiddle
The all_dates subquery is only looking back from the current day (curdate()). If you want to include future dates, change the first line of the subquery to something like:
select '2015-01-01' - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date