Использование файла журнала посещаемости с устройства отпечатков пальцев.Как показать настоящее и отсутствующее? - PullRequest
0 голосов
/ 25 октября 2018

Я получаю этот файл из журнала отпечатков пальцев с устройства:

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 голосов
/ 03 ноября 2018
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
0 голосов
/ 31 октября 2018

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ : (Этот предназначен для обоих: этот здесь и этот )

демо: 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 оконная функция принимает следующееchecktime значение для текущей строки.Таким образом, следующее checktime считается как time_out
  3. generate_series и генерирует все значения даты между заданными (расчетными) границами
  4. CASE частей: если текущий checktime равен несгенерированное значение даты затем выдает NULL;иначе выдайте текущую time_in / time_out / разницу во времени


Относительно почти дублированного расширенного вопроса: Использование журнала посещаемости и таблицы дней выключения.Как показать настоящее, отсутствующее и выходной день? Я добавляю ответ сюда, потому что дублирование должно быть закрыто, а вопрос выше должен быть расширен.

демонстрация, см. Вторую часть скрипта выше

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. Преобразование даты в будний день с помощью функции to_char
  3. Соединение таблицы off_day с user_id и днем ​​недели (снова с использованием функции to_char, на этот раз с небольшими заглавными буквами).to_char добавляет пробел - поэтому trim() удаляет его для сравнения
  4. Хитрая часть (вместе с 6): поскольку объединение дублирует строки, необходимо устранить неправильные,Невозможно сделать простой DISTINCT в user_id и день недели, потому что, например, 152 имеет две записи в один день.Но поскольку 53 имеет две записи в разные дни (в моем примере см. Fiddle), для обеих дат создается действительная и пустая строка.Эта строка кода дублирует значение time_in в пустой строке (следующий шаг см. (6))
  5. Если есть запись для сгенерированной даты (см. Первую часть выше), это present.Если нет, проверьте, является ли «выходной день», в противном случае absent
  6. У нас есть случаи: A: нет дублирующихся строк;B: две строки для user_id и дня недели, потому что есть две записи;C: две строки (с пустым временем) из-за соединения.Мы не хотим повторяющихся строк, поэтому есть distinct.Это работает также для (C), потому что мы продублировали time_in для NULL строк в (4)
0 голосов
/ 25 октября 2018

Следующий 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)
...
0 голосов
/ 26 октября 2018

Предполагая, что каждый сотрудник не появляется отсутствующим в тот же день, это должно сработать:

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
0 голосов
/ 25 октября 2018

Вам нужен справочный набор дат и пользователей, чтобы сравнить действия с.Генерирование этого не является тривиальным в любом языке программирования, особенно в 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...