Iterate over calendar weeks and fetch data per week - mysql

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.

Related

MySQL query to get sum of difference between Start date and End date

I have a table which has the following data:
+-----------+-----------+
| Date_from | Date_to |
+-----------+-----------+
| 20-NOV-19 | 22-NOV-19 |
+-----------+-----------+
| 10-NOV-19 | 21-NOV-19 |
+-----------+-----------+
| 14-NOV-19 | 26-NOV-19 |
+-----------+-----------+
I need a query to find the sum of the difference between date_from and date_to.
Example:
The difference between 20-Nov-19 and 22-Nov-19 is 2 days
The difference between 10-Nov-19 and 21-Nov-19 is 11 days but the query has to consider it as 9 days because days 20-Nov, 21-Nov are already considered in the first row.
The difference between 14-Nov-19 and 26-Nov-19 is 12 days but the query has to consider it as 4 days because days 14-Nov to 22-Nov are already considered in the above rows.
The query result should be
15 days (2+9+4)
Any help would be much appreciated.
You can use below query to get total.
Please change Table_name with your actual table name
SELECT SUM(TIMESTAMPDIFF(DAY,Date_from,Date_to)) as total FROM Table_name
I used window functions in a sub-query to calculate the difference between date_from and date_to and then subtracting any overlapping days
SELECT SUM(days) FROM
(SELECT CASE WHEN LEAD(date_from) OVER w < date_to THEN DATEDIFF(date_to, date_from) + DATEDIFF(LEAD(date_from) OVER w, date_to)
ELSE DATEDIFF(date_to, date_from)
END AS days
FROM test
WINDOW w AS (ORDER BY date_to)) as d
Note though that this produces the result 16 days, not 15 as in the question, but then again so does
SELECT DATEDIFF('2019-11-26', '2019-11-10')
You can use CTE to generate all the dates between the range and get the distinct count().It will give you the total number of days as 17. Since you want difference you have to subtract by 2(start date and end date)
WITH recursive Date_Ranges AS (
select datefrom as Dt,dateto from mydates
union all
select dt + interval 1 day , dateto
from Date_Ranges
where dt < dateto)
select count(distinct(dt))-2 from Date_Ranges
DEMO HERE

group by date containing certain date only

I have tried looking at some similar examples like group by date range and weekdays etc but I couldnt fix it on my query.
as per my sample data screenshot, I need to only return
sum(salesamount)/sum(salescount) for week 1
and
sum(salesamount)/sum(salescount) for week 2.
Each of the week contain 5 days (in this example is wednesday - sunday).
My Attempt:
select salesstartdate, date_add(salesstartdate, interval 5 day) as gdate,
salesamount, salescount, sum(salesamount)/sum(salescount) as ATV
from testing
group by gdate;
My desired output is:
Week 1 15.34173913
Week 2 15.80365088
Calculation to get week 1 is (3507.1+3639.97+5258.77+8417.04+5994.48)/(285+273+344+478+368)
Calculation to get week 2 is the same as above except the date would now be from 8 to 12 of June.
You can do it with a subquery. In order to first group your result set properly and then execute aggregation on it:
SELECT
concat('WEEK', ' ', weekno) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
(
SELECT
salesstartdate,
salesamount,
salescount,
WEEKOFYEAR(salesstartdate) as weekno -- get the week number of the current year
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
) as weeks
GROUP BY
weekno
I have used 2 MySQL functions here:
WEEKOFYEAR()
WEEKDAY()
Output:
WEEK 23 | 2016-06-08 | 2016-06-12 | 15.8040
WEEK 24 | 2016-06-16 | 2016-06-19 | 15.9323
and without subquery as well:
SELECT
concat('WEEK', ' ', WEEKOFYEAR(salesstartdate)) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
GROUP BY
WEEKOFYEAR(salesstartdate)
You can do this way
select SUBDATE(salesstartdate, WEEKDAY(salesstartdate)) as week_range
, sum(salesamount)/sum(salescount)
from testing
where salesstartdate between SUBDATE(salesstartdate, WEEKDAY(salesstartdate))
and date_add(SUBDATE(salesstartdate, WEEKDAY(salesstartdate)), interval 5 day))
Group by week_range

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;

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;

MySQL query help with grouping and adding

I have a table called user_logins which tracks user logins into the system. It has three columns, login_id, user_id, and login_time
login_id(INT) | user_id(INT) | login_time(TIMESTAMP)
------------------------------------------------------
1 | 4 | 2010-8-14 08:54:36
1 | 9 | 2010-8-16 08:56:36
1 | 9 | 2010-8-16 08:59:19
1 | 3 | 2010-8-16 09:00:24
1 | 1 | 2010-8-16 09:01:24
I am looking to write a query that will determine the number of unique logins for each day if that day has a login and only for the past 30 days from the current date. So for the output should look like this
logins(INT) | login_date(DATE)
---------------------------
1 | 2010-8-14
3 | 2010-8-16
in the result table 2010-8-16 only has 3 because the user_id 9 logged in twice that day and him logging into the system only counts as 1 login for that day. I am only looking for unique logins for a particular day. Remember I only want the past 30 days so its like a snapshot of the last month of user logins for a system.
I have attempted to create the query with little success what I have so far is this,
SELECT
DATE(login_time) as login_date,
COUNT(login_time) as logins
FROM
user_logins
WHERE
login_time > (SELECT DATE(SUBDATE(NOW())-1)) FROM DUAL)
AND
login_time < LAST_DAY(NOW())
GROUP BY FLOOR(login_time/86400)
I know this is wrong and this returns all logins only starting from the beginning of the current month and doesn't group them correctly. Some direction on how to do this would be greatly appreciated. Thank you
You need to use COUNT(DISTINCT ...):
SELECT
DATE(login_time) AS login_date,
COUNT(DISTINCT login_id) AS logins
FROM user_logins
WHERE login_time > NOW() - interval 30 day
GROUP BY DATE(login_time)
I was a little unsure what you wanted for your WHERE clause because your question seems to contradict itself. You may need to modify the WHERE clause depending on what you want.
As Mark suggests you can use COUNT(DISTINCT...
Alternatively:
SELECT login_day, COUNT(*)
FROM (
SELECT DATE_FORMAT(login_time, '%D %M %Y') AS login_day,
user_id
FROM user_logins
WHERE login_time>DATE_SUB(NOW(), INTERVAL 1 MONTH)
GROUP BY DATE_FORMAT(login_time, '%D %M %Y'),
user_id
)
GROUP BY login_day