【问题标题】:Get all the Attendance and Fullname even no attendance on date with daterange mysql使用日期范围mysql获取所有出勤和全名,即使没有出勤
【发布时间】:2022-01-28 05:41:02
【问题描述】:

如何在 Mysql 中执行此操作以获取所有用户,即使在所选日期范围内没有记录或缺席?

attendance_tbl

ID user_id time_in time_out created_at
1 001 2022-01-01 08:00:00 2022-01-01 17:00:00 2022-01-03 08:00:00
2 002 2022-01-01 08:15:24 2022-01-01 17:00:00 2022-01-03 08:15:24
3 003 2022-01-02 08:44:55 2022-01-02 17:00:00 2022-01-04 08:44:55
4 004 2022-01-03 08:40:22 2022-01-03 17:00:00 2022-01-04 08:40:22

users_tbl

ID user_id f_name
1 001 John Doe
2 002 Jane Doe
3 003 Ronal Black
4 004 Lucy White

预计输出日期范围:2022-01-01 至 2022-01-03 将获取所有用户全名

ID user_id Date f_name time_in time_out created_at
1 001 Jan 1 2022 John Doe 2022-01-01 08:00:00 2022-01-01 17:00:00 2022-01-03 08:00:00
2 002 Jan 1 2022 Jane Doe 2022-01-01 08:15:24 2022-01-01 08:15:24 2022-01-03 08:00:00
3 003 Jan 1 2022 Ronal Black
4 004 Jan 1 2022 Lucy White
5 001 Jan 2 2022 John Doe
6 002 Jan 2 2022 Jane Doe
7 003 Jan 2 2022 Ronal Black 2022-01-02 17:00:00 2022-01-02 17:00:00 2022-01-02 17:00:00
8 004 Jan 2 2022 Lucy White
9 001 Jan 3 2022 John Doe
10 002 Jan 3 2022 Jane Doe
11 003 Jan 3 2022 Ronal Black
12 004 Jan 3 2022 Lucy White 2022-01-04 17:00:00 2022-01-04 17:00:00 2022-01-04 17:00:00

【问题讨论】:

  • 删除那些外部链接,而是将所有相关示例数据作为文本直接包含在您的问题中。
  • 请修复您的示例表数据,特别是在users_tbl 中有点混乱,user_id 列是时间格式,attendance_tbl 中的user_id 是空白的。
  • @TheCoolCat 我已经编辑了帖子。
  • 感谢您的回复,但请在您的问题中添加详细信息。这使得现在和将来阅读问题和答案的其他人的一切都变得更加清晰。您没有具体解决我关于需要显示没有用户在场的日期的问题。例如,如果“2022 年 1 月 23 日”(星期日)的出席记录中没有条目,您是否仍需要在该日期为所有用户提供一行?还是每天都会有至少一个用户在场?出去吃午饭的用户会有多个条目吗?我们需要 MIN(time_in) 和 MAX(time_out) 吗?
  • 请通过编辑将所有说明添加到您的问题中。这样更容易理解问题

标签: mysql


【解决方案1】:

鉴于您想要包含缺失的数据,我们需要首先获取所需时间段的日期范围。使用用户变量来存储和增加计数器值是一种高效的方式 -

SELECT
    '2022-01-01' + INTERVAL @row_number DAY `date`,
    @row_number := @row_number + 1
FROM `attendance_tbl`, (SELECT @row_number := 0) AS `x`
LIMIT 31 /* 31 days in January */

如果您有一个具有连续整数序列的表(自动递增的 PK 没有删除),您可以改用它 -

SELECT '2022-01-01' + INTERVAL (`id` - 1) DAY `date`
FROM `attendance_tbl`
WHERE `id` <= 31 /* 31 days in January */
ORDER BY `id` ASC

然后我们添加一个交叉连接来构建完整的日期和用户集 -

