अलग माह और वर्ष कॉलम, या दिन हमेशा दिनांक 1 के साथ सेट करें?


15

मैं Postgres के साथ एक डेटाबेस जहां से चीजों के समूहीकरण का एक बहुत होने के लिए वहाँ जा रहा है निर्माण कर रहा हूँ monthऔर yearसे, लेकिन कभी नहीं date

  • मैं पूर्णांक monthऔर yearकॉलम बना सकता हूं और उन का उपयोग कर सकता हूं ।
  • या मैं एक month_yearकॉलम रख सकता था और हमेशा day1 पर सेट करता था।

यदि कोई डेटा देख रहा है तो पूर्व थोड़ा सरल और स्पष्ट लगता है, लेकिन बाद वाला इसमें अच्छा है कि यह एक उचित प्रकार का उपयोग करता है।


1
या आप अपना स्वयं का डेटा प्रकार बना सकते हैं monthजिसमें दो पूर्णांक होते हैं। लेकिन मुझे लगता है कि यदि आप कभी नहीं, कभी महीने के दिन की जरूरत है, दो पूर्णांक का उपयोग करना शायद आसान है
a_horse_with_no_name

1
आपको तिथियों की संभावित सीमा, पंक्तियों की संभावित संख्या, जिसे आप अनुकूलित करने की कोशिश कर रहे हैं (भंडारण, प्रदर्शन, सुरक्षा, सरलता?) और (हमेशा की तरह) पोस्टग्रेज का अपना संस्करण घोषित करना चाहिए।
इरविन ब्रान्डेसटेटर

जवाबों:


17

व्यक्तिगत रूप से अगर यह एक तारीख है, या एक तारीख हो सकती है, मेरा सुझाव है कि इसे हमेशा एक के रूप में संग्रहीत करें। अंगूठे के नियम के साथ काम करना आसान है।

  • एक तारीख 4 बाइट्स है।
  • एक छोटा सा 2 बाइट्स है (हमें दो की आवश्यकता है)
    • ... 2 बाइट्स: वर्ष के लिए एक स्मॉलिंट
    • ... 2 बाइट्स: महीने के लिए एक स्मॉलिंट

आपके पास एक तारीख हो सकती है जो दिन का समर्थन करेगी यदि आपको कभी इसकी आवश्यकता होती है, या एक smallintवर्ष और महीने के लिए जो कभी भी अतिरिक्त परिशुद्धता का समर्थन नहीं करेगा।

नमूना डेटा

आइए अब एक उदाहरण देखें .. चलो हमारे नमूने के लिए 1 मिलियन तिथियां बनाएं। यह 1901 और 2100 के बीच 200 वर्षों के लिए लगभग 5,000 पंक्तियाँ हैं। हर साल हर महीने के लिए कुछ होना चाहिए।

CREATE TABLE foo
AS
  SELECT
    x,
    make_date(year,month,1)::date AS date,
    year::smallint,
    month::smallint
  FROM generate_series(1,1e6) AS gs(x)
  CROSS JOIN LATERAL CAST(trunc(random()*12+1+x-x) AS int) AS month
  CROSS JOIN LATERAL CAST(trunc(random()*200+1901+x-x) AS int) AS year
;
CREATE INDEX ON foo(date);
CREATE INDEX ON foo (year,month);
VACUUM FULL ANALYZE foo;

परिक्षण

सरल WHERE

अब हम तारीख का उपयोग न करने के इन सिद्धांतों का परीक्षण कर सकते हैं .. मैंने इनमें से प्रत्येक को कुछ समय तक चलाया ताकि चीजों को गर्म किया जा सके।

EXPLAIN ANALYZE SELECT * FROM foo WHERE date = '2014-1-1'
                                                        QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=11.56..1265.16 rows=405 width=14) (actual time=0.164..0.751 rows=454 loops=1)
   Recheck Cond: (date = '2014-04-01'::date)
   Heap Blocks: exact=439
   ->  Bitmap Index Scan on foo_date_idx  (cost=0.00..11.46 rows=405 width=0) (actual time=0.090..0.090 rows=454 loops=1)
         Index Cond: (date = '2014-04-01'::date)
 Planning time: 0.090 ms
 Execution time: 0.795 ms

