आपके प्रश्न के अनुसार कार्य के घंटे हैं:Mo–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';
-
फ़ंक्शन
generate_series()
एक पंक्ति उत्पन्न करता है यदि अंत प्रारंभ से बड़ा है और प्रत्येक पूर्ण . के लिए दूसरी पंक्ति उत्पन्न करता है दिया गया अंतराल (1 घंटा)। यह गिनती हर घंटे में दर्ज किया गया . भिन्नात्मक घंटों को अनदेखा करने के लिए, अंत से 1 घंटा घटाएं। और 14:00 से पहले शुरू होने वाले घंटों की गिनती न करें। -
फ़ील्ड पैटर्न का उपयोग करें
ISODOW
DOW
. के बजायEXTRACT()
. के लिए अभिव्यक्तियों को सरल बनाने के लिए। रिटर्न7
0
. के बजाय रविवार के लिए। -
एक सरल (और बहुत सस्ता)
time
के लिए कास्ट किया गया योग्यता घंटों की पहचान करना आसान बनाता है। -
एक घंटे के अंशों को नजरअंदाज कर दिया जाता है, भले ही अंतराल के शुरू और अंत में अंश एक घंटे या उससे अधिक तक जोड़ दें।
पूरी तालिका के लिए
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;
एसक्यूएल फिडल.
अधिक सटीकता
अधिक सटीकता प्राप्त करने के लिए आप छोटी समय इकाइयों का उपयोग कर सकते हैं। उदाहरण के लिए 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;
इकाई जितनी छोटी होगी लागत उतनी ही अधिक ।
LATERAL
के साथ क्लीनर पोस्टग्रेज में 9.3+
नए LATERAL
. के संयोजन में पोस्टग्रेज 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;
इसका अतिरिक्त लाभ . है कि शून्य कार्य घंटों वाले अंतरालों को उपरोक्त संस्करणों की तरह परिणाम से बाहर नहीं रखा गया है।
LATERAL
. के बारे में अधिक जानकारी :
- इस द्वारा समूह के साथ सरणी में सबसे आम तत्व खोजें
- किसी अन्य तालिका में संख्या के आधार पर एक तालिका में एकाधिक पंक्तियों को सम्मिलित करें
सटीक परिणाम
8.4+ पोस्ट करें
या आप सटीक . प्राप्त करने के लिए अलग-अलग समय सीमा के प्रारंभ और समाप्ति के साथ डील करते हैं माइक्रोसेकंड के लिए परिणाम। क्वेरी को अधिक जटिल, लेकिन सस्ता और सटीक बनाता है:
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;
एसक्यूएल फिडल.
9.2+ को tsrange
. के साथ पोस्ट करता है
नई श्रेणी के प्रकार सटीक परिणामों . के लिए अधिक सुंदर समाधान प्रदान करते हैं चौराहा ऑपरेटर के साथ संयोजन में *
:
केवल एक दिन की समय सीमा के लिए सरल कार्य:
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;
एसक्यूएल फिडल.