SELECT *
FROM (
    SELECT '2022-01-01' + INTERVAL (`id` - 1) DAY `date`
    FROM `attendance_tbl`
    WHERE `id` <= 31
    ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`

通过这两个表之间的交叉连接,我们得到了笛卡尔积(两组的所有组合)。然后,我们通过对出勤数据使用左连接更进一步 -

SELECT
    `u`.`user_id`,
    DATE_FORMAT(`d`.`date`, '%b %e %Y') `date`,
    `u`.`f_name`,
    `a`.`time_in`,
    `a`.`time_out`
FROM (
    SELECT
        '2022-01-01' + INTERVAL (`id` - 1) DAY `date`,
        (SELECT TIMESTAMP(`date`, '00:00:00')) `begin`,
        (SELECT TIMESTAMP(`date`, '23:59:59')) `end`
    FROM `attendance_tbl`
    WHERE `id` <= 31
    ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`
LEFT JOIN `attendance_tbl` `a`
    ON `u`.`user_id` = `a`.`user_id`
    AND `a`.`time_in` BETWEEN `d`.`begin` AND `d`.`end`
ORDER BY `d`.`date`, `u`.`user_id`

如果您的 attendance_tbl 每天每个用户可以有超过 1 行,那么您需要在选择列表中添加 GROUP BY d.date, u.user_id 和 aggregate_functions。

我已将beginend 添加到派生表中。这是为了允许对连接使用索引。虽然attendance_tbl 很小,但这并不重要,但随着表格的增长,它会变得更重要。在 (user_id, time_in) 上添加索引将对长期性能产生巨大影响。

Here's a db<>fiddle for you to play with.

要使用 PDO 从 PHP 运行它,您可以执行以下操作 -

<?php

$pdo = new PDO($dsn, $user, $password);

$sql = "SELECT
    `u`.`user_id`,
    DATE_FORMAT(`d`.`date`, '%b %e %Y') `date`,
    `u`.`f_name`,
    `a`.`time_in`,
    `a`.`time_out`
FROM (
    SELECT
        :START_DATE + INTERVAL (`id` - 1) DAY `date`,
        (SELECT TIMESTAMP(`date`, '00:00:00')) `begin`,
        (SELECT TIMESTAMP(`date`, '23:59:59')) `end`
    FROM `attendance_tbl`
    WHERE `id` <= :DAYS_RANGE
    ORDER BY `id` ASC
) d
CROSS JOIN `users_tbl` `u`
LEFT JOIN `attendance_tbl` `a`
    ON `u`.`user_id` = `a`.`user_id`
    AND `a`.`time_in` BETWEEN `d`.`begin` AND `d`.`end`
ORDER BY `d`.`date`, `u`.`user_id`";

$stmt = $pdo->prepare($sql);

$startDate = new DateTime('2022-01-01');
$endDate   = new DateTime('2022-02-01');

$interval = $startDate->diff($endDate, true);
$daysRange = $interval->days + 1;

// Execute the statement
$stmt->execute([
    ':START_DATE' => $startDate->format('Y-m-d'),
    ':DAYS_RANGE' => $daysRange]
);

$attendance = $stmt->fetchAll(PDO::FETCH_OBJ);

【讨论】:

  • 如何设置具体到日期?就像我的意思是从 1 月 1 日到 2 月 1 日?因为日期范围根据 datetimepicker 是动态的。非常感谢。
  • 我的查询对于日期范围有点尴尬,因为它使用的是开始日期和范围内的天数。子查询中的第一个表达式 - '2022-01-01' + INTERVAL (id - 1) DAY date 和 where 子句 WHERE id &lt;= 31 应进行参数化,然后在执行前将 start datedays in range 的值传递给查询。如何实现这一点取决于您的客户端应用程序所用的语言。
  • 是的,我使用的是 PHP 8.0,谢谢
  • 这正是我正在寻找的,但我可以将其更改为 AND a.time_in BETWEEN d.begin AND d.end 到此 AND @ 987654342@.time_in 在“2022-01-16”和“2022-01-31”之间?注意:日期范围是动态的。
  • 不,这是从日期到出席的加入条件之一。您需要更改的唯一位是将 Y-m-d 字符串传递给 new DateTime()。如果您收到不同格式的日期,则可以更改为 $startDate = DateTime::createFromFormat('m/d/Y', '01/16/2022');
【解决方案2】:

检查这个。在这里,我两次调用attendance_tbl,一次用于创建日期和用户列表,另一次用于获取数据(时间进入和超时)。并按照@nnichols 的建议使用BETWEEN 来过滤您喜欢的选定范围,我刚刚意识到这一点。

select u.`user_id`, date(a.time_in) as `date`, u.`f_name`, b.`time_in`, b.`time_out`, b.created_at from attendance_tbl a
join users_tbl u
left join attendance_tbl b on b.`user_id`=u.`user_id` and date(b.`time_in`)=date(a.`time_in`) 
WHERE DATE(a.time_in) BETWEEN '2022-01-01' AND '2022-01-31'
GROUP BY `date`, u.user_id;

结果

user_id  date        f_name       time_in              time_out             created_at           
-------  ----------  -----------  -------------------  -------------------  ---------------------
001      2022-01-01  John Doe     2022-01-01 08:00:00  2022-01-01 17:00:00  2022-01-03 08:00:00  
002      2022-01-01  Jane Doe     2022-01-01 08:15:24  2022-01-01 17:00:00  2022-01-03 08:15:24  
003      2022-01-01  Ronal Black  (NULL)               (NULL)               (NULL)               
004      2022-01-01  Lucy White   (NULL)               (NULL)               (NULL)               
001      2022-01-02  John Doe     (NULL)               (NULL)               (NULL)               
002      2022-01-02  Jane Doe     (NULL)               (NULL)               (NULL)               
003      2022-01-02  Ronal Black  2022-01-02 08:44:55  2022-01-02 17:00:00  2022-01-04 08:44:55  
004      2022-01-02  Lucy White   (NULL)               (NULL)               (NULL)               
001      2022-01-03  John Doe     (NULL)               (NULL)               (NULL)               
002      2022-01-03  Jane Doe     (NULL)               (NULL)               (NULL)               
003      2022-01-03  Ronal Black  (NULL)               (NULL)               (NULL)               
004      2022-01-03  Lucy White   2022-01-03 08:40:22  2022-01-03 17:00:00  2022-01-04 08:40:22  

对于ID 列,只需创建一个带有AUTO_INCREMENT id 的表并插入您选择的数据。

要像示例结果中的那样格式化您的日期(如果您真的需要),只需将 DATE(a.time_in) 更改为 DATE_format(a.time_in, '%b %d %Y')

SQL Fiddle Example

【讨论】:

  • 谢谢,但我忘了提到从 1 月 1 日到 1 月 30 日过滤的日期范围是动态的,该怎么做?
  • 啊,是的。如果是这种情况,您需要为所选范围创建一个临时表并将其与出席和用户表连接。
  • 不需要临时桌子。当您使用 date(a.time_in) 创建日期范围时,只需添加一个基于此的 where 子句 - WHERE a.time_in BETWEEN '2022-01-01 00:00:00' AND '2022-01-30 23:59:59'
  • 但是他需要显示 Absent 日期,所以我们需要一个 Data 来显示这些日期......如果我们使用中间,它将只显示出现在考勤表。
  • @LightYagami 您只需更改 where 子句中的日期 - WHERE DATE(a.time_in) BETWEEN '2022-01-01' AND '2022-01-31'。这里的日期应该是参数化的,然后你只需传入你想要的日期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-13
  • 2021-06-25
  • 1970-01-01
  • 2011-12-23
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
相关资源
最近更新 更多