SQL 总和列由其他 "dislocated" 列汇总

SQL sum columns aggregate by others "dislocated" columns

如果你能帮我解决这个问题,我将不胜感激。

我有这个 Oracle SQL 查询的结果,是关于夜班时间表的。

start_day_hours 是轮班 start_date 和午夜之间的总工作时间。 end_day_hours 是从午夜到轮班结束之间的总工作时间。

start            midnight          end               start_day_hours      end_day_hours
02/10/17 21:33  02/10/17 23:59   03/10/17 00:42       2,43                0,71
03/10/17 21:34  03/10/17 23:59   04/10/17 00:19       2,42                0,32
04/10/17 21:59  04/10/17 23:59   05/10/17 55:36       2,00                0,92
16/10/17 21:59  16/10/17 23:59   17/10/17 00:01       2,00                0,01
18/10/17 22:50  18/10/17 23:59   19/10/17 00:25       1,16                0,42
19/10/17 22:19  19/10/17 23:59   20/10/17 01:00       1,67                1,01

我每天需要 start_day_hours 和 end_day_hours 的总和,例如:

  day         total_hours
02/10/17         2,43    (2,43)
03/10/17         3,13    (0.71+2,42)
04/10/17         2,32    (0.32+2.00)
05/10/17         0,92    (0,92)
16/10/17         2,00    (2,00)
17/10/17         0,01    (0,01)
18/10/17         1,16    (1,16)
19/10/17         2,51    (0.42+1.67)
20/10/17         1,01    (1,01)

非常感谢您的帮助!

SQL Fiddle

Oracle 11g R2 架构设置:

CREATE TABLE shift (
  "start" DATE,
  "end"   DATE
);

INSERT INTO shift
SELECT TIMESTAMP '2017-10-02 21:33:00',  TIMESTAMP '2017-10-03 00:42:00' FROM DUAL UNION ALL
SELECT TIMESTAMP '2017-10-03 21:34:00',  TIMESTAMP '2017-10-04 00:19:00' FROM DUAL UNION ALL
SELECT TIMESTAMP '2017-10-04 21:59:00',  TIMESTAMP '2017-10-05 00:55:00' FROM DUAL UNION ALL
SELECT TIMESTAMP '2017-10-16 21:59:00',  TIMESTAMP '2017-10-17 00:01:00' FROM DUAL UNION ALL
SELECT TIMESTAMP '2017-10-18 22:50:00',  TIMESTAMP '2017-10-19 00:25:00' FROM DUAL UNION ALL
SELECT TIMESTAMP '2017-10-19 22:19:00',  TIMESTAMP '2017-10-20 01:00:00' FROM DUAL;

查询 1:

使用行生成器获取最早 start 和最晚 end 之间的每一天,然后在这一天与班次重叠时将其加入原始 table 并聚合这些重叠:

WITH days ( dt ) AS (
  SELECT min_dt + LEVEL - 1
  FROM   (
    SELECT TRUNC( MIN( "start" ) ) AS min_dt,
           TRUNC( MAX( "end" ) )   AS max_dt
    FROM   shift
  )
  CONNECT BY min_dt + LEVEL - 1 <= max_dt
)
SELECT dt,
       SUM(
         LEAST( "end", dt + INTERVAL '1' DAY )
         - GREATEST( "start", dt )
       ) * 24 AS hours_worked
FROM   shift s
       INNER JOIN days d
       ON (    s."start" < d.dt + INTERVAL '1' DAY
           AND s."end"   > d.dt )
GROUP BY dt
ORDER BY dt

Results:

|                   DT |         HOURS_WORKED |
|----------------------|----------------------|
| 2017-10-02T00:00:00Z |                 2.45 |
| 2017-10-03T00:00:00Z |   3.1333333333333333 |
| 2017-10-04T00:00:00Z |   2.3333333333333335 |
| 2017-10-05T00:00:00Z |   0.9166666666666666 |
| 2017-10-16T00:00:00Z |   2.0166666666666666 |
| 2017-10-17T00:00:00Z | 0.016666666666666666 |
| 2017-10-18T00:00:00Z |   1.1666666666666667 |
| 2017-10-19T00:00:00Z |                  2.1 |
| 2017-10-20T00:00:00Z |                    1 |
SELECT T1.START_DATE,nvl(T1.START_DAY_HOURS,0) + nvl(T2.END_DAY_HOURS,0) TOTAL_HOURS
FROM 
(SELECT TRUNC(START) START_DATE,sum(nvl(START_DAY_HOURS,0)) START_DAY_HOURS  FROM YOUR_TABLE GROUP BY TRUNC(START)) T1

LEFT JOIN (SELECT TRUNC(END) END_DATE,SUM(nvl(END_DAY_HOURS,0)) END_DAY_HOURS FROM YOUR_TABLE GROUP BY TRUNC(END)) T2
ON T1.START_DATE = T2.END_DATE

一种简单的方法是将 start/end date 并入 1 列,将 start_hours/end_hours 并入另一列,trunc date 列,group by 日期并计算sum hours 列如下。

SELECT date1 AS DAY,
       sum(hours) AS total_hours
FROM
  (SELECT trunc(start1) AS date1,
          start_day_hours AS hours
   FROM t1
   UNION ALL 
   SELECT trunc(end1),
          end_day_hours
   FROM t1 ) t
GROUP BY date1
ORDER BY date1;

结果:

DAY                   TOTAL_HOURS
---------------------------------
02.10.2017 00:00:00   2,43
03.10.2017 00:00:00   3,13
04.10.2017 00:00:00   2,32
05.10.2017 00:00:00   0,92
16.10.2017 00:00:00   2
17.10.2017 00:00:00   0,01
18.10.2017 00:00:00   1,16
19.10.2017 00:00:00   2,09
20.10.2017 00:00:00   1,01

DEMO

非常感谢@MT0 的帮助。我终于做到了:

SELECT dt,
   NVL(SUM(
     LEAST( SIXTY, dt + INTERVAL '1' DAY )
     - GREATEST( ZERO, dt )
   ) * 24, 0.0) AS hours_worked
FROM shitf s
   RIGHT JOIN 
(SELECT TO_DATE('2017-10-01 00:00:00', 'YYYY/MM/DD  HH24:MI:SS')+LEVEL-1 AS dt
FROM DUAL CONNECT BY LEVEL <= TO_CHAR(LAST_DAY(TO_DATE('2017-10-01 00:00:00', 'YYYY/MM/DD  HH24:MI:SS')),'DD')) d
ON (s.ZERO < d.dt + INTERVAL '1' DAY
       AND s.SIXTY   > d.dt )
GROUP dt
ORDER dt