我有一个LOGGED_LOG表,该表有USERNAME、LOGIN_TIME和LOGOUT_TIME列. 我需要计算每天的登录用户.

表 struct :

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

样本数据:

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

预期结果:

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

我try 用用例替换LOGGED_LOG部分的空注销, 然后在DATE_PERIOD部分创建日列表的临时表, 但在入表获取结果时,只有首日和所有天数的用户.

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

推荐答案

由于您使用的是MySQL5.0,所以在8.0中引入的方便的窗口函数是不可能的.因此,我们必须坚持基本特征.
首先,我强烈建议创建一个名为DATE_LIST的临时表,它列出了LOGGED_LOG表中从最小登录日期到最大注销日期之间的完整日期.我们只需要执行复杂的查询ONCE来创建临时表,并且它在今天剩下的时间里都是合格的.否则,如果我们将代码放到主查询中,就会浪费大量时间.(相信我,如果我们每次都必须执行复杂的事情,那么响应时间将非常费力)此外,通过创建临时表,它提高了可读性.

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

接下来,我们将把LOGGED_LOG表与DATE_LIST临时表连接起来,以获得每个用户名的在线日期.结果将在最终查询中进行处理.注意:为了更好地演示,我在这里添加了ORDER BY子句.但在最后的查询中,我将删除它以节省文件排序时间.

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

在最后阶段,我们只需要使用前一个查询的结果进行聚合.

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

Mysql相关问答推荐

如何在循环查询中删除默认架构?

如何有效地计算共同的朋友/追随者?

为什么我安装MySQL时不能使用3306端口?

MySQL连接序列

使用Check查看过go 两个月是否完成了IRM Meetup

Golang中使用Gorm的Where条件转义字符无法正常工作的问题

获取每个参数的记录,不重复

实体内约束的唯一增量 ID 生成

mysqli 无法以非 root 用户身份连接

MySQL 可以用于将列表排序为三分之三吗?

当mysql中只有一个索引列时,为什么使用范围条件锁定读取会锁定每条记录?

java.lang.NullPointerException:无法调用com.proj.my.repository.OrderRepository.save(Object),因为this.orderRepository为空

在 MySQL 中使用非空条件 LAG() 函数

如何替换 SELECT 查询中重复记录的列?

结果差异(MySQL 5.7 vs MySQL 8.0)

Facebook user_id:big_int、int 还是 string?

SQLSTATE [42000]:语法错误或访问冲突:1055 SELECT 列表的表达式 #3 不在 GROUP BY 子句中并且包含非聚合

我可以在 PHP 中使用 PDO 创建数据库吗?

在网页的 HTML 表中显示 MySQL 数据库表中的值

从另一个表中 Select 具有 id 的行