तारीख के अंतराल पर रोलिंग योग / गणना / औसत


20

18 महीनों में 1,000 संस्थाओं का विस्तार करने वाले लेन-देन के एक डेटाबेस में, मैं 30 दिनों की अवधि entity_idमें उनके लेनदेन की राशि और उनके लेनदेन के सीओयूएम के साथ हर संभव 30-दिन की अवधि के लिए एक क्वेरी चलाना चाहता हूं , और डेटा को इस तरह से लौटाएं कि मैं फिर क्वेरी कर सकूं। बहुत सारे परीक्षण के बाद, यह कोड मुझे जो चाहिए वह पूरा करता है:

SELECT id, trans_ref_no, amount, trans_date, entity_id,
    SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
    COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
  FROM transactiondb;

और मैं एक बड़ी क्वेरी में संरचित कुछ का उपयोग करूंगा:

SELECT * FROM (
  SELECT id, trans_ref_no, amount, trans_date, entity_id,
      SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
      COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
    FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;

यह मामला जो कवर नहीं करता है वह यह है कि जब लेन-देन की गणना में कई महीने लगेंगे, लेकिन फिर भी एक-दूसरे के 30 दिनों के भीतर होना चाहिए। क्या Postgres के साथ इस प्रकार की क्वेरी संभव है? यदि हां, तो मैं किसी भी इनपुट का स्वागत करता हूं। अन्य विषयों में से कई " चल रहा है " समुच्चय, रोलिंग नहीं ।

अपडेट करें

CREATE TABLEस्क्रिप्ट:

CREATE TABLE transactiondb (
    id integer NOT NULL,
    trans_ref_no character varying(255),
    amount numeric(18,2),
    trans_date date,
    entity_id integer
);

नमूना डेटा यहां पाया जा सकता है । मैं PostgreSQL 9.1.16 चला रहा हूं।

आदर्श उत्पादन को शामिल किया जाएगा SUM(amount)और COUNT()एक रोलिंग 30 दिन की अवधि में सभी लेनदेन के। इस छवि को देखें, उदाहरण के लिए:

पंक्तियों का उदाहरण जो आदर्श रूप से "सेट" में शामिल किया जाएगा, लेकिन ऐसा नहीं है क्योंकि मेरा सेट महीने तक स्थिर है।

हरे रंग की तारीख हाइलाइटिंग इंगित करती है कि मेरी क्वेरी द्वारा क्या शामिल किया जा रहा है। येलो रो हाइलाइटिंग रिकॉर्ड्स को इंगित करता है कि मैं सेट का हिस्सा क्या बनना चाहता हूं।

पिछला पढ़ने:


1
तक every possible 30-day period by entity_idतुम्हारा मतलब अवधि शुरू कर सकते हैं किसी भी दिन एक (गैर छलांग) वर्ष में तो 365 संभव अवधि,? या क्या आप केवल वास्तविक लेनदेन के साथ दिनों पर विचार करना चाहते हैं क्योंकि किसी भी अवधि के लिए व्यक्तिगत रूप से शुरू करना entity_id ? किसी भी तरह, कृपया अपनी तालिका परिभाषा, पोस्टग्रेज संस्करण, कुछ नमूना डेटा और नमूना के लिए अपेक्षित परिणाम प्रदान करें।
इरविन ब्रान्डस्टेट्टर

सिद्धांत रूप में, मेरा मतलब किसी भी दिन था, लेकिन व्यवहार में उन दिनों पर विचार करने की कोई आवश्यकता नहीं है जहां कोई लेनदेन नहीं है। मैंने नमूना डेटा और तालिका परिभाषा पोस्ट की है।
टफेलकाइंडर

इसलिए आप प्रत्येक वास्तविक लेनदेन पर शुरू होने वाली entity_id30-दिवसीय विंडो में उसी की पंक्तियों को जमा करना चाहते हैं । क्या एक ही के लिए कई लेनदेन हो सकते हैं या क्या संयोजन अद्वितीय है? आपकी तालिका की परिभाषा में कोई या पीके बाधा नहीं है, लेकिन अड़चनें गायब लगती हैं ...(trans_date, entity_id)UNIQUE
Erwin Brandstetter

एकमात्र बाधा idप्राथमिक कुंजी पर है। प्रति दिन प्रति इकाई कई लेनदेन हो सकते हैं।
tufelkinder

डेटा वितरण के बारे में: क्या अधिकांश दिनों के लिए प्रविष्टियाँ (प्रति यूनिट_आईडी) हैं?
एरविन ब्रैंडसेटेटर

जवाबों:


26

आपके पास जो क्वेरी है

आप एक WINDOWक्लॉज़ का उपयोग करके अपनी क्वेरी को सरल बना सकते हैं , लेकिन यह सिंटैक्स को छोटा कर रहा है, क्वेरी प्लान को नहीं बदल रहा है।

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date)
             ORDER BY trans_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
  • थोड़ा तेज का उपयोग करने के count(*)बाद से , idनिश्चित रूप से परिभाषित किया गया है NOT NULL?
  • और आपको ORDER BY entity_idपहले से ही जरूरत नहीं हैPARTITION BY entity_id

आप इसे और सरल कर सकते हैं, हालांकि: विंडो की परिभाषा को बिल्कुल भी
न जोड़ें ORDER BY, यह आपकी क्वेरी के लिए प्रासंगिक नहीं है। तब आपको कोई कस्टम विंडो फ़्रेम परिभाषित करने की आवश्यकता नहीं है:

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date);

सरल, तेज, लेकिन अभी भी आपके पास जो कुछ भी है उसका बेहतर संस्करण है , स्थिर महीनों के साथ ।

