यदि आप स्पष्टीकरण और विवरण की परवाह नहीं करते हैं, तो "काला जादू संस्करण" . का उपयोग करें नीचे।
अन्य उत्तरों में प्रस्तुत सभी प्रश्न अब तक उन शर्तों के साथ काम करते हैं जो सरल योग्य नहीं हैं - वे एक इंडेक्स का उपयोग नहीं कर सकते हैं और मेल खाने वाली पंक्तियों को खोजने के लिए बेस टेबल में प्रत्येक पंक्ति के लिए अभिव्यक्ति की गणना करनी होगी। छोटी टेबल के साथ ज्यादा फर्क नहीं पड़ता। मायने रखता है (बहुत ) बड़ी टेबल के साथ।
निम्नलिखित सरल तालिका को देखते हुए:
CREATE TABLE event (
event_id serial PRIMARY KEY
, event_date date
);
क्वेरी
संस्करण 1 और 2 नीचे दिए गए फॉर्म की एक साधारण अनुक्रमणिका का उपयोग कर सकते हैं:
CREATE INDEX event_event_date_idx ON event(event_date);
लेकिन निम्नलिखित सभी समाधान सूचकांक के बिना भी तेज़ हैं ।
<एच4>1. सरल संस्करणSELECT *
FROM (
SELECT ((current_date + d) - interval '1 year' * y)::date AS event_date
FROM generate_series( 0, 14) d
CROSS JOIN generate_series(13, 113) y
) x
JOIN event USING (event_date);
सबक्वेरी x
CROSS JOIN
. से दिए गए वर्षों में सभी संभावित तिथियों की गणना करता है दो में से generate_series()
कॉल। चयन अंतिम सरल जुड़ाव के साथ किया जाता है।
WITH val AS (
SELECT extract(year FROM age(current_date + 14, min(event_date)))::int AS max_y
, extract(year FROM age(current_date, max(event_date)))::int AS min_y
FROM event
)
SELECT e.*
FROM (
SELECT ((current_date + d.d) - interval '1 year' * y.y)::date AS event_date
FROM generate_series(0, 14) d
,(SELECT generate_series(min_y, max_y) AS y FROM val) y
) x
JOIN event e USING (event_date);
वर्षों की सीमा को तालिका से स्वचालित रूप से घटा दिया जाता है - जिससे उत्पन्न वर्ष कम से कम हो जाते हैं।
आप कर सकते थे एक कदम और आगे बढ़ें और अगर कोई अंतराल हो तो मौजूदा वर्षों की सूची तैयार करें।
प्रभावशीलता सह-तिथियों के वितरण पर निर्भर करती है। कई पंक्तियों के साथ कुछ साल इस समाधान को और अधिक उपयोगी बनाते हैं। कई वर्षों में कुछ पंक्तियों के साथ प्रत्येक इसे कम उपयोगी बनाता है।
सरल SQL Fiddle साथ खेलने के लिए।
<एच4>3. काला जादू संस्करण
<उप>एक "जेनरेटेड कॉलम" को हटाने के लिए 2016 को अपडेट किया गया, जो एच.ओ.टी. अद्यतन; सरल और तेज़ कार्य।
MMDD की गणना करने के लिए 2018 को अपडेट किया गया IMMUTABLE
फ़ंक्शन इनलाइनिंग की अनुमति देने के लिए भाव।
integer
की गणना करने के लिए एक सरल SQL फ़ंक्शन बनाएं पैटर्न से 'MMDD'
:
CREATE FUNCTION f_mmdd(date) RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT (EXTRACT(month FROM $1) * 100 + EXTRACT(day FROM $1))::int';
मेरे पास to_char(time, 'MMDD')
. था सबसे पहले, लेकिन उपरोक्त अभिव्यक्ति पर स्विच किया गया जो पोस्टग्रेज़ 9.6 और 10 पर नए परीक्षणों में सबसे तेज़ साबित हुआ:
db<>फिडल यहाँ
यह फ़ंक्शन इनलाइनिंग की अनुमति देता है क्योंकि EXTRACT (xyz FROM date)
IMMUTABLE
. के साथ क्रियान्वित किया जाता है फ़ंक्शन date_part(text, date)
आंतरिक रूप से। और यह IMMUTABLE
होना चाहिए निम्नलिखित आवश्यक बहु-स्तंभ अभिव्यक्ति अनुक्रमणिका में इसके उपयोग की अनुमति देने के लिए:
CREATE INDEX event_mmdd_event_date_idx ON event(f_mmdd(event_date), event_date);
बहु-स्तंभ कई कारणों से:ORDER BY
में सहायता कर सकते हैं या दिए गए वर्षों में से चयन के साथ। यहां पढ़ें। सूचकांक के लिए लगभग कोई अतिरिक्त लागत नहीं। एक date
4 बाइट्स में फिट बैठता है जो अन्यथा डेटा संरेखण के कारण पैडिंग में खो जाएगा। यहां पढ़ें।
इसके अलावा, चूंकि दोनों इंडेक्स कॉलम एक ही टेबल कॉलम को संदर्भित करते हैं, इसलिए H.O.T. के संबंध में कोई दोष नहीं है। अद्यतन। यहां पढ़ें।
एक PL/pgSQL तालिका उन सभी पर शासन करने के लिए कार्य करती है
वर्ष के अंत को कवर करने के लिए दो प्रश्नों में से एक को फोर्क करें:
CREATE OR REPLACE FUNCTION f_anniversary(date = current_date, int = 14)
RETURNS SETOF event AS
$func$
DECLARE
d int := f_mmdd($1);
d1 int := f_mmdd($1 + $2 - 1); -- fix off-by-1 from upper bound
BEGIN
IF d1 > d THEN
RETURN QUERY
SELECT *
FROM event e
WHERE f_mmdd(e.event_date) BETWEEN d AND d1
ORDER BY f_mmdd(e.event_date), e.event_date;
ELSE -- wrap around end of year
RETURN QUERY
SELECT *
FROM event e
WHERE f_mmdd(e.event_date) >= d OR
f_mmdd(e.event_date) <= d1
ORDER BY (f_mmdd(e.event_date) >= d) DESC, f_mmdd(e.event_date), event_date;
-- chronological across turn of the year
END IF;
END
$func$ LANGUAGE plpgsql;
कॉल करें डिफ़ॉल्ट का उपयोग करना:"आज" से शुरू होने वाले 14 दिन:
SELECT * FROM f_anniversary();
'2014-08-23' से शुरू होने वाले 7 दिनों के लिए कॉल करें:
SELECT * FROM f_anniversary(date '2014-08-23', 7);
एसक्यूएल फिडल EXPLAIN ANALYZE
. की तुलना करना ।
फरवरी 29
वर्षगांठ या "जन्मदिन" के साथ काम करते समय, आपको यह परिभाषित करने की आवश्यकता है कि लीप वर्ष में "29 फरवरी" विशेष मामले से कैसे निपटें।
तिथियों की श्रेणी के लिए परीक्षण करते समय, Feb 29
आमतौर पर स्वचालित रूप से शामिल किया जाता है, भले ही चालू वर्ष लीप वर्ष न हो . जब यह इस दिन को कवर करता है तो दिनों की सीमा 1 पूर्वव्यापी रूप से बढ़ा दी जाती है। दूसरी ओर, यदि चालू वर्ष एक लीप वर्ष है, और आप 15 दिनों के लिए देखना चाहते हैं, तो आपको 14 के लिए परिणाम मिल सकते हैं। लीप वर्ष में दिन यदि आपका डेटा गैर-लीप वर्ष से है।
मान लीजिए, बॉब का जन्म 29 फरवरी को हुआ है:
मेरी क्वेरी 1 और 2। केवल लीप वर्ष में 29 फरवरी को शामिल करें। बॉब का जन्मदिन केवल ~ 4 साल में होता है।
मेरी क्वेरी 3. रेंज में 29 फरवरी शामिल है। बॉब का हर साल जन्मदिन होता है।
कोई जादुई समाधान नहीं है। आपको यह परिभाषित करना होगा कि आप प्रत्येक मामले के लिए क्या चाहते हैं।
परीक्षा
अपनी बात को पुष्ट करने के लिए मैंने सभी प्रस्तुत समाधानों के साथ एक व्यापक परीक्षण किया। मैंने प्रत्येक प्रश्न को दी गई तालिका में अनुकूलित किया और ORDER BY
. के बिना समान परिणाम प्राप्त करने के लिए अनुकूलित किया ।
अच्छी खबर:वे सभी सही हैं और एक ही परिणाम प्राप्त करें - गॉर्डन की क्वेरी को छोड़कर जिसमें सिंटैक्स त्रुटियां थीं, और @ वाइल्डप्लेसर की क्वेरी जो विफल हो जाती है जब वर्ष समाप्त हो जाता है (ठीक करने में आसान)।
20वीं शताब्दी से यादृच्छिक तिथियों के साथ 108000 पंक्तियाँ डालें, जो जीवित लोगों (13 या उससे अधिक) की तालिका के समान है।
INSERT INTO event (event_date)
SELECT '2000-1-1'::date - (random() * 36525)::int
FROM generate_series (1, 108000);
कुछ मृत टुपल्स बनाने के लिए ~ 8% हटाएं और तालिका को और अधिक "वास्तविक जीवन" बनाएं।
DELETE FROM event WHERE random() < 0.08;
ANALYZE event;
मेरे परीक्षण मामले में 99289 पंक्तियाँ, 4012 हिट थीं।
C - कैटकॉल
WITH anniversaries as (
SELECT event_id, event_date
,(event_date + (n || ' years')::interval)::date anniversary
FROM event, generate_series(13, 113) n
)
SELECT event_id, event_date -- count(*) --
FROM anniversaries
WHERE anniversary BETWEEN current_date AND current_date + interval '14' day;
C1 - Catcall का विचार फिर से लिखा गया
मामूली अनुकूलन के अलावा, मुख्य अंतर केवल सटीक वर्षों की राशि add को जोड़ने का है date_trunc('year', age(current_date + 14, event_date))
इस साल की सालगिरह पाने के लिए, जो पूरी तरह से सीटीई की आवश्यकता से बचा जाता है:
SELECT event_id, event_date
FROM event
WHERE (event_date + date_trunc('year', age(current_date + 14, event_date)))::date
BETWEEN current_date AND current_date + 14;
D - डेनियल
SELECT * -- count(*) --
FROM event
WHERE extract(month FROM age(current_date + 14, event_date)) = 0
AND extract(day FROM age(current_date + 14, event_date)) <= 14;
E1 - इरविन 1
ऊपर "1. सरल संस्करण" देखें।
E2 - इरविन 2
ऊपर "2. उन्नत संस्करण" देखें।
E3 - इरविन 3
ऊपर "3. काला जादू संस्करण" देखें।
G - गॉर्डन
SELECT * -- count(*)
FROM (SELECT *, to_char(event_date, 'MM-DD') AS mmdd FROM event) e
WHERE to_date(to_char(now(), 'YYYY') || '-'
|| (CASE WHEN mmdd = '02-29' THEN '02-28' ELSE mmdd END)
,'YYYY-MM-DD') BETWEEN date(now()) and date(now()) + 14;
H - a_horse_with_no_name
WITH upcoming as (
SELECT event_id, event_date
,CASE
WHEN date_trunc('year', age(event_date)) = age(event_date)
THEN current_date
ELSE cast(event_date + ((extract(year FROM age(event_date)) + 1)
* interval '1' year) AS date)
END AS next_event
FROM event
)
SELECT event_id, event_date
FROM upcoming
WHERE next_event - current_date <= 14;
W - वाइल्डप्लेसर
CREATE OR REPLACE FUNCTION this_years_birthday(_dut date) RETURNS date AS
$func$
DECLARE
ret date;
BEGIN
ret :=
date_trunc( 'year' , current_timestamp)
+ (date_trunc( 'day' , _dut)
- date_trunc( 'year' , _dut));
RETURN ret;
END
$func$ LANGUAGE plpgsql;
अन्य सभी के समान लौटाने के लिए सरल:
SELECT *
FROM event e
WHERE this_years_birthday( e.event_date::date )
BETWEEN current_date
AND current_date + '2weeks'::interval;
W1 - वाइल्डप्लेसर की क्वेरी फिर से लिखी गई
उपरोक्त कई अक्षम विवरणों से ग्रस्त है (इस पहले से ही बड़े पद के दायरे से परे)। पुनर्लेखित संस्करण बहुत है तेज़:
CREATE OR REPLACE FUNCTION this_years_birthday(_dut INOUT date) AS
$func$
SELECT (date_trunc('year', now()) + ($1 - date_trunc('year', $1)))::date
$func$ LANGUAGE sql;
SELECT *
FROM event e
WHERE this_years_birthday(e.event_date)
BETWEEN current_date
AND (current_date + 14);
परीक्षा परिणाम
मैंने इस परीक्षण को PostgreSQL 9.1.7 पर एक अस्थायी तालिका के साथ चलाया। परिणाम EXPLAIN ANALYZE
के साथ एकत्र किए गए थे , बेस्ट ऑफ़ 5.
परिणाम
Without index C: Total runtime: 76714.723 ms C1: Total runtime: 307.987 ms -- ! D: Total runtime: 325.549 ms E1: Total runtime: 253.671 ms -- ! E2: Total runtime: 484.698 ms -- min() & max() expensive without index E3: Total runtime: 213.805 ms -- ! G: Total runtime: 984.788 ms H: Total runtime: 977.297 ms W: Total runtime: 2668.092 ms W1: Total runtime: 596.849 ms -- ! With index E1: Total runtime: 37.939 ms --!! E2: Total runtime: 38.097 ms --!! With index on expression E3: Total runtime: 11.837 ms --!!
अन्य सभी क्वेरी इंडेक्स के साथ या बिना इंडेक्स के समान प्रदर्शन करती हैं क्योंकि वे गैर-सरगने योग्य . का उपयोग करती हैं भाव।
निष्कर्ष
-
अब तक, @Daniel की क्वेरी सबसे तेज़ थी।
-
@wildplasers (पुनः लिखित) दृष्टिकोण भी स्वीकार्य रूप से प्रदर्शन करता है।
-
@ Catcall का संस्करण मेरे विपरीत दृष्टिकोण जैसा कुछ है। बड़ी तालिकाओं के साथ प्रदर्शन जल्दी से हाथ से निकल जाता है।
फिर से लिखा गया संस्करण बहुत अच्छा प्रदर्शन करता है, हालांकि। मैं जिस अभिव्यक्ति का उपयोग करता हूं वह @wildplassser केthis_years_birthday()
के सरल संस्करण जैसा कुछ है समारोह। -
मेरा "सरल संस्करण" तेज़ है सूचकांक के बिना भी , क्योंकि इसे कम संगणना की आवश्यकता है।
-
अनुक्रमणिका के साथ, "उन्नत संस्करण" "सरल संस्करण" जितना तेज़ है, क्योंकि
min()
औरmax()
बहुत बनें एक सूचकांक के साथ सस्ता। दोनों बाकी की तुलना में काफी तेज हैं जो इंडेक्स का उपयोग नहीं कर सकते हैं। -
मेरा "काला जादू संस्करण" अनुक्रमणिका के साथ या उसके बिना सबसे तेज़ है . और यह बहुत है कॉल करने में आसान।
-
वास्तविक जीवन तालिका के साथ एक सूचकांक और भी बड़ा बना देगा अंतर। अधिक कॉलम तालिका को बड़ा बनाते हैं, और अनुक्रमिक स्कैन अधिक महंगा बनाते हैं, जबकि अनुक्रमणिका का आकार वही रहता है।