Home > database >  How can I get daily login user number?
How can I get daily login user number?

Time:01-11

I have a logged_log table, the table have username, login_time and logout_time column. I need to count daily login user.

Table structure:

column name type
username varchar(32)
login_time datetime
logout_time datetime nullable

sample data:

username login_time logout_time
ddd 2023-01-05 23:10:00 null
aaa 2023-01-06 23:10:00 2023-01-06 23:59:00
bbb 2023-01-06 23:35:00 2023-01-07 03:00:00
ccc 2023-01-07 13:35:00 2023-01-07 14:00:00
ccc 2023-01-07 18:35:00 2023-01-07 19:00:00
aaa 2023-01-08 13:35:00 2023-01-09 14:00:00
bbb 2023-01-09 13:35:00 null
ccc 2023-01-09 14:35:00 2023-01-10 14:00:00
aaa 2023-01-10 13:35:00 null

expected results:

date total
2023-01-05 1
2023-01-06 3
2023-01-07 3
2023-01-08 2
2023-01-09 4
2023-01-10 4

I have try to use case to replace the null logout on logged_log part, then create temp table of day list on date_period part, but when join the table to get result, only has first day and total of all days users.

SELECT
   daily_logged_log.date, count( daily_logged_log.username )
FROM (
   SELECT
      date_period.date, logged_log.username
   FROM (
      SELECT curdate() - INTERVAL (a.a   (10 * b.a)   (100 * c.a)   (1000 * d.a) ) DAY as date
      FROM (SELECT 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
      cross join (SELECT 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
      cross join (SELECT 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
      cross join (SELECT 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as d
   ) as date_period
   LEFT JOIN (
      SELECT 
         username, 
         DATE( login_time ) as login_at,
         CASE
            WHEN logout_time IS NULL
            THEN DATE( NOW() )
            ELSE DATE( logout_time )
            END
         as logout_at
      FROM logged_log
      WHERE DATE( login_time ) <= '2023-01-09'
      AND CASE
            WHEN logout_time IS NULL
            THEN DATE( NOW() )
            ELSE DATE( logout_time )
            END >= '2023-01-07'
   ) as logged_log
   ON date_period.date BETWEEN logged_log.login_at AND logged_log.logout_at
   WHERE date_period.date BETWEEN '2023-01-07' AND '2023-01-09'
   GROUP BY date_period.date, logged_log.username
) as daily_logged_log

CodePudding user response:

As you are using MySQL 5.0, the convenient window functions which are introduced in 8.0 are out of questions. So we have to stick to the fundamental features.
First of all, I would strongly suggest creating a temporary table named date_list which lists the complete dates between the minimum login date and the maximum logout date from the logged_log table. We only need to execute the complex query ONCE to make the temp table and it stays eligible for the rest of today. Otherwise, it wastes a lot of time if we put the code into the main query.(Trust me, the reponse time would be painstaking if we have to execute the complicated thing every time) Besides, by making a temp table it improves readability.

create temporary table date_list as
(select  selected_date as each_date from 
(select adddate('1970-01-01',t4.i*10000   t3.i*1000   t2.i*100   t1.i*10   t0.i) selected_date from
 (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
 (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
 (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
 (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
 (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where selected_date between (select min(date(login_time)) from logged_log) and curdate()) 
;
-- let's test it
select * from date_list;

 ------------ 
| each_date  |
 ------------ 
| 2023-01-05 |
| 2023-01-06 |
| 2023-01-07 |
| 2023-01-08 |
| 2023-01-09 |
| 2023-01-10 |
 ------------ 

Next, we shall join the logged_log table with the date_list temporary table to get the ONLINE date for each username. The result will be worked upon in the final query. Note: Here I added the ORDER BY clause for better demonstration. But in the final query I shall remove it to save the filesort time.

select distinct username,each_date
from logged_log l
join
date_list d
on d.each_date between  date(login_time) and ifnull(date(logout_time),curdate())
order by username,each_date 
;
 ---------- ------------ 
| username | each_date  |
 ---------- ------------ 
| aaa      | 2023-01-06 |
| aaa      | 2023-01-08 |
| aaa      | 2023-01-09 |
| aaa      | 2023-01-10 |
| bbb      | 2023-01-06 |
| bbb      | 2023-01-07 |
| bbb      | 2023-01-09 |
| bbb      | 2023-01-10 |
| ccc      | 2023-01-07 |
| ccc      | 2023-01-09 |
| ccc      | 2023-01-10 |
| ddd      | 2023-01-05 |
| ddd      | 2023-01-06 |
| ddd      | 2023-01-07 |
| ddd      | 2023-01-08 |
| ddd      | 2023-01-09 |
| ddd      | 2023-01-10 |
 ---------- ------------ 

At the final stage, we just need to do the aggregation using the result from the previous query.

select each_date as date,count(username) as total
from
(select distinct username,each_date
from logged_log l
join
date_list d
on d.each_date between  date(login_time) and ifnull(date(logout_time),curdate()) ) t
group by each_date
;
 ------------ ------- 
| date       | total |
 ------------ ------- 
| 2023-01-05 |     1 |
| 2023-01-06 |     3 |
| 2023-01-07 |     3 |
| 2023-01-08 |     2 |
| 2023-01-09 |     4 |
| 2023-01-10 |     4 |
 ------------ ------- 
  • Related