SQL Aggregating by 6 month windows - mysql

Is there a better way of using SQL (mysql) to aggregate by 6 month windows? This seems to perform okay gets the relevant data in the normal case, but is obviously limited.
SELECT SUM(...) some_aggg,
(CASE
WHEN SOME_DATE > DATE_SUB(now(), INTERVAL 6 MONTH) THEN 3
WHEN SOME_DATE > DATE_SUB(now(), INTERVAL 12 MONTH) THEN 2
WHEN SOME_DATE > DATE_SUB(now(), INTERVAL 18 MONTH) THEN 1
ELSE 0
END) RECENCY
WHERE ...
GROUP BY RECENCY
ORDER BY RECENCY DESC

How about a little arithmetics?
select sum(...) some_aggg,
floor(timestampdiff(month, some_date, now()) / 6) recency
where ...
group by recency
order by recency
recency gives you an integer value that starts at 0 (the date is less than 6 months old) and increments by 1 for every 6 month period: 1 means between 6 months and 1 year old, and so on.

Related

Get user who has transaction in last 7 days, and only one transaction in 30 days

I have a table like below, today is 2021-05-25.
UserId
TransactionDate
1
2021-05-21
1
2021-05-12
4
2021-05-25
1
2021-04-03
3
2021-05-15
3
2021-04-02
4
2021-03-25
I want the output is 4.
Since 1 has transaction within 7 days, but 2 transactions within 30days.
3 don't have any transaction within 7 days.
Only 4 has transaction within 7 days, and that's the only transaction within 30 days.
I can only filtered by the first condition using
WHERE t.TransactionDate > to_date('2021-05-17')
GROUP BY 1
Can anyone help how to apply both condition?
You can use aggregation with having:
select userid
from t
where transactiondate >= curdate() - interval 30 day
group by userid
having count(*) = 1 and
max(transactiondate) >= curdate() - interval 7 day;
If you want the transaction detail, it is a little more complciated:
select t.*
from transactions t
where t.transactiondate >= curdate() - interval 7 day and
not exists (select 1
from transactions t2
where t2.userid = t.userid and
t2.transactiondate >= curdate() - interval 30 day and
t2.transactiondate <> t.transactiondate
);

MySQL - Get Aggregates For Last 1 Day, 7 Days, 30 Days And Allow For Records To Be Counted In More Than 1 Group

I have a table with the following data:
I am looking to group the rows into the following:
Within the last day (everything within the last 24 hours)
Within the last 7 days (everything within the last week)
Within the last 30 days (everything within the last month)
The end result for the above rows would look something like:
I can group the records into these brackets right now with:
SELECT (CASE WHEN created_at = CURDATE() THEN '1 Day'
WHEN created_at >= CURDATE() - INTERVAL 6 DAY THEN '7 Days'
WHEN created_at >= CURDATE() - INTERVAL 29 DAY THEN '30 Days'
END) AS Timeframe, COUNT(*) AS Count
FROM my_table
GROUP BY (CASE WHEN created_at = CURDATE() THEN '1 Day'
WHEN created_at >= CURDATE() - INTERVAL 6 DAY THEN '7 Days'
WHEN created_at >= CURDATE() - INTERVAL 29 DAY THEN'30 Days'
END)
But this will prevent individual records from being counted more than once. For example, lines 2 and 3 in the first picture needs to be counted in all three brackets (1 day, 7 days, and 30 days) - while lines 6 through 9 only needs to be counted in the 30 days bracket.
How would you do this with MySQL?
It is easiest to do this as columns, rather than rows:
SELECT SUM(created_at = CURDATE()) as today
SUM(created_at >= CURDATE() - INTERVAL 6 DAY) as last_7_days,
SUM(created_at >= CURDATE() - INTERVAL 29 DAY) as last_30_days,
SUM(created_at < CURDATE() - INTERVAL 29 DAY) as older
FROM my_table;
If you want your response in several rows, instead of just one with several columns, take #Gordon Linoff as your starting point... but perform the queries "one row at at time" (it won't be as efficient, because you visit the table 4 times instead of 1!):
-- Row for the 1 day timeframe
SELECT '1 Day' AS `Timeframe`, SUM(created_at = CURDATE()) AS `Count`
FROM my_table
UNION
-- Row for the 7 days timeframe...
SELECT '7 Days' AS `Timeframe`, SUM(created_at >= CURDATE() - INTERVAL 6 DAY) AS `Count`
FROM my_table
UNION
SELECT '30 Days' AS `Timeframe`, SUM(created_at >= CURDATE() - INTERVAL 29 DAY) AS `Count`
FROM my_table
UNION
SELECT 'Older' AS `Timeframe`, SUM(created_at < CURDATE() - INTERVAL 29 DAY) AS `Count`
FROM my_table ;
If you can use MariaDB instead of MySQL, you can use a WITH, which will allow the query to be efficient again:
WITH stats AS
(
SELECT SUM(created_at = CURDATE()) as today,
SUM(created_at >= CURDATE() - INTERVAL 6 DAY) as last_7_days,
SUM(created_at >= CURDATE() - INTERVAL 29 DAY) as last_30_days,
SUM(created_at < CURDATE() - INTERVAL 29 DAY) as older
FROM my_table
)
-- Convert to rows with negligible overhead
SELECT '1 Day' AS `Timeframe`, today FROM stats
UNION
SELECT '7 Days', last_7_days FROM stats
UNION
SELECT '30 Days', last_30_days FROM stats
UNION
SELECT 'Older', older FROM stats ;
In both cases, you'll get (as of 2017-07-25):
Timeframe | today
:-------- | ----:
1 Day | 0
7 Days | 4
30 Days | 8
Older | 0
dbfiddle here

