使用指纹设备的出勤日志文件。如何显示现在和缺席?

时间:2018-10-25 11:14:34

标签: sql postgresql

我从设备的指纹日志中获取了此文件:

Id User_id      PuchTime
--------------------------
1   152      2018-07-17 09:38:03
2   184      2018-07-17 16:56:43
3   152      2018-07-17 16:57:18
4   165      2018-07-17 16:57:43
5   70       2018-07-17 16:57:59
6   134      2018-07-17 16:58:28
7   276      2018-07-17 16:59:04
8   278      2018-07-17 16:59:05
9   271      2018-07-17 16:59:10
10  268      2018-07-17 16:59:13
11  284      2018-07-17 16:59:16
12  364      2018-07-17 16:59:35
13  19       2018-07-17 16:59:38
14  381      2018-07-17 17:01:12
15  73       2018-07-17 17:12:31
16  126      2018-07-17 17:12:36
17  382      2018-07-17 17:13:50
18  53       2018-07-18 06:34:13
19  284      2018-07-18 08:05:17

如何在Postgres查询中进行查询以提取数据,如下所示:

User_id  Check_Date   TimeIN    TimeOUT   Hours   status
--------------------------------------------------------
152      2018-07-17    09:38:03 16:56:43  7.8     present
152      2018-07-18                               Absent  

我使用此查询

SELECT userid, name, CAST(PuchTime as DATE) Check_Date, 
to_char(PuchTime, 'day') days,
       MIN(CAST(PuchTime as Time)) TimeIN, 
       MAX(Cast(PuchTime as Time)) TimeOUT,
       CAST(MAX(PuchTime) - MIN(PuchTime) AS Time) As hour
FROM attendance_FHLHR
GROUP BY userid,name, CAST(PuchTime as DATE), to_char(PuchTime, 'day')
order by name DESC, check_date ASC, userid ASC

Output of my query:

我需要状态和小时数计算。

    User_id  Check_Date   TimeIN    TimeOUT   Hours
    -----------------------------------------------
    152      2018-07-17    09:38:03 16:56:43  7:18:40  
    152      2018-07-18    

5 个答案:

答案 0 :(得分:1)

免责声明 :(这两个都适用:这里和this one都这样)

demo: db<>fiddle


WITH dates AS(                                                   -- 1
    SELECT
        min(checktime)::date as min,
        max(checktime)::date as max
    FROM log
)

SELECT 
    user_id,
    check_date::date,
    -- 4: 
    CASE WHEN checktime::date = check_date THEN checktime::time  ELSE NULL END as time_in,
    CASE WHEN checktime::date = check_date THEN time_out::time ELSE NULL END as time_out,
    CASE WHEN checktime::date = check_date THEN (time_out - checktime)::time ELSE NULL END as hours
FROM (
    SELECT
        user_id,
        checktime,
        lead(checktime) OVER (ORDER BY checktime) as time_out,  -- 2
        generate_series(                                        -- 3
            (SELECT min FROM dates), 
            (SELECT max FROM dates), 
            interval '1 day'
        ) as check_date
    FROM log
)s
ORDER BY user_id, check_date
  1. 计算日志的最小/最大日期阈值以获取生成日期的界限
  2. lead window function将下一个checktime值带到当前行。因此下一个checktime被计为time_out
  3. generate_series生成给定(计算)范围之间的所有日期值
  4. CASE部分:如果当前checktime不等于生成的日期值,则给出NULL;否则给出当前的time_in / time_out /时差


关于几乎重复的扩展问题:Using Attendance Log and OFF days Table . How to show present ,absents and OFF day?我在这里添加答案,因为应该关闭重复项,并且应该扩展上述问题。

演示,请参见上方的小提琴的第二部分

WITH dates AS(
    SELECT
        min(checktime)::date as min,
        max(checktime)::date as max
    FROM log
)
SELECT DISTINCT ON (user_id, check_date, time_in)   -- 6
    user_id, 
    check_date, 
    to_char(check_date, 'Day') as day,              -- 2
    COALESCE(time_in,                               -- 4
         MAX(time_in) OVER (PARTITION BY user_id, check_date ORDER BY time_out NULLS LAST)
    ) as time_in, 
    time_out, 
    hours,
    CASE                                            -- 5
        WHEN checktime::date = check_date THEN 'present' 
        WHEN of.days IS NOT NULL THEN 'OFF DAY'
        ELSE 'absent'
    END as status
