在PostgreSQL中的日期列上做一些操作

时间:2019-01-09 07:12:49

标签: sql postgresql pivot

我有一个包含以下列的表格:

personnel_id INT,日期DATE,时间TIME,但无时区。

每一个人每天都有进场和出场的机会。也可以有多个输入和输出。例如,某人可能在8:00出发并在13:00退出,然后在16:36再次返回并报到,最后在19:20退出。

因此,对于每个日期,我需要计算一个人上班的总时数,并据此计算出该人每个月的工作时间。因此,我需要一个选择来获取一个person_id并返回该人每个月的工作时间。例如:

ID  1        2      3        4      5        6      7        8   9       10   11    12  
3   173.24   134    147.26   180    50.47    138    196.36   47  93.56   .56  78    139

1 个答案:

答案 0 :(得分:1)

首先,您需要计算每个切入/切出组合的持续时间。

假设每个人员ID的每个日期的条目数始终为偶数,则可以使用以下内容计算每对的持续时间:

select personnel_id, 
       "date", 
       case 
         when row_number() over w % 2 = 0 then "time" - lag("time") over w
       end as duration
from person_work
window w as (partition by personnel_id, "date" order by "time")

row_number()window function,为每一行分配一个数字。 lag()是另一个窗口函数,它从上一行获取一列的值。由于这两个函数共享相同的“窗口定义”,因此我只在结尾处使用window子句声明了一次。 CASE表达式为第二行计算time列的差。时钟输入行的行号为奇数,时钟输出行的行号为偶数。 % 2检查偶数行号。

在下一步中,我们需要将成对的货币汇总为每月的持续时间。 这可以通过在先前的查询基础上完成。我正在使用common table expression重用上一个查询:

with hours as (
  select personnel_id, 
         "date", 
         case 
           when row_number() over w % 2 = 0 then 
              -- this converts the interval into a decimal value
              extract(epoch from "time" - lag("time") over w)/3600
         end as hours
  from person_work
  window w as (partition by personnel_id, "date" order by "time")
), hours_per_month as (
  select personnel_id, 
         extract(year from "date")::int as work_year,
         extract(month from "date")::int as work_month,
         sum(hours) work_hours
  from hours
  where hours is not null
  group by personnel_id, work_year, work_month
)
select *
from hours_per_month;

extract(year from ...)返回date列的年份作为十进制值。 ::inttype cast,将其简单地转换为整数。严格来说,这并不是必须的。

extract(epoch from ..)以秒为单位返回interval的持续时间。将该结果除以3600将返回间隔,以小时为单位。

这将返回类似:

personnel_id | work_year | work_month | work_hours
-------------+-----------+------------+-----------
           1 |      2018 |          1 |      25.33
           1 |      2018 |          2 |      17.08
           1 |      2018 |          3 |       8.25

然后在最后一步中,我们需要将行变成列。这可以通过使用filter子句进行条件聚合来完成:

with hours as (
  select personnel_id, 
         "date", 
         case 
           when row_number() over w % 2 = 0 then extract(epoch from "time" - lag("time") over w)/3600
         end as hours
  from person_work
  window w as (partition by personnel_id, "date" order by "time")
), hours_per_month as (
  select personnel_id, 
         extract(year from "date")::int as work_year,
         extract(month from "date")::int as work_month,
         sum(hours) hours
  from hours
  where hours is not null
  group by personnel_id, work_year, work_month
)
select personnel_id, 
       work_year,
       sum(hours) filter (where work_month = 1) as hours_jan,
       sum(hours) filter (where work_month = 2) as hours_feb,
       sum(hours) filter (where work_month = 3) as hours_mar,
       sum(hours) filter (where work_month = 4) as hours_apr,
       sum(hours) filter (where work_month = 5) as hours_may,
       sum(hours) filter (where work_month = 6) as hours_jun,
       sum(hours) filter (where work_month = 7) as hours_Jul,
       sum(hours) filter (where work_month = 8) as hours_aug,
       sum(hours) filter (where work_month = 9) as hours_sep,
       sum(hours) filter (where work_month = 10) as hours_oct,
       sum(hours) filter (where work_month = 11) as hours_nov,
       sum(hours) filter (where work_month = 12) as hours_dec
from hours_per_month
group by personnel_id, work_year;

这将返回如下内容:

personnel_id | work_year | hours_jan | hours_feb | hours_mar | hours_apr | hours_may | hours_jun | hours_jul | hours_aug | hours_sep | hours_oct | hours_nov | hours_dec
-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+----------
           1 |      2018 |     25.33 |     17.08 |      8.25 |      ...  |    ...    |    ...    |    ...    |    ...    |     ....  |    ....   |     ...   |    ....  

如果您只想要一份年度报告,则可以在最终选择中使用where work_year = ...,然后从选择列表和group by

中删除该列。

在线示例:https://rextester.com/OEEAZ64654