MySQL Query to get all rows for 2 months ago

I need to do a select where I can chose to see results for current month, previous month, 1 month ago, 2 months ago, 3 months ago.
I found this question: MySQL: Query to get all rows from previous month, but I'm stuck with a filter that will get me all the results for 2 months ago from first to last day of the month.
I tried with this but it doesn't work:
SELECT * FROM table
AND MONTH(date_created) = MONTH(1 MONTH - INTERVAL 2 MONTH);
Try this:
SELECT * FROM table
WHERE MONTH(date_created) = MONTH(NOW() - INTERVAL 2 MONTH)
AND (
YEAR(date_created) = YEAR(NOW())
OR
YEAR(date_created) = YEAR(NOW() - INTERVAL 2 MONTH)
);
Returning records CREATED PRIOR the last 2 months only in MySQL.
If you want all rows from 2 months ago, then use logic like this:
WHERE date_created >= DATE_SUB(DATE_SUB(CURDATE(), 1 - DAY(CURDATE())), INTERVAL 2 MONTH) AND
date_created < DATE_SUB(DATE_SUB(CURDATE(), 1 - DAY(CURDATE())), INTERVAL 1 MONTH)
What is this doing? First, it is only applying functions to the current date part of the expression. This allows MySQL to use an index on date_created, if available and appropriate.
The expression DATE_SUB(CURDATE(), 1 - DAY(CURDATE()) is simply a way to get the first day of the month.
You query have an error, correct one would be:
SELECT * FROM table
WHERE MONTH(date_created) = MONTH(DATE_SUB(NOW(),INTERVAL 2 MONTH))
For current month just MONTH(NOW()), replace "2" with any number of months you need (1,3,.. 23)
as mentioned in comments this solution ignores YEAR differences, it just selects all records with the same month, no matter the year
you can filter wrong year results with additional clause:
AND YEAR(date_created) = '2019' # or year you need
Or use more complex query:
SELECT * FROM table
where
date_created between
/* first day of -2 month*/
date_sub(date_sub(now(),interval 2 month), interval (day(now())-1) day)
and
/* last day of -2 month*/
last_day(date_sub(now(),interval 2 month))

How to select records starting from last month to 12 months ago?

I need to select all the records starting from last month to 12 months ago. So when now is 2015-07-09, I would need to select records between 2014-08-01 and 2015-06-30.
Use this query :
select * from table_name where date_column between date_sub(ADDDATE(LAST_DAY(SUBDATE(curdate(), INTERVAL 11 MONTH)), 1), interval 1 month) , last_day(date_sub(curdate(), interval 1 month))
This would give you up to the end of last month.
select ... from table where mydate < date_sub(curdate(), interval day(curdate())-1 day)
This should point you in the right direction for what to learn about date math so you can figure out what to do to also test for beginning of 12 months ago (hint: subtract 12 months from the expression I have above and test for greater than or equal to that).

Combine 2 MySQL queries

I have this query
SELECT COUNT(*) from `login_log` where from_unixtime(`date`) >= DATE_SUB(NOW(), INTERVAL 1 WEEK);
and the same one with 1 diff. it's not 1 WEEK , but 1 MONTH
how can I combine those two and assign them to aliases?
I would do this with conditional aggregation:
SELECT SUM(from_unixtime(`date`) >= DATE_SUB(NOW(), INTERVAL 1 WEEK)),
SUM(from_unixtime(`date`) >= DATE_SUB(NOW(), INTERVAL 1 MONTH))
FROM `login_log`;
MySQL treats boolean values as integers, with 1 being "true" and 0 being "false". So, using sum() you can count the number of matching values. (In other databases, you would do something similar using case.)
Use the where condition with one month internal and add the same where condition with one week internal as a Boolean column return.
I mean
Select count (*) all_in_month, (from_unixtime(`date`) >= DATE_SUB(NOW(), INTERVAL 1 WEEK)) as in_week from `login_log` where from_unixtime(`date`) >= DATE_SUB(NOW(), INTERVAL 1 a MONTH) GROUP BY in_week;
P.s. haven't tested but afaik it should work
Even though it's pretty tough to understand what you ask:
If you want them in the same column use OR
SELECT COUNT(*) from 'login_log' where from_unixtime('date') >= DATE_SUB(NOW(), INTERVAL 1 WEEK) OR from_unixtime('date') >= DATE_SUB(NOW(), INTERVAL 1 MONTH) ;
If you don't want duplicate answers: use GROUP BY