FROM (
    SELECT 
        user_id,
        check_date,
        checktime,
        CASE WHEN checktime::date = check_date THEN checktime::time  ELSE NULL END as time_in,
        CASE WHEN checktime::date = check_date THEN time_out::time ELSE NULL END as time_out,
        -- 1
        CASE WHEN checktime::date = check_date THEN extract(epoch FROM (time_out - checktime)) / 60 / 60 ELSE NULL END as hours
    FROM (
        SELECT
            user_id,
            checktime,
            lead(checktime) OVER (ORDER BY checktime) as time_out,
            generate_series(
                (SELECT min FROM dates), 
                (SELECT max FROM dates), 
                interval '1 day'
            ) as check_date
        FROM log
    ) s
) s
--- 3
LEFT JOIN off_days of ON (of.userid = s.user_id) AND (of.days = trim(to_char(check_date, 'day')))
ORDER BY user_id, check_date

因为这是上一个查询的扩展,所以我仅解释更改:

  1. 需要将时间值设为time而不是numeric。因此extract(epoch...)会得到差值的秒数,/ 60 / 60会转换成小时数
  2. Converting a date into a weekdayto_char功能
  3. 针对user_id和工作日加入休假表(再次使用to_char函数,这次使用小写字母)。 to_char adds whitespace-因此trim()将其删除以进行比较
  4. 棘手的部分(与6一起):由于联接重复了行,因此有必要消除错误的行。无法在DISTINCT和工作日进行简单的user_id,因为152一天有两个条目。但是,由于53在两个日期的不同日期都有两个条目(在我的示例中,显示小提琴),因此两个日期都有效,并且创建了一个空行。此代码行将time_in值复制到空行中(下一步,请参阅(6))
  5. 如果存在生成日期的条目(请参见上面的第一部分),则为present。如果不是,请检查是否是“休息日”,否则absent
  6. 我们有以下情况:A:没有重复的行; B:每个user_id和工作日两行,因为有两个条目; C:由于连接,两行(空的时间)。我们不希望有重复的行,所以有distinct。这也适用于(C),因为我们在(4)中的time_in行中重复了NULL

答案 1 :(得分:0)

您需要一组参考日期和用户来比较活动。在任何编程语言中,生成该函数都不是一件容易的事,对于SQL而言尤其如此。但是,您可以使用已经拥有的日期/用户列表,并通过笛卡尔加入用户列表:

SELECT alldatesusers.userid, alldatesusers.ref_date AS check_date
,  TimeIn, TimeOut, hour
FROM
(SELECT DISTINCT CAST(rd.PuchTime as DATE) AS refdate, user.userid
  FROM attendance_FHLHR rd
  , (SELECT DISTINCT ru.userid
     FROM attendance_FHLHR ru) AS user
) AS alldatesusers
LEFT JOIN
( SELECT userid, name, CAST(PuchTime as DATE) Check_Date
  ,to_char(PuchTime, 'day') days
  ,MIN(CAST(PuchTime as Time)) TimeIN 
  ,MAX(Cast(PuchTime as Time)) TimeOUT
  ,CAST(MAX(PuchTime) - MIN(PuchTime) AS Time) As hour
  FROM attendance_FHLHR
  GROUP BY userid,name, CAST(PuchTime as DATE), to_char(PuchTime, 'day')
) AS attendance
ON alldatesusers.refdate=attendance.Check_date
AND alldates.userid=attendance.userid

答案 2 :(得分:0)

以下SQL使用generate_series()创建日历,并将打孔卡表中的现有工作程序列表用作用户列表。通过交叉加入,可以列出日期和用户,您可以从中轻松地得出缺席/在场的信息。

将练习的分钟数转换为小数小时留给读者练习。

SQL Fiddle

SQL

with workers as ( select distinct user_id from clock)
, calendar as (
    select workday from 
    generate_series(
           (date '2018-07-01')::timestamp,
           (date '2018-07-31')::timestamp,
           interval '1 day') workday
)
SELECT w.user_id, workday, 
       to_char(cast(PuchTime as DATE), 'day') days,
       MIN(CAST(PuchTime as Time)) TimeIN, 
       MAX(Cast(PuchTime as Time)) TimeOUT,
       CAST(MAX(PuchTime) - MIN(PuchTime) AS Time) As hour
FROM
  calendar cross join workers w
  left join clock c
    on workday = CAST(c.PuchTime as DATE)
         and w.user_id=c.user_id
GROUP BY w.user_id, workday, calendar.*, CAST(c.PuchTime as DATE)
order by w.user_id DESC, calendar ASC

结果(摘要)

