टेबल लेआउट
tsrange
के सेट के रूप में खुलने का समय (संचालन के घंटे) स्टोर करने के लिए तालिका को फिर से डिज़ाइन करें (timestamp without time zone
) मूल्य। पोस्टग्रेज की आवश्यकता है 9.2 या बाद के संस्करण ।
अपने शुरुआती घंटों को व्यवस्थित करने के लिए एक यादृच्छिक सप्ताह चुनें। मुझे सप्ताह पसंद है:
1996-01-01 (सोमवार) से 1996-01-07 (रविवार)
यह सबसे हालिया लीप वर्ष है जहां 1 जनवरी को सुविधाजनक रूप से सोमवार होता है। लेकिन इस मामले के लिए यह कोई भी यादृच्छिक सप्ताह हो सकता है। बस सुसंगत रहें।
अतिरिक्त मॉड्यूल स्थापित करें btree_gist
पहला:
CREATE EXTENSION btree_gist;
देखें:
- पूर्णांक और श्रेणी से बनी बहिष्करण बाधा के बराबर
फिर इस तरह टेबल बनाएं:
CREATE TABLE hoo (
hoo_id serial PRIMARY KEY
, shop_id int NOT NULL -- REFERENCES shop(shop_id) -- reference to shop
, hours tsrange NOT NULL
, CONSTRAINT hoo_no_overlap EXCLUDE USING gist (shop_id with =, hours WITH &&)
, CONSTRAINT hoo_bounds_inclusive CHECK (lower_inc(hours) AND upper_inc(hours))
, CONSTRAINT hoo_standard_week CHECK (hours <@ tsrange '[1996-01-01 0:0, 1996-01-08 0:0]')
);
एक कॉलम hours
आपके सभी कॉलम बदल देता है:
opens_on, closes_on, opens_at, closes_at
उदाहरण के लिए, बुधवार, 18:30 . से संचालन के घंटे से गुरुवार, 05:00 UTC को इस प्रकार दर्ज किया जाता है:
'[1996-01-03 18:30, 1996-01-04 05:00]'
बहिष्करण बाधा hoo_no_overlap
प्रति दुकान अतिव्यापी प्रविष्टियों को रोकता है। इसे GiST अनुक्रमणिका . के साथ क्रियान्वित किया जाता है , जो हमारे प्रश्नों का समर्थन करने के लिए भी होता है। अध्याय "सूचकांक और प्रदर्शन" . पर विचार करें नीचे अनुक्रमण रणनीतियों पर चर्चा कर रहे हैं।
चेक बाधा hoo_bounds_inclusive
दो उल्लेखनीय परिणामों के साथ, आपकी सीमाओं के लिए समावेशी सीमाओं को लागू करता है:
- निचली या ऊपरी सीमा पर पड़ने वाला समय बिंदु हमेशा शामिल होता है।
- एक ही दुकान के लिए आसन्न प्रविष्टियां प्रभावी रूप से अस्वीकृत हैं। समावेशी सीमाओं के साथ, वे "ओवरलैप" होंगे और बहिष्करण बाधा एक अपवाद उठाएगी। इसके बजाय आसन्न प्रविष्टियों को एक पंक्ति में मर्ज किया जाना चाहिए। सिवाय जब वे रविवार आधी रात के आसपास लपेटते हैं , जिस स्थिति में उन्हें दो पंक्तियों में विभाजित किया जाना चाहिए। फ़ंक्शन
f_hoo_hours()
नीचे इसका ख्याल रखता है।
चेक बाधा hoo_standard_week
"रेंज में निहित है" ऑपरेटर <@
का उपयोग करके स्टेजिंग सप्ताह की बाहरी सीमाओं को लागू करता है ।
समावेशी . के साथ सीमा, आपको एक कोने का मामला observe देखना होगा जहां रविवार मध्यरात्रि में समय समाप्त हो जाता है:
'1996-01-01 00:00+0' = '1996-01-08 00:00+0'
Mon 00:00 = Sun 24:00 (= next Mon 00:00)
आपको दोनों टाइमस्टैम्प को एक साथ खोजना होगा। यहां अनन्य . से संबंधित मामला है ऊपरी सीमा जो इस कमी को प्रदर्शित नहीं करेगी:
- पोस्टग्रेएसक्यूएल में EXCLUDE के साथ आसन्न/अतिव्यापी प्रविष्टियों को रोकना
फ़ंक्शन f_hoo_time(timestamptz)
किसी दिए गए timestamp with time zone
. को "सामान्यीकृत" करने के लिए :
CREATE OR REPLACE FUNCTION f_hoo_time(timestamptz)
RETURNS timestamp
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$
SELECT timestamp '1996-01-01' + ($1 AT TIME ZONE 'UTC' - date_trunc('week', $1 AT TIME ZONE 'UTC'))
$func$;
PARALLEL SAFE
केवल पोस्टग्रेज़ 9.6 या बाद के संस्करण के लिए।
फ़ंक्शन timestamptz
takes लेता है और देता है timestamp
. यह संबंधित सप्ताह के बीते हुए अंतराल को जोड़ता है ($1 - date_trunc('week', $1)
यूटीसी समय में हमारे मंचन सप्ताह के शुरुआती बिंदु तक। (date
+ interval
timestamp
produces उत्पन्न करता है ।)
फ़ंक्शन f_hoo_hours(timestamptz, timestamptz)
पर्वतमाला को सामान्य करने और सोम 00:00 को पार करने वालों को विभाजित करने के लिए। यह फ़ंक्शन कोई भी अंतराल लेता है (दो timestamptz
. के रूप में ) और एक या दो सामान्यीकृत tsrange
. उत्पन्न करता है मूल्य। इसमें कोई भी . शामिल है कानूनी इनपुट और बाकी को अस्वीकार करता है:
CREATE OR REPLACE FUNCTION f_hoo_hours(_from timestamptz, _to timestamptz)
RETURNS TABLE (hoo_hours tsrange)
LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE COST 500 ROWS 1 AS
$func$
DECLARE
ts_from timestamp := f_hoo_time(_from);
ts_to timestamp := f_hoo_time(_to);
BEGIN
-- sanity checks (optional)
IF _to <= _from THEN
RAISE EXCEPTION '%', '_to must be later than _from!';
ELSIF _to > _from + interval '1 week' THEN
RAISE EXCEPTION '%', 'Interval cannot span more than a week!';
END IF;
IF ts_from > ts_to THEN -- split range at Mon 00:00
RETURN QUERY
VALUES (tsrange('1996-01-01', ts_to , '[]'))
, (tsrange(ts_from, '1996-01-08', '[]'));
ELSE -- simple case: range in standard week
hoo_hours := tsrange(ts_from, ts_to, '[]');
RETURN NEXT;
END IF;
RETURN;
END
$func$;
करने के लिए INSERT
एक एकल इनपुट पंक्ति:
INSERT INTO hoo(shop_id, hours)
SELECT 123, f_hoo_hours('2016-01-11 00:00+04', '2016-01-11 08:00+04');
किसी भी के लिए इनपुट पंक्तियों की संख्या:
INSERT INTO hoo(shop_id, hours)
SELECT id, f_hoo_hours(f, t)
FROM (
VALUES (7, timestamptz '2016-01-11 00:00+0', timestamptz '2016-01-11 08:00+0')
, (8, '2016-01-11 00:00+1', '2016-01-11 08:00+1')
) t(id, f, t);
यदि किसी श्रेणी को सोम 00:00 यूटीसी पर विभाजित करने की आवश्यकता है तो प्रत्येक दो पंक्तियों को सम्मिलित कर सकता है।
क्वेरी
समायोजित डिज़ाइन के साथ, आपकी पूरी बड़ी, जटिल, महंगी क्वेरी से बदला जा सकता है ... यह:
<ब्लॉकक्वॉट क्लास ="स्पॉइलर">
SELECT *
FROM hoo
WHERE hours @> f_hoo_time(now());
<उप>थोड़ा सस्पेंस के लिए मैंने घोल के ऊपर एक स्पॉइलर प्लेट लगाई। माउस ओवर करें यह।उप>
यह क्वेरी उक्त जिस्ट इंडेक्स द्वारा समर्थित है और बड़ी तालिकाओं के लिए भी तेज़ है।
db<>फिडल यहाँ (अधिक उदाहरणों के साथ)
पुराना sqlfiddle
यदि आप कुल खुलने का समय (प्रति दुकान) की गणना करना चाहते हैं, तो यहां एक नुस्खा है:
- PostgreSQL में 2 तिथियों के बीच काम के घंटों की गणना करें
सूचकांक और प्रदर्शन
श्रेणी के प्रकारों के लिए नियंत्रण ऑपरेटर को GiST या SP-GiST . द्वारा समर्थित किया जा सकता है अनुक्रमणिका। या तो एक बहिष्करण बाधा को लागू करने के लिए इस्तेमाल किया जा सकता है, लेकिन केवल जीआईएसटी बहु-स्तंभ अनुक्रमणिका का समर्थन करता है:
<ब्लॉकक्वॉट>वर्तमान में, केवल B-tree, GiST, GIN, और BRIN अनुक्रमणिका प्रकार बहु-स्तंभ अनुक्रमणिका का समर्थन करते हैं।
और अनुक्रमणिका स्तंभों का क्रम मायने रखता है:
<ब्लॉकक्वॉट>एक बहु-स्तंभ जिस्ट इंडेक्स का उपयोग क्वेरी शर्तों के साथ किया जा सकता है जिसमें इंडेक्स के कॉलम के किसी भी सबसेट को शामिल किया जाता है। अतिरिक्त कॉलम पर शर्तें इंडेक्स द्वारा लौटाई गई प्रविष्टियों को प्रतिबंधित करती हैं, लेकिन पहले कॉलम पर शर्त यह निर्धारित करने के लिए सबसे महत्वपूर्ण है कि इंडेक्स को कितना स्कैन करने की आवश्यकता है। एक GiST इंडेक्स अपेक्षाकृत अप्रभावी होगा यदि इसके पहले कॉलम में केवल कुछ विशिष्ट मान हैं, भले ही अतिरिक्त कॉलम में कई अलग-अलग मान हों।
इसलिए हमारे पास परस्पर विरोधी हित हैं यहाँ। बड़ी तालिकाओं के लिए, shop_id
. के लिए और भी कई अलग-अलग मान होंगे hours
. की तुलना में ।
- अग्रणी
shop_id
के साथ एक GiST अनुक्रमणिका लिखने और बहिष्करण बाधा को लागू करने के लिए तेज़ है। - लेकिन हम
hours
खोज रहे हैं हमारी क्वेरी में। पहले उस कॉलम का होना बेहतर होगा। - अगर हमें
shop_id
देखने की जरूरत है अन्य प्रश्नों में, उसके लिए एक सादा btree अनुक्रमणिका बहुत तेज़ है। - उसे पूरा करने के लिए, मुझे एक SP-GiST मिला केवल
hours
. पर अनुक्रमणिका सबसे तेज़ . होने के लिए क्वेरी के लिए।
बेंचमार्क
पुराने लैपटॉप पर पोस्टग्रेज 12 के साथ नया परीक्षण। डमी डेटा उत्पन्न करने के लिए मेरी स्क्रिप्ट:
INSERT INTO hoo(shop_id, hours)
SELECT id
, f_hoo_hours(((date '1996-01-01' + d) + interval '4h' + interval '15 min' * trunc(32 * random())) AT TIME ZONE 'UTC'
, ((date '1996-01-01' + d) + interval '12h' + interval '15 min' * trunc(64 * random() * random())) AT TIME ZONE 'UTC')
FROM generate_series(1, 30000) id
JOIN generate_series(0, 6) d ON random() > .33;
~ 141k बेतरतीब ढंग से उत्पन्न पंक्तियों में परिणाम, ~ 30k विशिष्ट shop_id
, ~ 12k अलग hours
. टेबल का आकार 8 एमबी.
मैंने बहिष्करण प्रतिबंध को हटा दिया और फिर से बनाया:
ALTER TABLE hoo
DROP CONSTRAINT hoo_no_overlap
, ADD CONSTRAINT hoo_no_overlap EXCLUDE USING gist (shop_id WITH =, hours WITH &&); -- 3.5 sec; index 8 MB
ALTER TABLE hoo
DROP CONSTRAINT hoo_no_overlap
, ADD CONSTRAINT hoo_no_overlap EXCLUDE USING gist (hours WITH &&, shop_id WITH =); -- 13.6 sec; index 12 MB
shop_id
इस वितरण के लिए पहला ~ 4x तेज है।
इसके अलावा, मैंने पढ़ने के प्रदर्शन के लिए दो और परीक्षण किए:
CREATE INDEX hoo_hours_gist_idx on hoo USING gist (hours);
CREATE INDEX hoo_hours_spgist_idx on hoo USING spgist (hours); -- !!
VACUUM FULL ANALYZE hoo;
, मैंने दो क्वेरी चलाईं:
- Q1 :देर रात, केवल 35 पंक्तियाँ ढूंढ रहे हैं
- Q2 :दोपहर में, 4547 पंक्तियां ढूंढ रहे हैं ।
परिणाम
एक केवल-अनुक्रमणिका स्कैन मिला है प्रत्येक के लिए (बेशक "नो इंडेक्स" को छोड़कर):
index idx size Q1 Q2
------------------------------------------------
no index 38.5 ms 38.5 ms
gist (shop_id, hours) 8MB 17.5 ms 18.4 ms
gist (hours, shop_id) 12MB 0.6 ms 3.4 ms
gist (hours) 11MB 0.3 ms 3.1 ms
spgist (hours) 9MB 0.7 ms 1.8 ms -- !
- SP-GiST और GiST कुछ परिणाम खोजने वाले प्रश्नों के लिए बराबर हैं (GiST बहुत के लिए और भी तेज़ है) कुछ)।
- SP-GiST परिणामों की बढ़ती संख्या के साथ बेहतर होता है, और छोटा भी होता है।
यदि आप लिखने से बहुत अधिक पढ़ते हैं (सामान्य उपयोग के मामले), तो बहिष्करण बाधा को शुरुआत में सुझाए गए अनुसार रखें और पठन प्रदर्शन को अनुकूलित करने के लिए एक अतिरिक्त SP-GiST अनुक्रमणिका बनाएं।