简体   繁体   中英

SQL sum columns aggregate by others “dislocated” columns

I would be very grateful if you could help me with this problem.

I have this results of a Oracle SQL query, are about a night shift schedule.

start_day_hours are the total worked hours between the shift start_date and the midnight. end_day_hours are the total worked hours between the midnight en the end of the shift.

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

I need the sum of start_day_hours and end_day_hours by day, something like:

  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)

Thanks a lot for your help!

SQL Fiddle

Oracle 11g R2 Schema Setup :

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;

Query 1 :

Use a row generator to get each day between the earliest start and the latest end and then join it to the original table when the day overlaps the shift and aggregate those overlaps:

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 |

One simple way could be to union start/end date into 1 column and start_hours/end_hours into other column, trunc date column, group by date and calculate sum of hours column as below.

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;

Result:

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

Thanks a lot for your help @MT0. Finally I have done this:

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
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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM