How to get daily active users considering previous subscription days? - mysql

I am using MySQL to make some data analysis on subscribers and I would like to sort out daily active subscribers since the service launch.
i have a subscription table like below
id | subscriptiondate | unsubscriptiondate
---|------------------|--------------------
1 | 2020-02-12 | null
---|------------------|--------------------
2 | 2020-03-20 | 2020-04-01
---|------------------|--------------------
3 | 2020-03-10 | null
---|------------------|--------------------
4 |2020-04-02 | null
and i expect a result like:
date | active_user
-----------|---------------------------
2020-02-12 | 1
-----------|------------------
2020-03-10 | 2
-----------|------------------
2020-03-20 | 3
-----------|------------------
2020-04-02 | 3
A subscriber opted out the 2020-04-01, that is why we have 3 active subscribers the 2020-04-02.
here is my SQL script, someone could check and assist me to achieve my goal?
SELECT
COUNT(distinct is) AS active_user,
date(subscriptiondate) as day
FROM
subscriptions
WHERE
subscriptiondate in (select subscriptiondate from subscriptions where subscriptiondate <=date(subscriptiondate))
AND (unsubscriptiondate is NULL or unsubscriptiondate>date(subscriptiondate))
GROUP BY
day
ORDER BY day ASC*

You can "unpivot" the table and aggregate with a cumulative sum:
select date, sum(inc) as change_on_date,
sum(sum(inc)) over (order by date) as active_on_day
from ((select subscriptiondate as date, 1 as inc from subscriptions
) union all
(select unsubscriptiondate, -1 from subscriptions
)
) s
group by date;

Related

Calculating average time between dates in SQL

Using MySQL, I'm trying to figure out how to answer the question: What is the average number of months between users creating their Nth project?
Expected result:
| project count | Average # months |
| 1 | 0 | # On average, it took 0 months to create the first project (nothing to compare to)
| 2 | 12 | # On average, it takes a user 12 months to create their second project
| 3 | 3 | # On average, it takes a user 3 months to create their third project
My MySQL table represents projects created by users. The table can be summarized as:
| user_id | project created at |
|---------|--------------------|
| 1 | Jan 1, 2020 1:00 pm|
| 1 | Feb 2, 2020 3:45 am|
| 1 | Nov 6, 2020 0:01 am|
| 1 | Mar 4, 2021 5:01 pm|
|------------------------------|
| 2 | Another timestamp |
| 2 | Another timestamp |
| 2 | Another timestamp |
| 2 | Another timestamp |
| 2 | Another timestamp |
| 2 | Another timestamp |
|------------------------------|
| ... | Another timestamp |
| ... | Another timestamp |
Some users will have one project while some may have hundreds.
Edit: Current Implementation
with
paid_self_serve_projects_presentation as (
select
`Paid Projects`.owner_email
`Owner Email`,
row_number() over (partition by `Paid Projects`.owner_uuid order by created_at)
`Project Count`,
day(`Paid Projects`.created_at)
`Created Day`,
month(`Paid Projects`.created_at)
`Created Month`,
year(`Paid Projects`.created_at)
`Created Year`,
`Paid Projects`.created_at
`Created`
from self_service_paid_projects as `Paid Projects`
order by `Paid Projects`.owner_uuid, `Paid Projects`.created_at
)
select `Projects`.* from paid_self_serve_projects_presentation as `Projects`
You can use window functions. I am thinking row_number() to enumerate the projects of each user ordered by creation date, and lag() to get the date when the previous project was created:
select rn, avg(datediff(created_at, lag_created_at)) avg_diff_days
from (
select t.*,
row_number() over(partition by user_id order by created_at) rn,
lag(created_at, 1, created_at) over(partition by user_id order by created_at) lag_created_at
from mytable t
) t
group by rn
This gives you the average difference in days, which is somehow more accurates that months. If you really want months, then use timestampdiff(month, lag_created_at, created_at) instead of datediff() - but be aware that the function returns an integer value, hence there is a loss of precision.

Finding total active hours by calculating difference between TimeDate records