अब, दूसरी विधि को उनके साथ अलग करके देखें

EXPLAIN ANALYZE SELECT * FROM foo WHERE year = 2014 AND month = 1;
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=12.75..1312.06 rows=422 width=14) (actual time=0.139..0.707 rows=379 loops=1)
   Recheck Cond: ((year = 2014) AND (month = 1))
   Heap Blocks: exact=362
   ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.64 rows=422 width=0) (actual time=0.079..0.079 rows=379 loops=1)
         Index Cond: ((year = 2014) AND (month = 1))
 Planning time: 0.086 ms
 Execution time: 0.749 ms
(7 rows)

निष्पक्षता में, वे सभी 0.749 नहीं हैं .. कुछ अधिक या कम हैं, लेकिन इससे कोई फर्क नहीं पड़ता। वे सभी अपेक्षाकृत समान हैं। यह बस जरूरत नहीं है।

एक महीने के भीतर

अब, चलो इसके साथ मज़े करते हैं .. मान लीजिए कि आप जनवरी 2014 के 1 महीने के भीतर सभी अंतरालों को ढूंढना चाहते हैं (उसी महीने हमने ऊपर इस्तेमाल किया था)।

EXPLAIN ANALYZE
  SELECT *
  FROM foo
  WHERE date
    BETWEEN
      ('2014-1-1'::date - '1 month'::interval)::date 
      AND ('2014-1-1'::date + '1 month'::interval)::date;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=21.27..2310.97 rows=863 width=14) (actual time=0.384..1.644 rows=1226 loops=1)
   Recheck Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
   Heap Blocks: exact=1083
   ->  Bitmap Index Scan on foo_date_idx  (cost=0.00..21.06 rows=863 width=0) (actual time=0.208..0.208 rows=1226 loops=1)
         Index Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
 Planning time: 0.104 ms
 Execution time: 1.727 ms
(7 rows)

इसकी तुलना संयुक्त विधि से करें

EXPLAIN ANALYZE
  SELECT *
  FROM foo
  WHERE year = 2013 AND month = 12
    OR ( year = 2014 AND ( month = 1 OR month = 2) );

                                                                 QUERY PLAN                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=38.79..2999.66 rows=1203 width=14) (actual time=0.664..2.291 rows=1226 loops=1)
   Recheck Cond: (((year = 2013) AND (month = 12)) OR (((year = 2014) AND (month = 1)) OR ((year = 2014) AND (month = 2))))
   Heap Blocks: exact=1083
   ->  BitmapOr  (cost=38.79..38.79 rows=1237 width=0) (actual time=0.479..0.479 rows=0 loops=1)
         ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.64 rows=421 width=0) (actual time=0.112..0.112 rows=402 loops=1)
               Index Cond: ((year = 2013) AND (month = 12))
         ->  BitmapOr  (cost=25.60..25.60 rows=816 width=0) (actual time=0.218..0.218 rows=0 loops=1)
               ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.62 rows=420 width=0) (actual time=0.108..0.108 rows=423 loops=1)
                     Index Cond: ((year = 2014) AND (month = 1))
               ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.38 rows=395 width=0) (actual time=0.108..0.108 rows=401 loops=1)
                     Index Cond: ((year = 2014) AND (month = 2))
 Planning time: 0.256 ms
 Execution time: 2.421 ms
(13 rows)

यह धीमी, और बदसूरत दोनों है।

GROUP BY/ORDER BY

संयुक्त विधि,

EXPLAIN ANALYZE
  SELECT date, count(*)
  FROM foo
  GROUP BY date
  ORDER BY date;
                                                        QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=20564.75..20570.75 rows=2400 width=4) (actual time=286.749..286.841 rows=2400 loops=1)
   Sort Key: date
   Sort Method: quicksort  Memory: 209kB
   ->  HashAggregate  (cost=20406.00..20430.00 rows=2400 width=4) (actual time=285.978..286.301 rows=2400 loops=1)
         Group Key: date
         ->  Seq Scan on foo  (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.012..70.582 rows=1000000 loops=1)
 Planning time: 0.094 ms
 Execution time: 286.971 ms
