如何在 MySQL 中使用 ROW_NUMBER 来检查每位员工每天的行数?

Posted

技术标签:

【中文标题】如何在 MySQL 中使用 ROW_NUMBER 来检查每位员工每天的行数?【英文标题】:How do I use ROW_NUMBER in MySQL to check number of rows per day per employee? 【发布时间】:2021-08-11 17:21:40 【问题描述】:

我在一个表格中存储打卡和打卡数据,如下所示:

id empid punchtime
74 4 2021-08-04 17:11:54
171 4 2021-08-06 13:47:45
202 4 2021-08-09 10:14:01
271 4 2021-08-09 18:20:01
308 4 2021-08-10 11:14:54
343 4 2021-08-10 14:46:21
349 4 2021-08-10 15:22:10
380 4 2021-08-10 18:10:58
406 4 2021-08-11 10:13:48

我想检查每天的打卡和打卡。因此,每个奇数的第 n 拳被认为是一个拳入,每个偶数的第 n 个拳被认为是一个拳出。我还有一个包含员工信息的表格,即员工 ID 和姓名。

这是我的 SQL 查询:

WITH PunchDataPlus AS (
SELECT pd.*, ei.Name, ROW_NUMBER() OVER(PARTITION BY EmpID, datediff(PunchTime, '2021-08-11')=0 ORDER BY PunchTime) AS RN 
FROM PunchData pd LEFT JOIN EmployeeInfo ei ON pd.EmpID=ei.EmpID
), FinalPunchData AS (
SELECT PunchDataPlus.*, CASE WHEN RN % 2 =1 THEN 'IN' ELSE 'OUT' END AS inOutCol FROM PunchDataPlus
) 
SELECT * FROM FinalPunchData WHERE EmpID=4 ORDER BY PunchTime DESC;

这个查询的问题是datediff 一次只能比较一天。如何与每一天进行比较,以便每一天都有自己的输入/输出值?

SQL查询的结果:

id EmpID PunchTime Name RN inOutCol
406 4 2021-08-11 10:13:48 redacted 1 IN
380 4 2021-08-10 18:10:58 redacted 8 OUT
349 4 2021-08-10 15:22:10 redacted 7 IN
343 4 2021-08-10 14:46:21 redacted 6 OUT
308 4 2021-08-10 11:14:54 redacted 5 IN
271 4 2021-08-09 18:20:01 redacted 4 OUT
202 4 2021-08-09 10:14:01 redacted 3 IN
171 4 2021-08-06 13:47:45 redacted 2 OUT
74 4 2021-08-04 17:11:54 redacted 1 IN

我正在寻找的结果:

id EmpID PunchTime Name RN inOutCol
406 4 2021-08-11 10:13:48 redacted 1 IN
380 4 2021-08-10 18:10:58 redacted 4 OUT
349 4 2021-08-10 15:22:10 redacted 3 IN
343 4 2021-08-10 14:46:21 redacted 2 OUT
308 4 2021-08-10 11:14:54 redacted 1 IN
271 4 2021-08-09 18:20:01 redacted 2 OUT
202 4 2021-08-09 10:14:01 redacted 1 IN
171 4 2021-08-06 13:47:45 redacted 1 IN
74 4 2021-08-04 17:11:54 redacted 1 IN

【问题讨论】:

所以你总是想假设他们在午夜出去(无论打卡时间在哪个时区,我真的希望是UTC)? @ysth 是的,没错。 【参考方案1】:

您可以尝试将datediff(PunchTime, '2021-08-11')=0 更改为DATE(PunchTime)。这将仅提取您的日期时间值的日期部分,从而根据需要按每天对您的数据进行分区。

让我知道这是否适合你。

【讨论】:

这正是我想要的。谢谢!【参考方案2】:

你不需要比较天,你只需要你的窗口是empid和天:

select pd.*,
    case (row_number() over (partition by empid, date(punchtime) order by punchtime)) % 2
        when 1 then 'IN'
        else 'OUT'
    end in_out
from PunchData pd

fiddle

但你真的不应该这样做。您应该将 IN/OUT 与打孔数据一起存储;当有人进出时,他们知道自己的意图,并且应该存储该意图。即使由于某种原因您只能获得原始时间,您也应该在导入数据时计算 IN/OUT 并将其存储,以便推断意图时的错误可以在数据中得到纠正,而不是持久化。

