Select most recent monthly anniversary day from a unix timestamp - mysql

I have an app that inserts a Unix timestamp on registration. What I'd like to do, is calculate usage details for the month since the last monthly anniversary day. So I would need a unix timestamp of what the most recent anniversary day would be.
For example, if a registration is submitted on January 5, the customer's anniversary day is the 5th. So to check usage on February 15th, I need to retrieve all entries from the logs since Feb 5.
Getting the day of registration is easy:
SELECT DATE_FORMAT(FROM_UNIXTIME(created), '%d') FROM accounts
however, I'm lost finding the unix timestamp of the last anniversary date based on the registration day. How would I do that? To clarify, I'm looking to return all action_id created on or after the most recent anniversary date.
Tables:
accounts
+------------+------------+
| account_id | created |
+------------+------------+
| 1 | 1321838910 |
+------------+------------+
....
logs
+------------+------------+------------+
| account_id | action_id | created |
+------------+------------+------------+
| 1 | 233 | 1348249244 |
+------------+------------+------------+
| 1 | 263 | 1348257653 |
+------------+------------+------------+
....
Note: to keep things simple, I'm going to forgo figuring out what happens if an anniversary day is the 31st for example - that is, unless someone has a super ninja statement that takes those occurrences into account.

Not tested. See what you think. Logic is to:
Get the last day of the current month.
Add the account created day number of days to #1 result.
If current day is greater than created day, subtract 1 month from #2 result. Else subtract 2 months.
SELECT l.*
FROM accounts a
LEFT JOIN logs l
ON a.account_id = l.account_id
AND l.created >= UNIX_TIMESTAMP(
DATE_SUB(DATE_ADD(LAST_DAY(NOW()), INTERVAL DAY(FROM_UNIXTIME(a.created)) DAY),
INTERVAL IF(DAY(NOW()) > DAY(FROM_UNIXTIME(a.created)), 1, 2) MONTH));
Edit
I gave this some more thought and perhaps the query below will work regardless of when the anniversary date is. Assumption I made that if the anniversary day is not in a particular month then last day of the month should be taken. It's ugly but I put in some variables to make it more concise, there must be a nicer way. Anyway, I haven't tested but logic as follows.
If current day > anniversay day then just subtract the difference in days to get date.
else if the last day of the previous month is less than anniversary day then use the last day of previous month.
else subtract the day difference between anniversary day and last day of previous month from last date of previous month.
SELECT l.*
FROM accounts a
JOIN logs l
ON a.account_id = l.account_id
AND l.created >= UNIX_TIMESTAMP(
IF(#dNow := DAY(NOW()) >= #dCreated := DAY(FROM_UNIXTIME(a.created)),
DATE_SUB(NOW(), INTERVAL #dNow - #dCreated DAY),
IF(DAY(#endLastMonth := LAST_DAY(DATE_SUB(NOW(), INTERVAL 1 MONTH))) <= #dCreated,
#endLastMonth,
DATE_SUB(#endLastMonth, INTERVAL DAY(#endLastMonth) - #dCreated DAY))));

Perhaps this using order by desc to get most recent created date?
SELECT DATE_FORMAT(FROM_UNIXTIME(X.created), '%d')
FROM (
SELECT CREATED
FROM mytable
WHERE ACCOUNT_ID = ? -- customer id
AND DATE_DIFF(DATE_FORMAT(FROM_UNIXTIME(CREATED),'%Y-%m-%d'), NOW()) MOD 30 = 0
AND DATE_DIFF(DATE_FORMAT(FROM_UNIXTIME(CREATED),'%Y-%m-%d'), NOW()) / 30 = 1
ORDER BY CREATED DESC LIMIT 1)X;

Related

SQL query with time comparison

I have a table of users and a table of orders. Table data is linked using a key
user_id. The user has a date of birth. It is necessary to compose a query to display one random user from the users table, over 30 years old, who has made at least 3 orders in the last six months.
I was able to make a query to sample by age:
SELECT Name from users WHERE(DATEDIFF(SYSDATE(), birthday_at)/365)>30;
but I don’t know how to solve the problem to the end
I like the additional effort LukStorms has shown by including details of the date calculations but one important point was missed. It may seem like a subtle difference but it is amazing how often it goes unnoticed until the dataset gets significantly larger. In the WHERE clause for the users age -
WHERE TIMESTAMPDIFF(YEAR, usr.birthday_at, CURDATE()) > 30
the result of the function call (age calculation) is being compared to a static integer. This will result in every user record having its age calculated unnecessarily and will also mean that any applicable index on the birthday_at column cannot be used. By moving the date calculation to the other side of the comparison available indices can be used -
WHERE u.birthday_at <= DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
This may be insignificant for your use case but it is still a good habit to get into as it will almost certainly catch you out one day.
Furthermore, if you are retrieving the random user as part of some kind of reward scheme, I would suggest applying a random order of some kind as the single row returned will be predictable and repeatable.
SELECT u.id, u.Name
FROM users AS u
JOIN orders AS o
ON u.id = o.user_id
AND o.order_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
WHERE u.birthday_at <= DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
GROUP BY u.id
HAVING COUNT(o.id) >= 3
ORDER BY RAND()
LIMIT 1
Join to orders
Get only those over 30 years old and with orders from last 6 months
Group by the user
Filter on the count with a having
Limit to 1 without sorting (since random)
SELECT usr.Name AS UserName
FROM users AS usr
JOIN orders AS ord
ON ord.user_id = usr.user_id
WHERE TIMESTAMPDIFF(YEAR, usr.birthday_at, CURDATE()) > 30
AND ord.order_date BETWEEN DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY)
AND LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
GROUP BY usr.Name
HAVING COUNT(ord.order_id) >= 3
LIMIT 1
Test code for the date calculations
-- previous month, last day
select LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
| LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) |
| :---------------------------------------------- |
| 2021-10-31 |
-- 6 months ago, first day
select DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY)
| DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY) |
| :-------------------------------------------------------------------------- |
| 2021-05-01 |
-- someone's current age
select TIMESTAMPDIFF(YEAR, '2005-11-28', CURDATE())
| TIMESTAMPDIFF(YEAR, '2005-11-28', CURDATE()) |
| -------------------------------------------: |
| 15 |
db<>fiddle here

Iterate over calendar weeks and fetch data per week

i got a sql question.
Given this table structure:
user-id | isActive | start | end
------------------------------------------
1 | 1 | 10.01.2021 | null
2 | 1 | 03.01.2021 | 01.12.2021
...
I need to do a query to see how many user are active per calendar week.
How could i do this with plain sql? A user is considered as active if the start date is before or within the calendar week and end is null or after that week.
I'd like to have something like for a given time period like last year
date | amount
----------------------------------------------------
xx.xx.xxxx (monday per calendarweek) | <amount of active user>
Thanks!
Your question is a bit vague on how you define whether a user is active for a week. Is it on the first day? The entire week? Any time during the week?
In any case, the basic idea is a recursive CTE. The following uses the logic of "any time in week":
with recursive cte as (
select startd - interval weekday(startd) day as week, endd, userid
from t
where isActive = 1
union all
select week + interval 7 day, endd, userid
from cte
where (endd > week + interval 7 day or endd is null) and week < curdate()
)
select week, count(*)
from cte
group by week
order by week;
Here is a db<>fiddle.

Mysql: How to retrieve data on weekly basis

I have a data like this:
And now i want to print this data on weekly basis like this
+------------+-----------+
| weeks | sum(count)|
+----------- +-----------+
| week 1 | 2526 |
| week 2 | 26987 |
+------------+-----------+
This query sum all mimi_count but i want data in above figure format may be it has to with
group by
.I have searched a lot but could't find what i want
SELECT sum(mimie_count) as mimie
FROM statistics
WHERE mimiDate > DATE_SUB(NOW(), INTERVAL 1 WEEK)
SELECT
WEEK(mimiDate) weeks,
sum(mimie_count) as mimie
FROM statistics
WHERE mimiDate > DATE_SUB(NOW(), INTERVAL 5 WEEK)
GROUP BY WEEK(mimiDate)
ORDER BY mimiDate;
Note: If you want last X weeks sum week wise then use this DATE_SUB(NOW(), INTERVAL X WEEK). Also note that WEEK function assumes the start of the week is Monday
EDIT:
If you want the first column like you stated then you need to adopt the following query:
SELECT
CONCAT('week ',WEEK(mimiDate)) weeks,
sum(mimie_count) as mimie
FROM statistics
WHERE mimiDate > DATE_SUB(NOW(), INTERVAL 5 WEEK)
GROUP BY WEEK(mimiDate)
ORDER BY mimiDate;
For Specific date range search:
SELECT
CONCAT('week ',WEEK(mimiDate)) weeks,
sum(mimie_count) as mimie
FROM statistics
WHERE mimiDate BETWEEN '2016-01-21' AND ' 2016-03-05'
GROUP BY WEEK(mimiDate)
ORDER BY mimiDate;

DATE in a between dynamic

I need to build an SQL query which does the following
SELECT pi.desc,
pa.nameAddress,
pi.ref,
pi.descItem,
pi.quantity,
pi.totalDF,
pi.code,
pi.codeBL,
cl.dateShip, po.dtValidated, po.supervisorDate, DATEDIFF(po.supervisorDate, po.dtValidated) AS 'diffValidSupervisor', DATEDIFF(cl.dtlivr, po.supervisorDate) AS 'diffExpeValid', year(cl.dtlivr), month(cl.dtlivr) FROM
new.proforma_item pi
INNER JOIN
old.cdestk_lig cl ON pi.codeCde = cl.codcde INNER JOIN new.proforma po ON po.idProforma = pi.idProforma Inner JOIN new.proforma_address pa ON po.idProforma = pa.idProforma GROUP BY pi.desc, pi.ref, pi.descItem, pi.code, pi.codeBL, cl.dateShip, po.dtValidated, po.supervisorDate, month(cl.dateShip), po.dateInvoice HAVING (po.dateInvoice between '2014-01-01' AND '2014-12-31')
But each year I have to review this request to change the year. I want to make this dynamic, because change this manually is just crazy in our architecture.
The best of all is:
Say we are the 15 June 2015. I would like a between clause that covers the period:
2015-01-01 to 2015-05-31
In finite I need a between which take the first day of the current year and last day of last month.
EDIT
When we are in Januray, we have to work on the full passed year, not the current month. (January will be treated next month)
You can use the DATE_FORMAT function to construct the first day of the year and LAST_DAY to get the last day of the previous month.
po.dateInvoice between DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-01-01') AND LAST_DAY(NOW() - INTERVAL 1 MONTH)
SQL fiddle for the current date
SQL fiddle for January 2015 (Showing all of 2014)
You can easily get the first day of the current year using concat function and for getting the last day of the previous month is also straight forward something as
mysql> select
concat(year(curdate()),'-01-01') as fday,
last_day(curdate()-interval 1 month) as lday ;
+------------+------------+
| fday | lday |
+------------+------------+
| 2015-01-01 | 2015-05-31 |
+------------+------------+
1 row in set (0.00 sec)
So in the where clause you just need to replace the hardcoded part with the above date ranges.
HAVING
(
po.dateInvoice between
concat(year(curdate()),'-01-01') AND
last_day(curdate()-interval 1 month)
)
The above works but still there might be a logical issue, what if we are at January now, so as per the logic it will get the first day of the year and then last day of the previous month and its Dec 31st previous year and having clause will fail, so may be need an extra condition something while getting the last day as
case
when month(curdate()) = 1 then concat(year(curdate()),'-01-31')
else last_day(curdate()-interval 1 month)
end
So when you are in January it will get record from 1st Jan till 31st
UPDATE:
When we are in Januray, we have to work on the full passed year, not
the current month. (January will be treated next month)
The logic could be applied as
HAVING
(
po.dateInvoice between
case
when
month(curdate()) = 1 then concat(year(curdate())-1,'-01-01') else concat(year(curdate()),'-01-01')
end
AND
case
when
month(curdate()) = 1 then concat(year(curdate())-1,'-12-31') else last_day(curdate()-interval 1 month)
end
)
I think these SQL help to you.
SELECT pi.desc,
pa.nameAddress,
pi.ref,
pi.descItem,
pi.quantity,
pi.totalDF,
pi.code,
pi.codeBL,
cl.dateShip, po.dtValidated, po.supervisorDate, DATEDIFF(po.supervisorDate, po.dtValidated) AS 'diffValidSupervisor',
DATEDIFF(cl.dtlivr, po.supervisorDate) AS 'diffExpeValid', year(cl.dtlivr), month(cl.dtlivr) FROM
new.proforma_item pi
INNER JOIN
old.cdestk_lig cl ON pi.codeCde = cl.codcde INNER JOIN new.proforma po ON po.idProforma = pi.idProforma
Inner JOIN new.proforma_address pa ON po.idProforma = pa.idProforma GROUP BY pi.desc, pi.ref, pi.descItem, pi.code, pi.codeBL, cl.dateShip,
po.dtValidated, po.supervisorDate, month(cl.dateShip), po.dateInvoice
HAVING (po.dateInvoice between CONCAT(YEAR(CURDATE()),'-01-01') AND last_day(curdate()-interval 1 month))
Thank you.

Mysql select record saved 1 month ago or the older one if none found

I got a small stat table similar to :
+----+----------+-------+------------+-------------+
| id | stat_key | value | created_at | customer_id |
+----+----------+-------+------------+-------------+
Each day, a cron perform some calculation and saves stat for each customer.
Now I want to retrieve the stat from 30 days ago, or if the customer is new, the older stat available as long as it's less than 30 days ago.
So I tried to perform the following query, but for customers older than 30 days, it's always the older stat of all time that is returned :
SELECT *
FROM stat_table
WHERE customer_id = 1 AND stat_key = 'some_key'
HAVING (DATE(created_at) = DATE(NOW() - INTERVAL 30 DAY)
OR DATE(created_at) = DATE(MIN(created_at)));
Is there a way to perfom this ? "Give me the stat from 30 days ago, and if not found, the older one you can find"
Thank you !
You can use limit with an order by to retrieve the oldest row that's not more than 30 days old:
SELECT *
FROM stat_table
WHERE customer_id = 1
AND stat_key = 'some_key'
AND created_at > NOW() - INTERVAL 30 DAY
ORDER BY
created_at
LIMIT 1