(8 rows)

और फिर से समग्र विधि के साथ

EXPLAIN ANALYZE
  SELECT year, month, count(*)
  FROM foo
  GROUP BY year, month
  ORDER BY year, month;
                                                        QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=23064.75..23070.75 rows=2400 width=4) (actual time=336.826..336.908 rows=2400 loops=1)
   Sort Key: year, month
   Sort Method: quicksort  Memory: 209kB
   ->  HashAggregate  (cost=22906.00..22930.00 rows=2400 width=4) (actual time=335.757..336.060 rows=2400 loops=1)
         Group Key: year, month
         ->  Seq Scan on foo  (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.010..70.468 rows=1000000 loops=1)
 Planning time: 0.098 ms
 Execution time: 337.027 ms
(8 rows)

निष्कर्ष

आम तौर पर, स्मार्ट लोगों को कड़ी मेहनत करने दें। डेटमथ कठिन है, मेरे ग्राहक मुझे पर्याप्त भुगतान नहीं करते हैं। मैं ये टेस्ट करता था। मुझे यह निष्कर्ष निकालने के लिए कड़ी मेहनत की गई थी कि मैं इससे बेहतर परिणाम प्राप्त कर सकता हूं date। मैंने कोशिश करना बंद कर दिया।

अद्यतन

@a_horse_with_no_name ने एक महीने के परीक्षण के लिए मेरा सुझाव दिया WHERE (year, month) between (2013, 12) and (2014,2)। मेरे विचार में, जबकि यह एक और अधिक जटिल प्रश्न है, और जब तक कोई लाभ नहीं होता, मैं इसे टाल देता। काश, यह अभी भी धीमा था, हालांकि यह करीब है - जो इस परीक्षण से दूर ले जाने के लिए अधिक है। यह बस ज्यादा मायने नहीं रखता।

EXPLAIN ANALYZE
  SELECT *
  FROM foo
  WHERE (year, month) between (2013, 12) and (2014,2);

                                                              QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=5287.16..15670.20 rows=248852 width=14) (actual time=0.753..2.157 rows=1226 loops=1)
   Recheck Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
   Heap Blocks: exact=1083
   ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..5224.95 rows=248852 width=0) (actual time=0.550..0.550 rows=1226 loops=1)
         Index Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
 Planning time: 0.099 ms
 Execution time: 2.249 ms
(7 rows)

4
कुछ अन्य आरडीबीएमएस (के देखें पेज 45 के विपरीत use-the-index-luke.com/blog/2013-07/... ), Postgres भी पूरी तरह से पंक्ति मूल्यों के साथ सूचकांक पहुँच का समर्थन: stackoverflow.com/a/34291099/939860 लेकिन वह एक है एक तरफ, मैं पूरी तरह से सहमत हूं: dateज्यादातर मामलों में जाने का तरीका है।
इरविन ब्रान्डेसटेटर

5

इवान कैरोल प्रस्तावित पद्धति के विकल्प के रूप में, जिसे मैं शायद सबसे अच्छा विकल्प मानता हूं, मैंने कुछ अवसरों में उपयोग किया है (और विशेष रूप से पोस्टग्रेक्यूएल का उपयोग करते समय) केवल एक year_monthकॉलम, प्रकार INTEGER(4 बाइट्स) के रूप में, गणना की गई

 year_month = year * 100 + month

यही है, आप पूर्णांक संख्या के दो सबसे सही दशमलव अंकों (अंक 0, और अंक 1) पर महीने को सांकेतिक शब्दों में बदलते हैं , और अंक 2 पर वर्ष 5 (या अधिक, यदि आवश्यक हो)।