I have a table to register users logs every one minute and other activities using DateTime for each user_id
This is a sample data of my table
id | user_id | log_datetime
------------------------------------------
1 | 1 | 2016-09-25 13:01:08
2 | 1 | 2016-09-25 13:04:08
3 | 1 | 2016-09-25 13:07:08
4 | 1 | 2016-09-25 13:10:08
5 | 2 | 2016-09-25 13:11:08
6 | 1 | 2016-09-25 13:13:08
7 | 2 | 2016-09-25 13:13:09
8 | 2 | 2016-09-25 13:14:10
I would like to calculate the total active time on the system
UPDATE: Expected Output
For Example user_id 1 his total available time should be 00:12:00
Since his hours and seconds are same so I'll just subtract last log from previous then previous from next previous and so on then I'll sum all subtracted values
this a simple for
Simply I want to loop through the data from last record to first record with in my range
this is a simple formula I hope that make my question clear
SUM((T< n > - T< n-1 >) + (T< n-1 > - T< n-2 >) ... + (T< n-x > - T< n-first >))
Since user_id 1 his hours and seconds are the same then I'll calculate the minutes only.
(13-10)+(10-7)+(7-4)+(4-1) = 12
user_id | total_hours
---------------------------------
1 | 00:12:00
2 | 00:03:02
I did this code
SET #start_date = '2016-09-25';
SET #start_time = '13:00:00';
SET #end_date = '2016-09-25';
SET #end_time = '13:15:00';
SELECT
`ul1`.`user_id`, SEC_TO_TIME(SUM(TIME_TO_SEC(`dl1`.`log_datetime`))) AS total_hours
FROM
`users_logs` AS `ul1`
JOIN `users_logs` AS `ul2`
ON `ul1`.`id` = `ul2`.`id`
WHERE
`ul1`.`log_datetime` >= CONCAT(#start_date, ' ', #start_time)
AND
`ul2`.`log_datetime` <= CONCAT(#end_date, ' ', #end_time)
GROUP BY `ul1`.`user_id`
But this code Sum all Time not getting the difference. This is the output of the code
user_id | total_hours
---------------------------------
1 | 65:35:40
2 | 39:38:25
How can I calculate the Sum of all difference datetime, then I want to display his active hours every 12 hours (00:00:00 - 11:59:59) and (12:00:00 - 23:59:59) with in selected DateTime Period at the beginning of the code
So the output would look like this (just an dummy example not from given data)
user_id | total_hours | 00_12_am | 12_00_pm |
-------------------------------------------------------
1 | 10:10:40 | 02:05:20 | 08:05:20 |
2 | 04:10:20 | 01:05:10 | 03:05:30 |
Thank you
So you log every minute and if a user is available there is a log entry.
Then count the logs per user, so you have the number of total minutes.
select user_id, count(*) as total_minutes
from user_logs
group by user_id;
If you want them displayed as time use sec_to_time:
select user_id, sec_to_time(count(*) * 60) as total_hours
from user_logs
group by user_id;
As to conditional aggregation:
select
user_id,
count(*) as total_minutes,
count(case when hour(log_datetime) < 12 then 1 end) as total_minutes_am,
count(case when hour(log_datetime) >= 12 then 1 end) as total_minutes_pm
from user_logs
group by user_id;
UPDATE: In order to count each minute just once count distinct minutes, i.e. DATE_FORMAT(log_datetime, '%Y-%m-%d %H:%i'). This can be done with COUNT(DISTINCT ...) or with a subquery getting distinct values.
The complete query:
select
user_id,
count(*) as total_minutes,
count(case when log_hour < 12 then 1 end) as total_minutes_am,
count(case when log_hour >= 12 then 1 end) as total_minutes_pm
from
(
select distinct
user_id,
date_format(log_datetime, '%y-%m-%d %h:%i') as log_moment,
hour(log_datetime) as log_hour
from.user_logs
) log
group by user_id;

How to group by month and return zero if no value for certain month?

This is my mysql income table.
+----+------------------+---------------------------+------------+---------+
| id | title | description | date | amount |
+----+------------------+---------------------------+------------+---------+
| 1 | Vehicle sales up | From new sale up | 2016-09-09 | 9999.99 |
| 2 | Jem 2 Sales | From rathnapura store | 2016-05-15 | 9545.25 |
| 3 | Jem 2 Sales 2 | From rathnapura store | 2016-05-15 | 9545.25 |
| 4 | Jem 2 Sales 2 | From rathnapura store 234 | 2016-05-15 | 9545.25 |
+----+------------------+---------------------------+------------+---------+
The field 'date' is standard sql date. And I executed this query in order to take sum of incomes by month and return zero if no income from a certain month. I want zeros if no income from a certain month because i want to display these data in a chart.
This is the query.
SELECT MONTHNAME(`date`) AS mName, MONTH(`date`) AS mOrder, ifnull(sum(amount),0) AS total_num FROM income GROUP BY mOrder ORDER BY mOrder DESC
But I only get a output like follows. No zeros if no values in other months. This is the output.
+-----------+--------+-----------+
| mName | mOrder | total_num |
+-----------+--------+-----------+
| September | 9 | 9999.99 |
| May | 5 | 28635.75 |
+-----------+--------+-----------+
And I want other months in above table and total_num as zero. How can I do this? There's same kind of question there too. But no working answer.
Group by month and return 0 if data not found
Please help me to solve this issue. The language I use for this application is Node.JS :)
Have a table of all the months and then left join to your table:
SELECT MONTHNAME(m.month) AS mName,
MONTH(m.month) AS mOrder,
ifnull(sum(amount),0) AS total_num
from months m
left join income i
on m.month = i.date
GROUP BY mOrder
ORDER BY mOrder DESC
If you don't want to create a months table then you can:
(select STR_TO_DATE('01/01/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/02/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/03/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/04/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/05/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/06/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/07/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/08/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/09/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/10/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/11/2016', '%d/%m/%Y') as month union
select STR_TO_DATE('01/12/2016', '%d/%m/%Y') as month)
You should create a CALENDAR table, with the precision you need, in this case months.
+-----------+
| Month |
+-----------+
| January |
| February |
.......
And Join on it
Maybe this it's not the best way to do it, but it will solve your problem. As a quick soution:
SELECT 'January' AS mName, 1 AS mOrder, COALESCE(SUM(amount),0) AS total_num
FROM income i
WHERE month(i.date) = 1
UNION
SELECT 'February' AS mName, 2 AS mOrder, COALESCE(SUM(amount),0) AS total_num
FROM income i
WHERE month(i.date) = 2
UNION
...and go on

SQL group: count multiple things

Right now I've got the following (My)SQL-Statement which returns the amount of entrys based on hour.
SELECT
COUNT(*) AS amount
HOUR(date) AS hour
-- [1]
FROM
table
GROUP BY
HOUR(date)
But I actually want another result that contains the amount of days the hour appeared. Basicly something like:
[1] = COUNT(DAY(date), MONTH(date), YEAR(date)) AS day_count
Example:
id | date
0 | 01/01/2001 5:15
1 | 01/01/2001 5:10
2 | 01/01/2001 6:03
3 | 01/01/2001 7:04
4 | 02/01/2001 5:00
Should return
amount | hour | day_count
3 | 5 | 2
1 | 6 | 1
1 | 7 | 1
I think you just need to part out the days by hour and day, and group by them...
SELECT count(*) as `amount`
, count(hour(date)) as `Hour`
, count(day(date)) as `Day_Count`
FROM table
GROUP BY hour(Date), day(date)

Pre transaction Rolling balance SQL Server 2008

I have a couple of relatively straight forward tables (examples below).
One has accounts details in:
AccountNo | CurrentBalance* | ReferredBalance
12345 | £1254.25 | 1500.00
Current balance refreshes hourly so is not static
Another one has payments:
Accountno | TranasctionNo | TransDate | Amount |
123456 | 558745489 | 01/01/2015 | £25.99 |
123456 | 558745490 | 01/02/2015 | £25.99 |
123456 | 558745491 | 01/02/2015 | £25.99 |
I’ve been tasked with keep a rolling balance based on payments received including a pre transaction amount.
So for example I need to an output to mirror :
AccountNo | TransactionDate | PreTransactionBalance | Amount | Current Balance|
123456 | 01/01/2015 | 1254.25 | 25.99 | 1228.26|
123456 | 01/02/2015 | 1228.26 | 25.99 | 1202.27|
123456 | 01/03/2015 | 1202.27 | 25.99 | 1176.28|
123456 | 01/03/2015 | 1176.28 | -100 | 1276.28|
I’ve added the negative in as it will need to calculate debits as well as credits.
Can't quiet work out how to get the rolling pre-transaction totals to work. Hopefully this is clear enough!
You can use SUM(amount) OVER() to get rolling amount based on the TransactionNo and then calculate pre transaction and current balance from this value.
Sample Data
DECLARE #Account TABLE
(
AccountNo VARCHAR(10) ,
CurrentBalance MONEY,
ReferredBalance MONEY
)
DECLARE #AccountPayment TABLE
(
Accountno VARCHAR(10),TranasctionNo VARCHAR(10),TransDate DATE,Amount MONEY
)
insert into #Account VALUES
('12345','£1254.25','1500.00');
insert into #AccountPayment values
('12345','558745489','01/01/2015','£25.99'),
('12345','558745490','01/02/2015','£25.99'),
('12345','558745491','01/02/2015','£25.99'),
('12345','558745492','01/02/2015','-100');
Query
SELECT AP.AccountNo,
TransDate,
A.CurrentBalance - SUM(amount) OVER(ORDER BY TranasctionNo) + amount as PreTransactionBalance ,
amount,
A.CurrentBalance - SUM(amount) OVER(ORDER BY TranasctionNo) Current_Balance
FROM #AccountPayment AP
INNER JOIN #Account A
ON AP.Accountno = A.Accountno
Edit
It seems ORDER BY is not supported with in SUM() OVER() in SQL Server 2008 / SQL Server 2008 R2. As per msdn
ORDER BY Clause cannot be used with aggregate window functions.
We can use CROSS APPLY like this.
SELECT AP.AccountNo,
TransDate,
A.CurrentBalance - pre_amount as PreTransactionBalance ,
amount,
A.CurrentBalance - pre_amount - amount Current_Balance
FROM #AccountPayment AP
INNER JOIN #Account A
ON AP.Accountno = A.Accountno
CROSS APPLY
(
select ISNULL(sum(amount),0) as pre_amount
from #AccountPayment ap1
where ap1.Accountno = ap.Accountno and ap1.TranasctionNo < ap.TranasctionNo
) as b
Output
AccountNo TransDate PreTransactionBalance amount Prev_Payments
12345 2015-01-01 1254.25 25.99 1228.26
12345 2015-01-02 1228.26 25.99 1202.27
12345 2015-01-02 1202.27 25.99 1176.28
12345 2015-01-02 1176.28 -100.00 1276.28
SQL Fiddle