I would like to receive the sum of all requests of the last 10 days grouped by date per day.
If there was no request on a day, the corresponding date should appear with sumrequests = 0.
My current query (today is the date 2020-01-10):
SELECT
count( 0 ) AS sumrequests,
cast( requests.created_at AS date ) AS created
FROM
requests
WHERE
(
requests.created_at
BETWEEN ( curdate() - INTERVAL 10 DAY )
AND ( curdate() + INTERVAL 1 DAY ))
GROUP BY
cast(requests.created_at AS date)
I then receive the following list:
sumrequests | created
--------------------------
3 | 2020-01-05
100 | 2020-01-08
But it should give back:
sumrequests | created
--------------------------
0 | 2020-01-01
0 | 2020-01-02
0 | 2020-01-03
0 | 2020-01-04
3 | 2020-01-05
0 | 2020-01-06
0 | 2020-01-07
100 | 2020-01-08
0 | 2020-01-09
0 | 2020-01-10
How can I get this without an additional calendar table.
Thanks for help!
For just 10 days of data, you can simply enumerate the numbers; using this derived number table, you can generate the corresponding date range, left join it with the table and aggregate.
SELECT
COALESCE(count(r.created_at), 0) AS sumrequests,
CURDATE() - INTERVAL (n.i) DAY AS created
FROM (
select 0 i 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
) n
LEFT JOIN requests r
ON r.created_at >= CURDATE() - INTERVAL n.i DAY
AND r.created_at < CURDATE() - INTERVAL (n.i - 1) DAY
GROUP BY n.i
ORDER BY n.i DESC
Side notes:
generally you want to avoid applying functions in the join or filtering conditions, since it prevents the use of an index; I modified your filters to not use CAST()
Since we are left joining, we need to count something that is coming from the requests table, hence we use COUNT(r.created_at) instead of COUNT(0)
Related
I'm trying to generate a result from a query that list the last 7 days from today (2020/07/15) and the views matching a specific code.
If in that day the code has no views, I want the day to return 0.
Table Format
DAY | CODE | VIEWS
2020-07-10 | 123 | 5
2020-07-11 | 123 | 2
2020-07-12 | 123 | 3
2020-07-15 | 123 | 8
2020-07-15 | 124 | 2
2020-07-15 | 125 | 2
Expected result from code 123
DAY | VIEWS
2020-07-09 | 0
2020-07-10 | 5
2020-07-11 | 2
2020-07-12 | 3
2020-07-13 | 0
2020-07-14 | 0
2020-07-15 | 8
I already found a way to generate the calendar dates from here and adjust to my needs, but I don't know how to join the result with my table.
select * from
(select
adddate(NOW() - INTERVAL 7 DAY, t0) day
from
(select 1 t0
union select 1
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7) t0) v
Any help would by apreceated.
One option uses a recursive query - available in MySQL 8.0:
with recursive cte as (
select current_date - interval 6 day dt
union all
select dt + interval 1 day from cte where dt < current_date
)
select c.dt, coalesce(sum(t.views), 0) views
from cte
left join mytable t on t.day = c.dt
group by c.dt
order by c.dt
You can also manually build a derived table, as you originaly intended to (this would work on all versions of MySQL):
select current_date - interval d.n day dt, coalesce(sum(t.views), 0) views
from (
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
) d
left join mytable t on t.day = current_date - interval d.n day
group by d.n
order by d.n desc
I'm having a table from my thermostat.
It records data as follows.
So when it switches on, I get a timestate with Status 1 meaning on, Status 0 mean heating switches off. Additionally it gives me with every on/off the total heatings per day.
Date | Status | Total_heatings
2019-01-20 10:00:00 | 1 | 1
2019-01-20 10:10:00 | 0 | 1
2019-01-20 14:00:00 | 1 | 2
2019-01-20 14:25:00 | 0 | 2
2019-01-20 18:00:00 | 1 | 3
2019-01-20 18:15:00 | 0 | 3
2019-01-21 01:00:00 | 1 | 1
2019-01-21 01:30:00 | 0 | 1
2019-01-21 06:00:00 | 1 | 2
2019-01-21 06:15:00 | 0 | 2
I'm trying to get the total duration by day. I tried the below script, which gives me the durations for the multiple heating sessions for each day.
When I use SUM(TIMESTAMPDIFF(Minute,Min(Date),MAX(Date))) it throws an error because of wrong usage of grouping.
SELECT
DATE_FORMAT(Date, '%d.%m') AS 'day',
TIMESTAMPDIFF(MINUTE,MIN(Date),MAX(Date)) AS 'Duration'
FROM thermostat
WHERE (Date BETWEEN '2019-01-21 00:00:00' + INTERVAL -7 DAY AND '2019-01-21 00:00:00')
GROUP BY DAY(Date),Total_heatings;
All I would need is to get a SUM by day of these various heating sessions per day.
So the result should have the following:
Day | Duration
20.01 | 50
21.01 | 45
Now I'm stuck with not being able to further summing all heating session per day, like total duration each day.
Thanks a lot for any pointers and help.
This query will work for MySQL versions before 8.0. It uses a SELF JOIN to find matching heater off rows for a given heater on row. Where a matching row doesn't exist, it uses either the end of the day or the current time, whichever is lower.
SELECT DATE_FORMAT(t1.Date, '%d.%m') AS `day`,
SUM(TIMESTAMPDIFF(MINUTE, t1.Date, COALESCE(t2.Date, LEAST(NOW(), DATE(t1.Date) + INTERVAL 1 DAY)))) AS Duration,
MAX(t1.Total_heatings) AS Total_heatings
FROM thermostat t1
LEFT JOIN thermostat t2 ON t2.Status = 0 AND t2.Total_heatings = t1.Total_heatings AND DATE(t2.Date) = DATE(t1.Date)
WHERE t1.Status = 1 AND DATE(t1.Date) BETWEEN '2019-01-21' - INTERVAL 7 DAY AND '2019-01-21'
GROUP BY `day`
Output:
day Duration Total_heatings
20.01 50 3
21.01 45 2
Demo on dbfiddle
If you are using MySQL 8, you can use window function LAG to access the previous switch. In the outer query, you can filter on intervals where the previous status was on.
SELECT
DATE_FORMAT(x.date, '%d.%m'),
SUM(TIMESTAMPDIFF( minute, x.date, x.last_date) duration
FROM (
SELECT
t.*,
LAG(t.date) OVER (PARTITION BY DATE_FORMAT(t.date, '%d.%m') ORDER BY t.date) last_date,
LAG(t.status) OVER (PARTITION BY DATE_FORMAT(t.date, '%d.%m') ORDER BY t.date) last_status
FROM mytable t
) x
WHERE x.last_status = 1
GROUP BY DATE_FORMAT(x.date, '%d.%m')
ORDER BY 1
In this db fiddle, this matches your expected output.
Using window function available in MySQL-8.0 and MariaDB-10.2:
select DATE(ts) as 'day', sum(ontime) as 'on time'
from (
select status, lead(ts,1,ts) over w - ts as 'ontime'
from (
select unix_timestamp(ts) as ts, status
from t
order by ts
) x
window w as (order by ts)
) y
where status=1
group by 'day';
I have a database in which i have a table to save reports . Each report haves a date (year-month-day) which is set whenever the report got created .
After a lot of tests i got something to work but just not as i would like it to work.
I want to get the quantity of reports that were made on every month from an initial date (year-month-day) to a final date (year-month-day). But i'm not quite sure how to get it done.
This is the MySQL sentence i'm using right now:
SELECT meses.month id_mes, count(re_fecha) total
FROM
(
SELECT 1 AS MONTH
UNION SELECT 2 AS MONTH
UNION SELECT 3 AS MONTH
UNION SELECT 4 AS MONTH
UNION SELECT 5 AS MONTH
UNION SELECT 6 AS MONTH
UNION SELECT 7 AS MONTH
UNION SELECT 8 AS MONTH
UNION SELECT 9 AS MONTH
UNION SELECT 10 AS MONTH
UNION SELECT 11 AS MONTH
UNION SELECT 12 AS MONTH
) as meses
LEFT JOIN reportes ON month(re_fecha) = meses.MONTH
WHERE re_fecha BETWEEN '2017-01-01' AND '2017-08-31'
GROUP BY meses.MONTH, monthName(re_fecha)
This is the following result i'm getting with the MySQL sentence:
id_mes | total
---------------
04 | 15
05 | 5
06 | 15
07 | 2
I'm not sure if this helps in any way, but if i don't use the "where re_fechas... " i get a result that is closer to what we look for:
id_mes | total
-------------
01 | 0
02 | 0
03 | 0
04 | 15
05 | 5
06 | 15
07 | 2
08 | 6
09 | 0
10 | 0
11 | 0
12 | 0
And finally, what i would like to see:
id_mes | total
-------------------
01-2017 | 0
02-2017 | 0
03-2017 | 0
04-2017 | 15
05-2017 | 5
06-2017 | 15
07-2017 | 2
08-2017 | 6
I have two problems with how it works now:
When i use the sentence "where" the months that have 0 reports on the specified dates, are not shown. If i do not use "where", i get the things almost in the way i want them, but not in the range of dates i want.
The other issue i had is i would like to get the year of the month (As shown in the desired code block above).
I hope this is enough information to understand everything, i'm not sure if i could provide the database, but if you think that would help, let me know.
You almost got it.
If you add OR re_fecha IS NULL to your WHERE clause, then you would got almost what you wanted.
I came up with another solution that can help you:
SELECT meses.aMonth aMonth, COUNT(re_fecha) total
FROM (
-- Listing all months in period
SELECT DATE_FORMAT(m1, '%m-%Y') aMonth
FROM (
-- Range limit: about 21 years
SELECT
('2017-01-01' - INTERVAL DAYOFMONTH('2017-01-01')-1 DAY) +INTERVAL m MONTH as m1
FROM (
SELECT #rownum:=#rownum+1 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 <= '2017-08-31'
ORDER BY m1) meses
LEFT JOIN reportes ON DATE_FORMAT(re_fecha, '%m-%Y') = meses.aMonth
WHERE re_fecha BETWEEN '2017-01-01' AND '2017-08-31'
OR re_fecha IS NULL
GROUP BY meses.aMonth;
Test it: http://sqlfiddle.com/#!9/d21de6/27
Example output:
aMonth total
01-2017 0
02-2017 0
03-2017 0
04-2017 15
05-2017 5
06-2017 0
07-2017 2
08-2017 0
If you wasn't using MySQL, then you could use a FULL OUTER JOIN instead of LEFT JOIN.
Keep in mind that this solution is limited to 21 years. Try it changing only the initial date to 1970 and see it for yourself.
If needed, add more (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) tn, to increase the number of months.
References and useful links:
MySQL monthly Sale of last 12 months including months with no Sale
How to get a list of months between two dates in mysql
How to do a FULL OUTER JOIN in MySQL?
In your query try to change WHERE to AND.
I have the following query
SELECT count(*) as count, Month(created_at) as month
FROM products
WHERE marketplace_id=21
and status='counterfeit'
and created_at < Now()
and created_at > DATE_ADD(Now(), INTERVAL - 5 MONTH)
group by month(created_at)
it return result as
+-------+-------+
| count | month |
+-------+-------+
| 410 | 1 |
| 174 | 2 |
| 301 | 3 |
| 329 | 4 |
| 141 | 12 |
+-------+-------+
in case a month does not have values it doesn't returns it at all, but I want the default value 0 to be set for that month.
I have tried this link Return a default value if no rows found
and
Returning a value if no result
I am not sure whether I am not able to implement it correctly or this is not what I want
Try this, seems to be a little stupid, but may help for you;)
SELECT SUM(count) AS count, month
FROM (
SELECT count(*) as count, Month(created_at) as month FROM products WHERE marketplace_id=21
and status='counterfeit' and created_at < Now() and created_at > DATE_ADD(Now(), INTERVAL - 5 MONTH)
group by month(created_at)
UNION
SELECT * FROM (
SELECT 0 AS count, 1 AS month
UNION SELECT 0 AS count, 2 AS month
UNION SELECT 0 AS count, 3 AS month
UNION SELECT 0 AS count, 4 AS month
UNION SELECT 0 AS count, 5 AS month
UNION SELECT 0 AS count, 6 AS month
UNION SELECT 0 AS count, 7 AS month
UNION SELECT 0 AS count, 8 AS month
UNION SELECT 0 AS count, 9 AS month
UNION SELECT 0 AS count, 10 AS month
UNION SELECT 0 AS count, 11 AS month
UNION SELECT 0 AS count, 12 AS month) M
WHERE M.month < Month(Now()) AND M.month > Month(DATE_ADD(Now(), INTERVAL - 5 MONTH)))
) tmp
GROUP BY mouth
ORDER BY month
You could create another table with default values
test_defaults
-----------------
| month | count |
and than just left join it with your table of values so if the value is found within the main table, it will be used, if not value from test_defaults would be used (we will use COALESCE function which returns first non null value):
SELECT t1.month, COALESCE(t2.count, t1.count)
FROM test_defaults t1
LEFT JOIN test_data t2 ON t1.month = t2.month
ORDER BY t1.month;
Here's a working SqlFiddle demo
I have four Tables with four date coloumns respectively.
Table 1 ---------- Date 1
Table 2 ---------- Date 2
Table 3 ---------- Date 3
Table 4 ---------- Date 4
Now i want to get a day report in a month for all the four tables.if there is no data in any particular table for particular date it should dispaly NULL.How can i achieve it?
Structure:-
Table-1:-
ID Amount1 Date1
1 340 24/04/2013
2 200 04/04/2013
3 1000 15/04/2013
Table-2:-
ID Amount2 Date2
1 2000 22/04/2013
2 200 04/04/2013
3 1500 15/04/2013
Table-3:-
ID Amount3 Date3
1 3400 24/04/2013
2 200 19/04/2013
3 1800 15/04/2013
Table-4:-
ID Amount4 Date4
1 3200 24/04/2013
2 2200 04/04/2013
3 1000 18/04/2013
Now my result should be like
Date Amount1 Amount2 Amount3 Amount4
01/04/2013 Null Null Null Null
|
|
|
04/04/2013 200 200 null 2200
|
|
|
|
15/0402013 1000 1500 1800 null
|
|
|
|
|24/0402013 340 null 3400 3200
|
|
|
|
31/04/2013
Using a subquery to get a range of dates (I am assuming you want every day in April 2013) and then left joining that against the tables of data.
SELECT, dates.aDate, Table-1.Amount1, Table-2.Amount2, Table-3.Amount3, Table-4.Amount4
FROM
(
SELECT DATE_ADD('2013-04-01', INTERVAL (Units.i + Tens.i * 10) DAY) AS aDate
FROM
(SELECT 0 AS 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) Units,
(SELECT 0 AS 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) Tens
HAVING aDate <= '2013-04-30'
) dates
LEFT OUTER JOIN Table-1 ON Table-1.Date1 = dates.aDate
LEFT OUTER JOIN Table-2 ON Table-2.Date2 = dates.aDate
LEFT OUTER JOIN Table-3 ON Table-3.Date3 = dates.aDate
LEFT OUTER JOIN Table-4 ON Table-4.Date4 = dates.aDate
This assumes that there are not duplicate dates in any particular table.
You can try the following query
with dates as (
(select date from date1)union(select date from date2)union
(select date from date3)union (select date from date4) order by date asc)
select date,
(select amount from date1 where date=dt.date limit 1),
(select amount from date2 where date=dt.date limit 1),
(select amount from date3 where date=dt.date limit 1),
(select amount from date4 where date=dt.date limit 1)
from dates as dt;
You can add the date constraints on dates.
P.S.: Tested on PgSQL