यह कुछ हद तक, एक गरीब आदमी का अपने year_monthप्रकार और ऑपरेटरों के निर्माण का विकल्प है । यह कुछ फायदे हैं, ज्यादातर "इरादे की स्पष्टता", और दो अलग-अलग कॉलम होने पर कुछ अंतरिक्ष बचत (पोस्टग्रेक्यूएल में नहीं, मुझे लगता है), और कुछ असुविधाएं भी हैं।

आप यह गारंटी दे सकते हैं कि मान केवल जोड़कर मान्य हैं

CHECK ((year_date % 100) BETWEEN 1 AND 12)   /*  % = modulus operator */

आप एक WHEREखंड की तरह लग सकते हैं:

year_month BETWEEN 201610 and 201702 

और यह कुशलता से काम करता है (यदि year_monthस्तंभ ठीक से अनुक्रमित है, तो निश्चित रूप से)।

आप year_monthउसी तरह से समूह बना सकते हैं जैसे आप इसे एक तारीख के साथ कर सकते हैं, और उसी दक्षता (कम से कम) के साथ।

यदि आपको अलग करने की आवश्यकता है yearऔर month, गणना सीधी है:

month = year_month % 100    -- % is modulus operator
year  = year_month / 100    -- / is integer division 

क्या असुविधाजनक है : यदि आप 15 महीने जोड़ना चाहते हैं तो आपको year_monthगणना करनी होगी (यदि मैंने कोई गलती नहीं की है या निरीक्षण किया है):

year_month + delta (months) = ...

    /* intermediate calculations */
    year = year_month/100 + delta/12    /* years we had + new years */
           + (year_month % 100 + delta%12) / 12  /* extra months make 1 more year? */
    month = ((year_month%10) + (delta%12) - 1) % 12 + 1

/* final result */
... = year * 100 + month

यदि आप सावधान नहीं हैं, तो यह त्रुटि प्रवण हो सकता है।

यदि आप दो साल के बीच महीनों की संख्या प्राप्त करना चाहते हैं तो आपको कुछ समान संगणना करने की आवश्यकता है। यह (बहुत सारे सरलीकरण के साथ) वास्तव में तारीख अंकगणित के साथ हुड के तहत क्या होता है, यह सौभाग्य से पहले से ही परिभाषित कार्यों और ऑपरेटरों के माध्यम से हमसे छिपा हुआ है।

यदि आपको इन ऑपरेशनों की बहुत आवश्यकता है, तो उपयोग year_monthकरना बहुत व्यावहारिक नहीं है। यदि आप नहीं करते हैं, तो यह आपके इरादे को स्पष्ट करने का एक बहुत ही स्पष्ट तरीका है।


एक विकल्प के रूप में, आप एक year_monthप्रकार को परिभाषित कर सकते हैं , और एक ऑपरेटर year_month+ को परिभाषित कर सकते हैं interval, और दूसरा भी year_month- year_month... और गणना छिपा सकते हैं। मैंने वास्तव में अभ्यास में आवश्यकता महसूस करने के लिए इतना भारी उपयोग कभी नहीं किया है। A date- dateवास्तव में आपको कुछ इसी तरह छिपा रहा है।


1
मैंने ऐसा करने का एक और तरीका लिखा है =) इसका आनंद लें।
इवान कैरोल

मैं कैसे और साथ ही पेशेवरों और विपक्ष की सराहना करता हूं।
फ्यूनहे

4

जोनोलो की विधि के विकल्प के रूप में =) (खेद है कि मैं व्यस्त था लेकिन यह लिखना चाहता था)

बीआईटी जोय

हम एक ही काम करने जा रहे हैं, लेकिन बिट्स के साथ। एक int4PostgreSQL में, एक हस्ताक्षरित पूर्णांक है -२१४७४८३६४८ से २१४७४८३६४७ को लेकर

यहाँ हमारी संरचना का अवलोकन है।

               bit                
----------------------------------
 YYYYYYYYYYYYYYYYYYYYYYYYYYYYMMMM

महीने का भंडारण।

  • एक महीने में 12 विकल्पों की आवश्यकता होती pow(2,4)है 4 बिट्स
  • बाकी हम वर्ष के लिए समर्पित करते हैं, 32-4 = 28 बिट्स

