[SQL] 계산은 PostgreSQL의 2 날짜 사이의 근무 시간
SQL계산은 PostgreSQL의 2 날짜 사이의 근무 시간
나는 포스트 그레스 (PL / pgSQL의)와 알고리즘을 개발하고 나는 주말이 작동하지 않습니다와 일의 나머지 만 15 오후 오전 8시에서 계산되는 것을 고려, 2 타임 스탬프 사이의 근로 시간 수를 계산해야합니다.
예를 들면 :
뿐만 아니라 시간의 분수를 고려하는 것이 좋은 것입니다.
해결법
-
==============================
1.귀하의 질문에 따라 근무 시간은 다음과 같습니다 모-FR, 08 : 00-15 : 00.
귀하의 질문에 따라 근무 시간은 다음과 같습니다 모-FR, 08 : 00-15 : 00.
1 시간 단위를 운영. 분수 그러므로 정확하지만 간단하지가 무시됩니다 :
SELECT count(*) AS work_hours FROM generate_series (timestamp '2013-06-24 13:30' , timestamp '2013-06-24 15:29' - interval '1h' , interval '1h') h WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= '08:00' AND h::time <= '14:00';
CREATE TEMP TABLE t (t_id int PRIMARY KEY, t_start timestamp, t_end timestamp); INSERT INTO t VALUES (1, '2009-12-03 14:00', '2009-12-04 09:00') ,(2, '2009-12-03 15:00', '2009-12-07 08:00') -- examples in question ,(3, '2013-06-24 07:00', '2013-06-24 12:00') ,(4, '2013-06-24 12:00', '2013-06-24 23:00') ,(5, '2013-06-23 13:00', '2013-06-25 11:00') ,(6, '2013-06-23 14:01', '2013-06-24 08:59'); -- max. fractions at begin and end
질문:
SELECT t_id, count(*) AS work_hours FROM ( SELECT t_id, generate_series (t_start, t_end - interval '1h', interval '1h') AS h FROM t ) sub WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= '08:00' AND h::time <= '14:00' GROUP BY 1 ORDER BY 1;
SQL 바이올린.
더 정밀도를 얻기 위해 더 작은 시간 단위를 사용할 수 있습니다. 예를 들어 5 분 조각 :
SELECT t_id, count(*) * interval '5 min' AS work_interval FROM ( SELECT t_id, generate_series (t_start, t_end - interval '5 min', interval '5 min') AS h FROM t ) sub WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= '08:00' AND h::time <= '14:55' -- 15.00 - interval '5 min' GROUP BY 1 ORDER BY 1;
비용이 높을수록 유닛 작은.
포스트 그레스 9.3의 새로운 측면 기능과 함께, 상기 쿼리는 다음과 같이 쓸 수있다 :
1 시간 정밀도 :
SELECT t.t_id, h.work_hours FROM t LEFT JOIN LATERAL ( SELECT count(*) AS work_hours FROM generate_series (t.t_start, t.t_end - interval '1h', interval '1h') h WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= '08:00' AND h::time <= '14:00' ) h ON TRUE ORDER BY 1;
5 분 정밀도 :
SELECT t.t_id, h.work_interval FROM t LEFT JOIN LATERAL ( SELECT count(*) * interval '5 min' AS work_interval FROM generate_series (t.t_start, t.t_end - interval '5 min', interval '5 min') h WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= '08:00' AND h::time <= '14:55' ) h ON TRUE ORDER BY 1;
이것은 제로 사용 시간을 포함하는 구간이 상기 버전과 같은 결과로부터 배제되지 않는다는 부가적인 장점을 갖는다.
측면에 대한 자세한 :
아니면 마이크로에 대한 정확한 결과를 얻기 위해 별도의 시간 프레임의 시작과 끝 처리합니다. 정확한 쿼리가 더 복잡하지만, 저렴하고 있습니다 :
WITH var AS (SELECT '08:00'::time AS v_start , '15:00'::time AS v_end) SELECT t_id , COALESCE(h.h, '0') -- add / subtract fractions - CASE WHEN EXTRACT(ISODOW FROM t_start) < 6 AND t_start::time > v_start AND t_start::time < v_end THEN t_start - date_trunc('hour', t_start) ELSE '0'::interval END + CASE WHEN EXTRACT(ISODOW FROM t_end) < 6 AND t_end::time > v_start AND t_end::time < v_end THEN t_end - date_trunc('hour', t_end) ELSE '0'::interval END AS work_interval FROM t CROSS JOIN var LEFT JOIN ( -- count full hours, similar to above solutions SELECT t_id, count(*)::int * interval '1h' AS h FROM ( SELECT t_id, v_start, v_end , generate_series (date_trunc('hour', t_start) , date_trunc('hour', t_end) - interval '1h' , interval '1h') AS h FROM t, var ) sub WHERE EXTRACT(ISODOW FROM h) < 6 AND h::time >= v_start AND h::time <= v_end - interval '1h' GROUP BY 1 ) h USING (t_id) ORDER BY 1;
SQL 바이올린.
새로운 범위 유형은 교차 연산자 *와 함께 정확한 결과를 위해 더 우아한 솔루션을 제공합니다 :
단 하루에 걸쳐 시간 범위에 대한 간단한 기능 :
CREATE OR REPLACE FUNCTION f_worktime_1day(_start timestamp, _end timestamp) RETURNS interval AS $func$ -- _start & _end within one calendar day! - you may want to check ... SELECT CASE WHEN extract(ISODOW from _start) < 6 THEN ( SELECT COALESCE(upper(h) - lower(h), '0') FROM ( SELECT tsrange '[2000-1-1 08:00, 2000-1-1 15:00)' -- hours hard coded * tsrange( '2000-1-1'::date + _start::time , '2000-1-1'::date + _end::time ) AS h ) sub ) ELSE '0' END $func$ LANGUAGE sql IMMUTABLE;
당신의 범위는 결코 여러 날짜에 걸쳐 없다면, 그것은 당신이 필요로하는 모든입니다. 그 밖에, 어떤 간격으로 대처하기 위해 래퍼 함수를 사용합니다 :
CREATE OR REPLACE FUNCTION f_worktime(_start timestamp , _end timestamp , OUT work_time interval) AS $func$ BEGIN CASE _end::date - _start::date -- spanning how many days? WHEN 0 THEN -- all in one calendar day work_time := f_worktime_1day(_start, _end); WHEN 1 THEN -- wrap around midnight once work_time := f_worktime_1day(_start, NULL) + f_worktime_1day(_end::date, _end); ELSE -- multiple days work_time := f_worktime_1day(_start, NULL) + f_worktime_1day(_end::date, _end) + (SELECT count(*) * interval '7:00' -- workday hard coded! FROM generate_series(_start::date + 1 , _end::date - 1, '1 day') AS t WHERE extract(ISODOW from t) < 6); END CASE; END $func$ LANGUAGE plpgsql IMMUTABLE;
요구:
SELECT t_id, f_worktime(t_start, t_end) AS worktime FROM t ORDER BY 1;
SQL 바이올린.
-
==============================
2.이 방법에 대해 : 24 * 7 행, 일주일의 각 시간 동안 하나의 행으로 작은 테이블을 만들 수 있습니다.
이 방법에 대해 : 24 * 7 행, 일주일의 각 시간 동안 하나의 행으로 작은 테이블을 만들 수 있습니다.
CREATE TABLE hours ( hour timestamp not null, is_working boolean not null ); INSERT INTO hours (hour, is_working) VALUES ('2009-11-2 00:00:00', false), ('2009-11-2 01:00:00', false), . . . ('2009-11-2 08:00:00', true), . . . ('2009-11-2 15:00:00', true), ('2009-11-2 16:00:00', false), . . . ('2009-11-2 23:00:00', false);
마찬가지로 다른 일 각각 24 개 행을 추가합니다. 당신이 잠시 살펴 보 겠지만 당신이주는 어떤 년 또는 월 중요하지 않습니다. 당신은 일주일의 모든 7 일 표현해야합니다.
SELECT t.id, t.start, t.end, SUM(CASE WHEN h.is_working THEN 1 ELSE 0 END) AS hours_worked FROM mytable t JOIN hours h ON (EXTRACT(DOW FROM TIMESTAMP h.hour) BETWEEN EXTRACT(DOW FROM TIMESTAMP t.start) AND EXTRACT(DOW FROM TIMESTAMP t.end)) AND (EXTRACT(DOW FROM TIMESTAMP h.hour) > EXTRACT(DOW FROM TIMESTAMP t.start) OR EXTRACT(HOUR FROM TIMESTAMP h.hour) >= EXTRACT(HOUR FROM TIMESTAMP t.start)) AND (EXTRACT(DOW FROM TIMESTAMP h.hour) < EXTRACT(DOW FROM TIMESTAMP t.end) OR EXTRACT(HOUR FROM TIMESTAMP h.hour) <= EXTRACT(HOUR FROM TIMESTAMP t.end)) GROUP BY t.id, t.start, t.end;
-
==============================
3.이것은 다음과 같은 기능은에 대한 입력을합니다 오늘의 작업 시작 시간 오늘의 작업 종료 시간 시작 시간 종료 시간
이것은 다음과 같은 기능은에 대한 입력을합니다 오늘의 작업 시작 시간 오늘의 작업 종료 시간 시작 시간 종료 시간
-- helper function CREATE OR REPLACE FUNCTION get_working_time_in_a_day(sdt TIMESTAMP, edt TIMESTAMP, swt TIME, ewt TIME) RETURNS INT AS $$ DECLARE sd TIMESTAMP; ed TIMESTAMP; swdt TIMESTAMP; ewdt TIMESTAMP; seconds INT; BEGIN swdt = sdt::DATE || ' ' || swt; -- work start datetime for a day ewdt = sdt::DATE || ' ' || ewt; -- work end datetime for a day IF (sdt < swdt AND edt <= swdt) -- case 1 and 2 THEN seconds = 0; END IF; IF (sdt < swdt AND edt > swdt AND edt <= ewdt) -- case 3 and 4 THEN seconds = EXTRACT(EPOCH FROM (edt - swdt)); END IF; IF (sdt < swdt AND edt > swdt AND edt > ewdt) -- case 5 THEN seconds = EXTRACT(EPOCH FROM (ewdt - swdt)); END IF; IF (sdt = swdt AND edt > swdt AND edt <= ewdt) -- case 6 and 7 THEN seconds = EXTRACT(EPOCH FROM (edt - sdt)); END IF; IF (sdt = swdt AND edt > ewdt) -- case 8 THEN seconds = EXTRACT(EPOCH FROM (ewdt - sdt)); END IF; IF (sdt > swdt AND edt <= ewdt) -- case 9 and 10 THEN seconds = EXTRACT(EPOCH FROM (edt - sdt)); END IF; IF (sdt > swdt AND sdt < ewdt AND edt > ewdt) -- case 11 THEN seconds = EXTRACT(EPOCH FROM (ewdt - sdt)); END IF; IF (sdt >= ewdt AND edt > ewdt) -- case 12 and 13 THEN seconds = 0; END IF; RETURN seconds; END; $$ LANGUAGE plpgsql; -- Get work time difference CREATE OR REPLACE FUNCTION get_working_time(sdt TIMESTAMP, edt TIMESTAMP, swt TIME, ewt TIME) RETURNS INT AS $$ DECLARE seconds INT = 0; strst VARCHAR(9) = ' 00:00:00'; stret VARCHAR(9) = ' 23:59:59'; tend TIMESTAMP; tempEdt TIMESTAMP; x int; BEGIN <<test>> WHILE sdt <= edt LOOP tend = sdt::DATE || stret; -- get the false end datetime for start time IF edt >= tend THEN tempEdt = tend; ELSE tempEdt = edt; END IF; -- skip saturday and sunday x = EXTRACT(DOW FROM sdt); if (x > 0 AND x < 6) THEN seconds = seconds + get_working_time_in_a_day(sdt, tempEdt, swt, ewt); ELSE -- RAISE NOTICE 'MISSED A DAY'; END IF; sdt = (sdt + (INTERVAL '1 DAY'))::DATE || strst; END LOOP test; --RAISE NOTICE 'diff in minutes = %', (seconds / 60); RETURN seconds; END; $$ LANGUAGE plpgsql; -- Table Definition DROP TABLE IF EXISTS test_working_time; CREATE TABLE test_working_time( pk SERIAL PRIMARY KEY, start_datetime TIMESTAMP, end_datetime TIMESTAMP, start_work_time TIME, end_work_time TIME ); -- Test data insertion INSERT INTO test_working_time VALUES (1, '2015-11-03 01:00:00', '2015-11-03 07:00:00', '08:00:00', '22:00:00'), (2, '2015-11-03 01:00:00', '2015-11-04 07:00:00', '08:00:00', '22:00:00'), (3, '2015-11-03 01:00:00', '2015-11-05 07:00:00', '08:00:00', '22:00:00'), (4, '2015-11-03 01:00:00', '2015-11-06 07:00:00', '08:00:00', '22:00:00'), (5, '2015-11-03 01:00:00', '2015-11-07 07:00:00', '08:00:00', '22:00:00'), (6, '2015-11-03 01:00:00', '2015-11-03 08:00:00', '08:00:00', '22:00:00'), (7, '2015-11-03 01:00:00', '2015-11-04 08:00:00', '08:00:00', '22:00:00'), (8, '2015-11-03 01:00:00', '2015-11-05 08:00:00', '08:00:00', '22:00:00'), (9, '2015-11-03 01:00:00', '2015-11-06 08:00:00', '08:00:00', '22:00:00'), (10, '2015-11-03 01:00:00', '2015-11-07 08:00:00', '08:00:00', '22:00:00'), (11, '2015-11-03 01:00:00', '2015-11-03 11:00:00', '08:00:00', '22:00:00'), (12, '2015-11-03 01:00:00', '2015-11-04 11:00:00', '08:00:00', '22:00:00'), (13, '2015-11-03 01:00:00', '2015-11-05 11:00:00', '08:00:00', '22:00:00'), (14, '2015-11-03 01:00:00', '2015-11-06 11:00:00', '08:00:00', '22:00:00'), (15, '2015-11-03 01:00:00', '2015-11-07 11:00:00', '08:00:00', '22:00:00'), (16, '2015-11-03 01:00:00', '2015-11-03 22:00:00', '08:00:00', '22:00:00'), (17, '2015-11-03 01:00:00', '2015-11-04 22:00:00', '08:00:00', '22:00:00'), (18, '2015-11-03 01:00:00', '2015-11-05 22:00:00', '08:00:00', '22:00:00'), (19, '2015-11-03 01:00:00', '2015-11-06 22:00:00', '08:00:00', '22:00:00'), (20, '2015-11-03 01:00:00', '2015-11-07 22:00:00', '08:00:00', '22:00:00'), (21, '2015-11-03 01:00:00', '2015-11-03 23:00:00', '08:00:00', '22:00:00'), (22, '2015-11-03 01:00:00', '2015-11-04 23:00:00', '08:00:00', '22:00:00'), (23, '2015-11-03 01:00:00', '2015-11-05 23:00:00', '08:00:00', '22:00:00'), (24, '2015-11-03 01:00:00', '2015-11-06 23:00:00', '08:00:00', '22:00:00'), (25, '2015-11-03 01:00:00', '2015-11-07 23:00:00', '08:00:00', '22:00:00'), (26, '2015-11-03 08:00:00', '2015-11-03 11:00:00', '08:00:00', '22:00:00'), (27, '2015-11-03 08:00:00', '2015-11-04 11:00:00', '08:00:00', '22:00:00'), (28, '2015-11-03 08:00:00', '2015-11-05 11:00:00', '08:00:00', '22:00:00'), (29, '2015-11-03 08:00:00', '2015-11-06 11:00:00', '08:00:00', '22:00:00'), (30, '2015-11-03 08:00:00', '2015-11-07 11:00:00', '08:00:00', '22:00:00'), (31, '2015-11-03 08:00:00', '2015-11-03 22:00:00', '08:00:00', '22:00:00'), (32, '2015-11-03 08:00:00', '2015-11-04 22:00:00', '08:00:00', '22:00:00'), (33, '2015-11-03 08:00:00', '2015-11-05 22:00:00', '08:00:00', '22:00:00'), (34, '2015-11-03 08:00:00', '2015-11-06 22:00:00', '08:00:00', '22:00:00'), (35, '2015-11-03 08:00:00', '2015-11-07 22:00:00', '08:00:00', '22:00:00'), (36, '2015-11-03 08:00:00', '2015-11-03 23:00:00', '08:00:00', '22:00:00'), (37, '2015-11-03 08:00:00', '2015-11-04 23:00:00', '08:00:00', '22:00:00'), (38, '2015-11-03 08:00:00', '2015-11-05 23:00:00', '08:00:00', '22:00:00'), (39, '2015-11-03 08:00:00', '2015-11-06 23:00:00', '08:00:00', '22:00:00'), (40, '2015-11-03 08:00:00', '2015-11-07 23:00:00', '08:00:00', '22:00:00'), (41, '2015-11-03 12:00:00', '2015-11-03 18:00:00', '08:00:00', '22:00:00'), (42, '2015-11-03 12:00:00', '2015-11-04 18:00:00', '08:00:00', '22:00:00'), (43, '2015-11-03 12:00:00', '2015-11-05 18:00:00', '08:00:00', '22:00:00'), (44, '2015-11-03 12:00:00', '2015-11-06 18:00:00', '08:00:00', '22:00:00'), (45, '2015-11-03 12:00:00', '2015-11-07 18:00:00', '08:00:00', '22:00:00'), (46, '2015-11-03 12:00:00', '2015-11-03 22:00:00', '08:00:00', '22:00:00'), (47, '2015-11-03 12:00:00', '2015-11-04 22:00:00', '08:00:00', '22:00:00'), (48, '2015-11-03 12:00:00', '2015-11-05 22:00:00', '08:00:00', '22:00:00'), (49, '2015-11-03 12:00:00', '2015-11-06 22:00:00', '08:00:00', '22:00:00'), (50, '2015-11-03 12:00:00', '2015-11-07 22:00:00', '08:00:00', '22:00:00'), (51, '2015-11-03 12:00:00', '2015-11-03 23:00:00', '08:00:00', '22:00:00'), (52, '2015-11-03 12:00:00', '2015-11-04 23:00:00', '08:00:00', '22:00:00'), (53, '2015-11-03 12:00:00', '2015-11-05 23:00:00', '08:00:00', '22:00:00'), (54, '2015-11-03 12:00:00', '2015-11-06 23:00:00', '08:00:00', '22:00:00'), (55, '2015-11-03 12:00:00', '2015-11-07 23:00:00', '08:00:00', '22:00:00'), (56, '2015-11-03 22:00:00', '2015-11-03 23:00:00', '08:00:00', '22:00:00'), (57, '2015-11-03 22:00:00', '2015-11-04 23:00:00', '08:00:00', '22:00:00'), (58, '2015-11-03 22:00:00', '2015-11-05 23:00:00', '08:00:00', '22:00:00'), (59, '2015-11-03 22:00:00', '2015-11-06 23:00:00', '08:00:00', '22:00:00'), (60, '2015-11-03 22:00:00', '2015-11-07 23:00:00', '08:00:00', '22:00:00'), (61, '2015-11-03 22:30:00', '2015-11-03 23:30:00', '08:00:00', '22:00:00'), (62, '2015-11-03 22:30:00', '2015-11-04 23:30:00', '08:00:00', '22:00:00'), (63, '2015-11-03 22:30:00', '2015-11-05 23:30:00', '08:00:00', '22:00:00'), (64, '2015-11-03 22:30:00', '2015-11-06 23:30:00', '08:00:00', '22:00:00'), (65, '2015-11-03 22:30:00', '2015-11-07 23:30:00', '08:00:00', '22:00:00'); -- select query to get work time difference SELECT start_datetime, end_datetime, start_work_time, end_work_time, get_working_time(start_datetime, end_datetime, start_work_time, end_work_time) AS diff_in_minutes FROM test_working_time;
이것은 시작과 끝 날짜 사이의 시간 (초) 만 작업 시간의 차이를 줄 것이다
from https://stackoverflow.com/questions/1839319/calculate-working-hours-between-2-dates-in-postgresql by cc-by-sa and MIT license
'SQL' 카테고리의 다른 글
[SQL] 어떻게 마이크로 소프트 SQL 서버의 순서를 구현하는 것이? (0) | 2020.04.07 |
---|---|
[SQL] 여러 문자를 대체 MYSQL 수 있습니까? (0) | 2020.04.07 |
[SQL] 그 콤바인 두 배열의 PostgreSQL에 우편 () 함수와 같은 뭔가인가? (0) | 2020.04.07 |
[SQL] Excel로 내보내기 SQL 쿼리 데이터 (0) | 2020.04.07 |
[SQL] OPENROWSET 쿼리에서 변수 사용 (0) | 2020.04.07 |