Mysql: How to retrieve data on weekly basis - mysql

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;

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

How to SELECT the last 30 days records from SQL, including days with zero?

I want to SELECT from my table the last 30 day records. My queries looks like this:
SELECT DATE(o_date) as date, count(id) AS sum FROM customers WHERE o_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND NOW() GROUP BY o_date
Or this:
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date >= DATE(NOW()) + INTERVAL -30 DAY GROUP BY DATE(o_date)
I want to create a list with dates and count of id-s.
But where I dont have any records in exact day, the query just skip that date. But I want to insert there a zero.
Example:
id
o_date
1
2021-11-23
2
2021-11-22
3
2021-11-20
4
2021-11-20
5
2021-11-19
6
2021-11-18
7
2021-11-18
The result will be this:
date
sum
2021-11-23
1
2021-11-22
1
2021-11-20
2
2021-11-19
1
2021-11-18
2
But where I dont have records like in this example in 2021-11-21 how can I insert to the sum 0?
Thank you!
UPDATE:
I need this query for MariaDB.
For MariaDB,
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY)
AND NOW();
For SQL,
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE DATEDIFF(day,o_date,GETDATE()) < 31
or
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE DATEDIFF(day,o_date,GETDATE()) between 0 and 30
From what I could gather, it should be :
SELECT * FROM customers WHERE o_date BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW();
Link to almost 10 year old post:
MySQL Query - Records between Today and Last 30 Days
Try this query:
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date >= DATE_ADD(NOW(), INTERVAL -30 DAY)
Your real question seems to be about how to show all 30 days, even days with a zero value.
Since you are using MariaDB 10.0 or newer, there is a nifty trick to give all the days in a range:
MariaDB [test]> SELECT '2019-01-01' + INTERVAL seq-1 DAY AS dates FROM seq_1_to_31;
+-----------------------------------+
| dates |
+-----------------------------------+
| 2019-01-01 |
| 2019-01-02 |
| 2019-01-03 |
| 2019-01-04 |
| 2019-01-05 |
| 2019-01-06 | etc.
So, what you do is
SELECT ...
FROM ( select using seq table ) AS dates
JOIN ( your table ) AS yours ON dates.dy = yours.o_date
WHERE ...
Your secondary question about how to ask for a date range -- both of your attempts give the same result with the same performance.

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 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

Select most recent monthly anniversary day from a unix timestamp

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;