यहां हमारा बिट मैप है जहां महीनों संग्रहीत हैं।

               bit                
----------------------------------
 00000000000000000000000000001111

महीने, 1-जनवरी - 12 दिसंबर

               bit                
----------------------------------
 00000000000000000000000000000001
               bit                
----------------------------------
 00000000000000000000000000001100

वर्षों। शेष 28 बिट्स हमें अपनी वर्ष-जानकारी संग्रहीत करने की अनुमति देते हैं

SELECT (pow(2,28)-1)::int;
   int4    
-----------
 268435455
(1 row)

इस बिंदु पर हमें यह तय करने की आवश्यकता है कि हम यह कैसे करना चाहते हैं। हमारे उद्देश्यों के लिए, हम एक स्थिर ऑफसेट का उपयोग कर सकते हैं, अगर हमें केवल 5,000 AD को कवर करने की आवश्यकता है, तो हम वापस जा सकते हैं 268,430,455 BCजो मेसोज़ोइक की संपूर्णता को कवर करता है और सब कुछ उपयोगी आगे बढ़ रहा है।

SELECT (pow(2,28)-1)::int4::bit(32) << 4;
               year               
----------------------------------
 11111111111111111111111111110000

और, अब हमारे पास हमारे प्रकार की अशिष्टताएं हैं, जो 2,700 वर्षों में समाप्त होने वाली हैं।

तो चलिए कुछ फंक्शन बनाने पर काम करते हैं।

CREATE DOMAIN year_month AS int4;

CREATE OR REPLACE FUNCTION to_year_month (cstring text)
RETURNS year_month
AS $$
  SELECT (
    ( ((date[1]::int4 - 5000) * -1)::bit(32) << 4 )
    | date[2]::int4::bit(32)
  )::year_month
  FROM regexp_split_to_array(cstring,'-(?=\d{1,2}$)')
    AS t(date)
$$
LANGUAGE sql
IMMUTABLE;

CREATE OR REPLACE FUNCTION year_month_to_text (ym year_month)
RETURNS text
AS $$
  SELECT ((ym::bit(32) >>4)::int4 * -1 + 5000)::text ||
  '-' ||
  (ym::bit(32) <<28 >>28)::int4::text
$$ LANGUAGE sql
IMMUTABLE;

एक त्वरित परीक्षण इस काम को दर्शाता है।

SELECT year_month_to_text( to_year_month('2014-12') );
SELECT year_month_to_text( to_year_month('-5000-10') );
SELECT year_month_to_text( to_year_month('-8000-10') );
SELECT year_month_to_text( to_year_month('-84398-10') );

अब हमारे पास कार्य हैं जो हम अपने बाइनरी प्रकारों पर उपयोग कर सकते हैं।

हम हस्ताक्षरित हिस्से से एक और बिट काट सकते हैं, वर्ष को सकारात्मक के रूप में संग्रहीत किया, और फिर इसे स्वाभाविक रूप से हस्ताक्षरित इंट के रूप में सॉर्ट किया गया। यदि भंडारण स्थान की तुलना में गति एक उच्च प्राथमिकता थी, तो हम नीचे जाने वाले मार्ग पर होते। लेकिन अभी के लिए, हमारे पास एक तारीख है जो मेसोजोइक के साथ काम करती है।

मैं बाद में इसके साथ अपडेट कर सकता हूं, सिर्फ मनोरंजन के लिए।


रंग अभी तक संभव नहीं हैं, मैं बाद में इसे देखूंगा।
इवान कैरोल

मुझे लगता है कि "बिट के लिए अनुकूलन" सभी समझ में आता है जब आप "निम्न स्तर सी" में भी सभी कार्य करेंगे। आप डाउन-टू-लास्ट बिट और डाउन-टू-द-अंतिम नैनोसेकंड ;-) किसी भी तरह, खुशहाल बचाओ! (मैं अभी भी बीसीडी को याद करता हूं। जरूरी नहीं कि खुशी के साथ।)
जोनलो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.