Aggregating a column by multiple date ranges in a single query - mysql

I have a MySQL table with 5 columns:
Device | Name | Date | Source | Income
Neither Device nor Name nor Source are unique.
I am trying to write the SQL for getting the following:
Device | Name | Source | Income (for last 3 days) | Income (for last 9 days) | Income (for last 12 days)
What is the best way to do this?

You can get a conditional SUM() via CASE statements:
SELECT Device
,Name
,Source
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 3 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_3_Days
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 9 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_9_Days
FROM YourTable
GROUP BY Device
,Name
,Source

SELECT COUNT(*) as total_l3d FROM table WHERE DATEDIFF(Date, NOW()) <= 3
UNION
SELECT COUNT(*) as total_l9d FROM table WHERE DATEDIFF(Date, NOW()) <= 9
UNION
SELECT COUNT(*) as total_l12d FROM table WHERE DATEDIFF(Date, NOW()) <= 12

Related

Count Number of a Specific Day(s) Between Two Dates

I have a single line in MySQL table: volunteers
user_id | start_date | end_date
11122 | 2017-04-20 | 2018-02-17
How can I find how many times the 3rd day or 24th day of a month appears? (i.e. 2017-05-03, 2017-06-03, 2017-12-24, 2018-01-24) I'm trying to get to the following count:
Sample Output:
user_id | number_of_third_day | number_of_twenty_fourth_day
11122 | 10 | 10
I look at the documentation online to see if there is a way I can say (pseudo):
SELECT
day, COUNT(*)
FROM volunteers
WHERE day(between(start_date, end_date)) in (3,24)
I tried to create a calendar table to no avail, but I would try to get the days, GROUP BY day, and COUNT(*) times that day appears in the range
WITH calendar AS (
SELECT start_date AS date
FROM volunteers
UNION ALL
SELECT DATE_ADD(start_date, INTERVAL 1 DAY) as date
FROM volunteers
WHERE DATE_ADD(start_date, INTERVAL 1 DAY) <= end_date
)
SELECT date FROM calendar;
Thanks for any help!
This one is more optimized since I generate date range by months not days as other questions, so its faster
WITH RECURSIVE cte AS
(
SELECT user_id, DATE_FORMAT(start_date, '%Y-%m-03') as third_day,
DATE_FORMAT(start_date, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM table1
UNION ALL
SELECT user_id,
DATE_FORMAT(third_day + INTERVAL 1 MONTH, '%Y-%m-03') as third_day,
DATE_FORMAT(twenty_fourth_day + INTERVAL 1 MONTH, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM cte
WHERE third_day + INTERVAL 1 MONTH <= end_date
)
SELECT user_id,
SUM(CASE WHEN third_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_third_day,
SUM(CASE WHEN twenty_fourth_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_twenty_fourth_day
FROM cte
GROUP BY user_id;
Demo here
A dynamic approach is.
but creating the dateranges, takes a lot of time, so you should have a date table to get the dates
CREATE TABLE table1
(`user_id` int, `start_date` varchar(10), `end_date` varchar(10))
;
INSERT INTO table1
(`user_id`, `start_date`, `end_date`)
VALUES
(11122, '2017-04-20', '2018-02-17')
,(11123, '2019-04-20', '2020-02-17')
;
Records: 2 Duplicates: 0 Warnings: 0
WITH RECURSIVE cte AS (
SELECT
user_id,
`start_date` as date_run ,
`end_date`
FROM table1
UNION ALL
SELECT
user_id,
DATE_ADD(cte.date_run, INTERVAL 1 DAY),
end_date
FROM cte
WHERE DATE_ADD(date_run, INTERVAL 1 DAY) <= end_date
)SELECT user_id,
SUM(DAYOFMONTH(date_run) = 3) as day_3th,
SUM(DAYOFMONTH(date_run) = 24) as day_24th
FROM cte
GROUP BY user_id
user_id
day_3th
day_24th
11122
10
10
11123
10
10
fiddle
In last MySQL version you can use recursion:
-- get list of all dates in interval
with recursive dates(d) as (
select '2017-04-20'
union all
select date_add(d, interval 1 day) from dates where d < '2018-02-17'
) select
-- calculate
sum(day(d) = 10) days_10,
sum(day(d) = 24) days_24
from dates
-- filter 10 & 24 days
where day(d) = 10 or day(d) = 24;
https://sqlize.online/sql/mysql80/c00eb7de69d011a85502fa538d64d22c/
As long as you are looking for days that occur in every month (so not the 29th or beyond), this is just straightforward math. The number of whole calendar months between two dates (exclusive) is:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
Then add one if the start month includes the target day and one if the end month includes it:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
+ (day(start_date) <= 3) + (day(end_date) >= 3)

How to do 2 queries in the same sql statement?

I have a DB in which I save all the daily visits to a lot of websites, the fields of the day table are name, nvisit and date
I want to make a comparison between the visits of the day before yesterday and those of yesterday, so the result to show should be 3 rows. name, the day before yesterday and yesterday
I have tried with this query but it shows me everything in 2 rows.
SELECT *
FROM (
SELECT name as nombre, nvisit as anteayer
from day
WHERE date < CURDATE() -1 and date > CURDATE() -2
UNION ALL
SELECT name as nombre, nvisit as ayer
from day
where date < CURDATE() and date > CURDATE() -1
GROUP by name
)
day
What can I do to solve this problem?
When executing the sentence the result is:
|name |beforeyesterday|
|.............|...............|
|example1.com |2154 |
|example1.com |3215 |
|example2.com |1524 |
|example2.com |2546 |
What I need:
|name |beforeyesterday|yesterday|
|.............|...............|.........|
|example1.com |2154 |3215 |
|example2.com |1524 |2546 |
I would want a separate row for each name with the columns for the counts on the various days:
select name,
sum(case when date < curdate() and date > curdate - interval 1 day
then nvisit
end) as yesterday,
sum(case when date < curdate() - interval 1 day and date > curdate - interval 2 day
then nvisit
end) as day_before
from day
group by name;

How to output the results of a SQL query into a report form table? [duplicate]

This question already has answers here:
multiple query same table but in different columns mysql
(4 answers)
Closed 4 years ago.
I have an the following table fields:
Invitations (user_id, type, created_at, completed_at)
I'm currently able to obtain last week's invitation conversation rate by running the following query and manually computing.
SELECT *
FROM invitations
WHERE created_at >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY
AND created_at < curdate() - INTERVAL DAYOFWEEK(curdate())-1 DAY
AND user_id != 1
AND type = 'email'
ORDER BY completed_at, created_at
Is it possible with SQL to output more of a report... Something that returns:
LAST WEEK | Total Invitations | Invitations Completed | % Conversion
| 100 | 50 | 50%
TWO WEEKS | Total Invitations | Invitations Completed | % Conversion
| 100 | 60 | 60%
Is something like this possible with SQL or do I need to create this with application logic?
Maybe you want to aggregate using count() and do a UNION ALL.
SELECT 'LAST WEEK' `Period`,
count(created_at) `Total Invitations`,
count(completed_at) `Invitations completed`,
concat(count(completed_at) / count(created_at) * 100, '%') `% Conversion`
FROM invitations
WHERE created_at >= curdate() - INTERVAL dayofweek(curdate()) + 6 DAY
AND created_at < curdate() - INTERVAL dayofweek(curdate()) - 1 DAY
AND user_id <> 1
AND type = 'email'
UNION ALL
SELECT 'TWO WEEKS' `Period`,
count(created_at) `Total Invitations`,
count(completed_at) `Invitations completed`,
concat(count(completed_at) / count(created_at) * 100, '%') `% Conversion`
FROM invitations
WHERE created_at >= curdate() - INTERVAL dayofweek(curdate()) + 13 DAY
AND created_at < curdate() - INTERVAL dayofweek(curdate()) - 1 DAY
AND user_id <> 1
AND type = 'email';
count(completed_at) does only count the rows, where completed_at isn't null. I assume that completed_at is null if and only if the invitation isn't completed. count(created_at) works analog. But I assume there are no null values in that column (and if they were, those rows won't match the conditions in the WHERE clause, so they aren't even candidates for counting). UNION ALL just unites the two result sets (without eliminating duplicates, which aren't in there anyway, as at least the Period differs).

Sql query to fetch the number of visitors per day since the last 7 days only

I have a database table visitors with three columns:
id | Name | checkin_date |
1 | Reg | 2018-04-20T08:28:54.446Z |
2 | Meg | 2018-04-21T08:28:54.446Z |
3 | Ted | 2018-04-21T08:28:54.446Z |
4 | Bin | 2018-04-23T08:28:54.446Z |
There are several records such as these.
I want to fetch the count of records per each day for only the past 7 days. Right now i was able to fetch the count of visitors per day for all the dates using :
select count(id) as no_of_users
, DATE_FORMAT(checkin_date, '%d %b, %Y') as date
from visitors
GROUP
BY DATE(checkin_date)
But this displays the count of users per each day of all the records. How to get the records of only past 7 days.
select count(id) as no_of_users, DATE_FORMAT(checkin_date, '%d %b, %Y') as date from visitors
where checkin_date >= DATE(NOW()) - INTERVAL 7 DAY
GROUP BY DATE(checkin_date)
in the where is where you want to do the date field >= last 7 days
From your question.
You need to create a calendar table, then LEFT JOIN on the calendar table.
SELECT DATE(t.dt),count(t1.id) cnt
FROM
(
SELECT NOW() dt
UNION ALL
SELECT NOW() - INTERVAL 1 DAY
UNION ALL
SELECT NOW() - INTERVAL 2 DAY
UNION ALL
SELECT NOW() - INTERVAL 3 DAY
UNION ALL
SELECT NOW() - INTERVAL 4 DAY
UNION ALL
SELECT NOW() - INTERVAL 5 DAY
UNION ALL
SELECT NOW() - INTERVAL 6 DAY
UNION ALL
SELECT NOW() - INTERVAL 7 DAY
) t LEFT JOIN T t1
ON DATE(t.dt) = DATE(t1.checkin_date)
group by t1.name,DATE(t.dt)
sqlfiddle:http://sqlfiddle.com/#!9/59f49b/5
select id, count(id) as TOTAL, min (checkin_date) as no_of_users
from visitors
where checkin_date between '<Start Date>' and '<End Date>'
GROUP
BY Id,checkin_date

mysql query for grabbing multiple date ranges

I seem to be having a bit of trouble coming up a query to achieve what I want. I have a table like the following..
| Date(TIMESTAMP) | Count |
|---------------------|-------|
| 2016-02-01 01:00:00 | 52 |
| 2016-01-05 11:30:00 | 14 |
| 2016-02-01 04:20:00 | 36 |
| ... | ... |
The table has about 40,000 rows. What I would like to do is grab the totals for multiple date ranges so I end up with the following...
| Period | Total |
|------------|-------|
| All | 10245 |
| Past year | 1401 |
| Past month | 104 |
| Past week | 26 |
Currently I am running through a loop in my PHP script and doing an individual query for each date range I'm looking for. Actually there are about 10 queries I'm doing per loop to grab different stats but for the example I'm simplifying it. This takes forever and I am hoping there is a more elegant way to do this, however I've spent quite a bit of time now trying different things and researching and have gotten nowhere. I understand how to use CASE to group but not when a record may need to be in multiple bins. Any help?
Try this UNION query:
SELECT 'All', COUNT(*) AS Total FROM yourTable
UNION
SELECT 'Past year', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 YEAR)
UNION
SELECT 'Past month', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 MONTH)
UNION
SELECT 'Past week', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 WEEK)
1st. get known to function getting first date of year, first date of month and first date of week.
Then compose your sql using count and filter with first and last date of different period.
ref:
MySQL Select First Day of Year and Month
month
https://stackoverflow.com/a/19259159/1258492
week https://stackoverflow.com/a/11831133/1258492
select 'All' as period, count(1) from
tbl
union
select 'Past Year' as period, count(1) from
tbl
where timestamp between
MAKEDATE(year(now())-1,1) and
last_day(MAKEDATE(year(now())-1,1) + interval 11 month)
union
select 'Past Month' as period, count(1) from
tbl
where timestamp between
LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY and
LAST_DAY(NOW() - INTERVAL 1 MONTH)
union
select 'Past Week' as period, count(1) from
tbl
where timestamp between
adddate(curdate(), INTERVAL 1-DAYOFWEEK(curdate())-7 DAY) and
adddate(curdate(), INTERVAL 7-DAYOFWEEK(curdate())-7 DAY) ;
You may use subqueries. Use one subquery per time breakdown like so:
SELECT everything, 'past year'
FROM
(
SELECT sum(c) AS 'everything'
FROM reports
) t1,
(
SELECT sum(c) AS 'past year'
FROM reports
WHERE d >= DATE_ADD(CURDATE(), INTERVAL -1 YEAR)
) t2