क्वेरी आप चाहते हो सकता है

... स्पष्ट रूप से परिभाषित नहीं है, इसलिए मैं इन धारणाओं पर निर्माण करूंगा:

किसी भी पहले और अंतिम लेनदेन के भीतर हर 30-दिन की अवधि के लिए लेनदेन और राशि की गणना करें entity_id। गतिविधि के बिना अग्रणी और अनुगामी अवधि को छोड़ दें, लेकिन उन बाहरी सीमाओं के भीतर सभी संभव 30-दिन की अवधि शामिल करें।

SELECT entity_id, trans_date
     , COALESCE(sum(daily_amount) OVER w, 0) AS trans_total
     , COALESCE(sum(daily_count)  OVER w, 0) AS trans_count
FROM  (
   SELECT entity_id
        , generate_series (min(trans_date)::timestamp
                         , GREATEST(min(trans_date), max(trans_date) - 29)::timestamp
                         , interval '1 day')::date AS trans_date
   FROM   transactiondb 
   GROUP  BY 1
   ) x
LEFT JOIN (
   SELECT entity_id, trans_date
        , sum(amount) AS daily_amount, count(*) AS daily_count
   FROM   transactiondb
   GROUP  BY 1, 2
   ) t USING (entity_id, trans_date)
WINDOW w AS (PARTITION BY entity_id ORDER BY trans_date
             ROWS BETWEEN CURRENT ROW AND 29 FOLLOWING);

यह entity_idआपके समुच्चय के साथ और अवधि trans_dateके पहले दिन (incl) होने के साथ प्रत्येक के लिए सभी 30-दिन की अवधि को सूचीबद्ध करता है । प्रत्येक व्यक्तिगत पंक्ति के लिए मान प्राप्त करने के लिए आधार तालिका में एक बार और शामिल हों ...

मूल कठिनाई यहाँ चर्चा के रूप में ही है:

विंडो की फ़्रेम परिभाषा वर्तमान पंक्ति के मूल्यों पर निर्भर नहीं कर सकती है।

और इनपुट के generate_series()साथ कॉल करें timestamp:

वह क्वेरी जो आप वास्तव में चाहते हैं

प्रश्न अद्यतन और चर्चा के बाद: प्रत्येक वास्तविक लेनदेन पर शुरू होने वाली 30-दिवसीय विंडो में
उसी की पंक्तियों को संचित करें entity_id

चूँकि आपके डेटा को बहुत कम वितरित किया जाता है, इसलिए रेंज कंडीशन के साथ सेल्फ-जॉइन को चलाने के लिए यह अधिक कुशल होना चाहिए , पोस्टग्रैजेस 9.1 के बाद से अधिक नहीं LATERALजुड़ता है, अभी तक:

SELECT t0.id, t0.amount, t0.trans_date, t0.entity_id
     , sum(t1.amount) AS trans_total, count(*) AS trans_count
FROM   transactiondb t0
JOIN   transactiondb t1 USING (entity_id)
WHERE  t1.trans_date >= t0.trans_date
AND    t1.trans_date <  t0.trans_date + 30  -- exclude upper bound
-- AND    t0.entity_id = 114284  -- or pick a single entity ...
GROUP  BY t0.id  -- is PK!
ORDER  BY t0.trans_date, t0.id

एसक्यूएल फिडल।

एक रोलिंग विंडो ज्यादातर दिनों के लिए केवल डेटा के साथ (प्रदर्शन के संबंध में) समझ में आ सकती है।

यह प्रति दिन डुप्लिकेट एकत्र नहीं करता (trans_date, entity_id)है, लेकिन एक ही दिन की सभी पंक्तियों को हमेशा 30-दिन की विंडो में शामिल किया जाता है।

एक बड़ी तालिका के लिए, इस तरह एक आवरण सूचकांक काफी मदद कर सकता है:

CREATE INDEX transactiondb_foo_idx
ON transactiondb (entity_id, trans_date, amount);

अंतिम कॉलम amountकेवल तभी उपयोगी होता है जब आपको इंडेक्स-ओनली स्कैन मिलता है। इसे छोड़ दो।

लेकिन जब भी आप पूरी तालिका का चयन करते हैं तो इसका उपयोग नहीं किया जाएगा। यह एक छोटे सबसेट के लिए प्रश्नों का समर्थन करेगा।


यह वास्तव में अच्छा लग रहा है, इसे अब डेटा पर परीक्षण कर रहा है, और आपकी क्वेरी को सब कुछ समझने की कोशिश कर रहा है ...
tufelkinder

@tufelkinder: अद्यतन किए गए प्रश्न के लिए एक समाधान जोड़ा गया।
एरविन ब्रान्डस्टेट्टर

अभी इसकी समीक्षा कर रहे हैं। मुझे लगता है कि यह एसक्यूएल फिडेल में चलता है ... जब मैं इसे सीधे अपने लेन-देन पर चलाने की कोशिश करता हूं, तो मुझे इससे column "t0.amount" must appear in the GROUP BY clause...
परेशानी

@tufelkinder: मैंने परीक्षण मामले को 100 पंक्तियों तक काट दिया। sqlfiddle परीक्षण डेटा के आकार को सीमित करता है। जेक (लेखक) ने कुछ महीने पहले सीमाएं कम कर दीं ताकि साइट कम आसानी से बंद हो जाए।
एरविन ब्रान्डस्टेट्टर

1
देरी के लिए क्षमा करें, इसे पूर्ण डेटाबेस पर परीक्षण करने की आवश्यकता है। आपका उत्तर हमेशा की तरह शानदार और शैक्षिक था। धन्यवाद!
tufelkinder
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.