user_id     workday             days    timein  timeout     hour
382     2018-07-01T00:00:00Z    (null)  (null)  (null)  (null)
382     2018-07-02T00:00:00Z    (null)  (null)  (null)  (null)
382     2018-07-03T00:00:00Z    (null)  (null)  (null)  (null)
382     2018-07-04T00:00:00Z    (null)  (null)  (null)  (null)
382     2018-07-05T00:00:00Z    (null)  (null)  (null)  (null)
...
152     2018-07-16T00:00:00Z    (null)  (null)  (null)  (null)
152     2018-07-17T00:00:00Z    tuesday     09:38:03    16:57:18    07:19:15
152     2018-07-18T00:00:00Z    (null)  (null)  (null)  (null)
152     2018-07-19T00:00:00Z    (null)  (null)  (null)  (null)
...

答案 3 :(得分:0)

假设每个员工都没有出现在同一天,这应该可以工作:

with from_thru as (
  select
    min (punchtime)::date as from_date, 
    max (punchtime)::date as thru_date
  from attendance_FHLHR
),
users as (
  select distinct user_id from attendance_FHLHR
)
select
  gs.date::date, u.user_id, min (a.punchtime) as TimeIn,
  max (a.punchtime) as TimeOut,
  extract (epoch from max (a.punchtime) - min (a.punchtime))/3600 as Hours,
  case
    when min (a.punchtime) is null then 'Absent'
    when count (1) = 1 then 'Missing Punch'
    else 'Present'
  end as status
from
  from_thru
  cross join generate_series (from_date, thru_date, interval '1 day') gs (date)
  cross join users u
  left join attendance_FHLHR a on
    a.punchtime::date = gs.date and
    a.user_id = u.user_id
group by
  gs.date, u.user_id

在只有一个拳的情况下,我还添加了一个称为“缺少拳”的条件。

-编辑11/2/2018-

根据您对“休息日”表的反馈,以下是我认为也可以解决的问题。请注意,无需更改子查询:

select
  gs.date::date, u.user_id, min (a.checktime) as TimeIn,
  max (a.checktime) as TimeOut,
  extract (epoch from max (a.checktime) - min (a.checktime))/3600 as Hours,
  case
    when o.userid is not null then 'Off Day'
    when min (a.checktime) is null then 'Absent'
    when count (1) = 1 then 'Missing Punch'
    else 'Present'
  end as status
from
  from_thru
  cross join generate_series (from_date, thru_date, interval '1 day') gs (date)
  cross join users u
  left join off_days o on
    extract (dow from gs.date)::text = o.days and
    u.user_id = o.userid
  left join attendance_FHLHR a on
    a.checktime::date = gs.date and
    a.user_id = u.user_id
group by
  gs.date, u.user_id, o.userid

答案 4 :(得分:0)

with from_thru as (
  select
    min (checktime)::date as from_date, 
    max (checktime)::date as thru_date
  from attendance_FHLHR
),
users as (
  select distinct userid, name, emp_id from attendance_FHLHR
)
select
  gs.date::date, u.userid, u.name, to_char(gs.date::date, 'day') days,
  min (a.checktime) as TimeIn,
  max (a.checktime) as TimeOut,
  extract (epoch from max (a.checktime) - min (a.checktime))/3600 as Hours,
  case
when uso.off_days = 'monday' and min (a.checktime) is null  then 'Off Day'
when uso.off_days = 'tuesday' and min (a.checktime) is null  then 'Off Day'
when uso.off_days = 'wednesday' and min (a.checktime) is null  then 'Off Day'
    when   max(a.checktime) is null  then 'ABsent'
    when count (1) = 1 then 'Half Day' 
    else 'Present'
  end as status,
case  when uso.off_days = 'monday' then 'monday'
when uso.off_days = 'tuesday' then 'tuesday'
when uso.off_days = 'wednesday' then 'wednesday'

                            end as ccc
from
  from_thru
  cross join generate_series (from_date, thru_date, interval '1 day') gs (date)
  cross join users u
  left join attendance_FHLHR a on
    a.checktime::date = gs.date and
    a.userid = u.userid
   left join users_staffuser us on u.emp_id::varchar = us.emp_id 
   left join users_staffuseroffdays uso on uso.staff_user_id = us.id
  -- and uso.off_days = to_char(gs.date::date, 'day')
and us.emp_id is not null
and uso.off_days = trim(to_char(gs.date::date, 'day'))
group by
  gs.date, u.userid, u.name, uso.off_days
order by u.name ASC