PostgreSQL
 sql >> डेटाबेस >  >> RDS >> PostgreSQL

आप तारीख गणित कैसे करते हैं जो वर्ष की उपेक्षा करता है?

यदि आप स्पष्टीकरण और विवरण की परवाह नहीं करते हैं, तो "काला जादू संस्करण" . का उपयोग करें नीचे।

अन्य उत्तरों में प्रस्तुत सभी प्रश्न अब तक उन शर्तों के साथ काम करते हैं जो सरल योग्य नहीं हैं - वे एक इंडेक्स का उपयोग नहीं कर सकते हैं और मेल खाने वाली पंक्तियों को खोजने के लिए बेस टेबल में प्रत्येक पंक्ति के लिए अभिव्यक्ति की गणना करनी होगी। छोटी टेबल के साथ ज्यादा फर्क नहीं पड़ता। मायने रखता है (बहुत ) बड़ी टेबल के साथ।

निम्नलिखित सरल तालिका को देखते हुए:

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() कॉल। चयन अंतिम सरल जुड़ाव के साथ किया जाता है।

<एच4>2. उन्नत संस्करण
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() बहुत बनें एक सूचकांक के साथ सस्ता। दोनों बाकी की तुलना में काफी तेज हैं जो इंडेक्स का उपयोग नहीं कर सकते हैं।

  • मेरा "काला जादू संस्करण" अनुक्रमणिका के साथ या उसके बिना सबसे तेज़ है . और यह बहुत है कॉल करने में आसान।

  • वास्तविक जीवन तालिका के साथ एक सूचकांक और भी बड़ा बना देगा अंतर। अधिक कॉलम तालिका को बड़ा बनाते हैं, और अनुक्रमिक स्कैन अधिक महंगा बनाते हैं, जबकि अनुक्रमणिका का आकार वही रहता है।



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. अपने PostgreSQL संस्करण की जांच कैसे करें

  2. कैसे Cos () PostgreSQL में काम करता है

  3. पोर्ट 5432 अवरुद्ध होने पर pg_dump दूरस्थ सर्वर से डेटाबेस पोस्टग्रेज करता है

  4. डेटाबेस से कनेक्ट होने के बाद भूमिका बदलें

  5. PostgreSQL और Apache Spark के साथ बड़ा डेटा