这种方法的其他问题:您不支持在午夜之前和之后打卡。如果您以 UTC 格式存储时间(您应该这样做),则 UTC 午夜可能不是您划定所有人都外出的界限的不合适时间。如果您没有以 UTC 格式存储时间,则会遇到夏令时问题,即在凌晨 1:45 打卡(以美国 DST 规则为例)之后可能会在凌晨 1:15 打卡,出现乱序(以及使时间计算不明确)。

【讨论】:

对我的回答添加了一些评论【参考方案3】:

请检查这个。使用特定 empid 搜索时启用 where 子句,否则禁用它。

-- mysql(v5.8)
SELECT t.id
     , t.empid
     , t.punchtime
     , ei.name
     , t.row_num RN
     , CASE t.row_num % 2
            WHEN 1 THEN 'IN'
            ELSE 'OUT'
       END inOutCol
FROM (SELECT id
           , empid
           , punchtime
           , ROW_NUMBER() OVER (PARTITION BY empid, CAST(punchtime AS DATE) ORDER BY punchtime) row_num
      FROM PunchData
      WHERE empid = 4) t
LEFT JOIN EmployeeInfo ei
       ON t.empid = ei.empid
ORDER BY t.punchtime DESC;

请查看网址https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4236c0424e87fe25be6033dcc93b4856

【讨论】:

这里不需要子查询。不确定 5.7 的评论是干什么用的;这在 5.7 中不起作用。 嗨@ysth 尝试写这个以提高可读性并考虑性能。这里提到版本是因为你知道低版本的 mysql 不支持 ROW_NUMBER()。 嗨@AidanHakimian,您可以在 PunchData 表中添加标志列,以区分打卡和打卡。即 1 = IN , 2 = OUT【参考方案4】:

您没有提供员工表,因此我将其从查询中删除。

请将其添加到您的数据库中

CREATE TABLE PunchData
    (`id` int, `empid` int, `punchtime` varchar(19))
;
    
INSERT INTO PunchData
    (`id`, `empid`, `punchtime`)
VALUES
    (74, 4, '2021-08-04 17:11:54'),
    (171, 4, '2021-08-06 13:47:45'),
    (202, 4, '2021-08-09 10:14:01'),
    (271, 4, '2021-08-09 18:20:01'),
    (308, 4, '2021-08-10 11:14:54'),
    (343, 4, '2021-08-10 14:46:21'),
    (349, 4, '2021-08-10 15:22:10'),
    (380, 4, '2021-08-10 18:10:58'),
    (406, 4, '2021-08-11 10:13:48')
;
WITH PunchDataPlus AS (
SELECT pd.*
, ROW_NUMBER() OVER(PARTITION BY EmpID, DATE(PunchTime) ORDER BY PunchTime) AS RN 
FROM PunchData pd 

), FinalPunchData AS (
SELECT PunchDataPlus.*, CASE WHEN RN % 2 =1 THEN 'IN' ELSE 'OUT' END AS inOutCol FROM PunchDataPlus
) 
SELECT * FROM FinalPunchData WHERE EmpID=4 ORDER BY PunchTime DESC;
编号 |空 |打卡时间 |注册护士 | inOutCol --: | ----: | :----------------- | -: | :-------- 406 | 4 | 2021-08-11 10:13:48 | 1 |在 380 | 4 | 2021-08-10 18:10:58 | 4 |出去 349 | 4 | 2021-08-10 15:22:10 | 3 |在 第343章4 | 2021-08-10 14:46:21 | 2 |出去 308 | 4 | 2021-08-10 11:14:54 | 1 |在 271 | 4 | 2021-08-09 18:20:01 | 2 |出去 202 | 4 | 2021-08-09 10:14:01 | 1 |在 171 | 4 | 2021-08-06 13:47:45 | 1 |在 74 | 4 | 2021-08-04 17:11:54 | 1 |在

db小提琴here

【讨论】:

以上是关于如何在 MySQL 中使用 ROW_NUMBER 来检查每位员工每天的行数?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 中用于插入的 ROW_NUMBER() 等效项[重复]

如何在 MySQL 中对 GROUP BY 结果的 SELECT INTO 使用自动增量?

mysql 怎么才能做到rownumber序号

在 MariaDB 中使用 ROW_NUMBER() 函数的问题

Mysql 里面使用row_number() 的用法和注意

MySQL 中的 ROW_NUMBER()