विशेष कठिनाई इस कार्य का:आप केवल अपनी समय सीमा के भीतर डेटा बिंदु नहीं चुन सकते हैं, लेकिन नवीनतम पर विचार करना होगा डेटा बिंदु पहले समय सीमा और जल्द से जल्द डेटा बिंदु बाद अतिरिक्त समय सीमा। यह प्रत्येक पंक्ति के लिए भिन्न होता है और प्रत्येक डेटा बिंदु मौजूद हो भी सकता है और नहीं भी। एक परिष्कृत क्वेरी की आवश्यकता है और अनुक्रमणिका का उपयोग करना कठिन बनाता है।
आप श्रेणी के प्रकार का उपयोग कर सकते हैं और ऑपरेटर (पोस्टग्रेज 9.2+ ) गणनाओं को सरल बनाने के लिए:
WITH input(a,b) AS (SELECT '2013-01-01'::date -- your time frame here
, '2013-01-15'::date) -- inclusive borders
SELECT store_id, product_id
, sum(upper(days) - lower(days)) AS days_in_range
, round(sum(value * (upper(days) - lower(days)))::numeric
/ (SELECT b-a+1 FROM input), 2) AS your_result
, round(sum(value * (upper(days) - lower(days)))::numeric
/ sum(upper(days) - lower(days)), 2) AS my_result
FROM (
SELECT store_id, product_id, value, s.day_range * x.day_range AS days
FROM (
SELECT store_id, product_id, value
, daterange (day, lead(day, 1, now()::date)
OVER (PARTITION BY store_id, product_id ORDER BY day)) AS day_range
FROM stock
) s
JOIN (
SELECT daterange(a, b+1) AS day_range
FROM input
) x ON s.day_range && x.day_range
) sub
GROUP BY 1,2
ORDER BY 1,2;
ध्यान दें, मैं कॉलम नाम day
. का उपयोग करता हूं date
. के बजाय . मैं स्तंभ नामों के रूप में मूल प्रकार के नामों का कभी भी उपयोग नहीं करता।
सबक्वेरी में sub
मैं विंडो फ़ंक्शन के साथ प्रत्येक आइटम के लिए अगली पंक्ति से दिन लाता हूं lead()
, "आज" को डिफ़ॉल्ट रूप से प्रदान करने के लिए अंतर्निहित विकल्प का उपयोग करते हुए जहां कोई अगली पंक्ति नहीं है।
इसके साथ मैं एक daterange
बनाता हूं और इसे इनपुट के साथ ओवरलैप ऑपरेटर &&
. के साथ मिलाएं , परिणामी दिनांक सीमा की गणना चौराहे ऑपरेटर *
. के साथ करें ।
यहां सभी श्रेणियां अनन्य . के साथ हैं ऊपरी सीमा। इसलिए मैं एक दिन इनपुट रेंज में जोड़ता हूं। इस तरह हम आसानी से lower(range)
. को घटा सकते हैं upper(range)
. से दिनों की संख्या प्राप्त करने के लिए।
मुझे लगता है कि "कल" विश्वसनीय डेटा के साथ नवीनतम दिन है। "आज" अभी भी वास्तविक जीवन के अनुप्रयोग में बदल सकता है। नतीजतन, मैं "आज" (now()::date
. का उपयोग करता हूं ) खुली सीमाओं के लिए अनन्य ऊपरी सीमा के रूप में।
मैं दो परिणाम प्रदान करता हूं:
-
your_result
आपके प्रदर्शित परिणामों से सहमत हैं।
आप बिना शर्त अपनी तिथि सीमा में दिनों की संख्या से विभाजित करते हैं। उदाहरण के लिए, यदि कोई आइटम केवल अंतिम दिन के लिए सूचीबद्ध है, तो आपको बहुत कम (भ्रामक!) "औसत" मिलता है। -
my_result
समान या उच्चतर संख्याओं की गणना करता है।
मैं वास्तविक . से विभाजित करता हूं किसी आइटम के सूचीबद्ध होने के दिनों की संख्या. उदाहरण के लिए, यदि कोई आइटम केवल अंतिम दिन के लिए सूचीबद्ध है, तो मैं सूचीबद्ध मान को औसत के रूप में वापस कर देता हूं।
अंतर को समझने के लिए मैंने आइटम के सूचीबद्ध होने के दिनों की संख्या को जोड़ा:days_in_range
सूचकांक और प्रदर्शन
इस प्रकार के डेटा के लिए, पुरानी पंक्तियाँ आमतौर पर नहीं बदलती हैं। यह एक भौतिक दृश्य . के लिए एक उत्कृष्ट मामला बन जाएगा :
CREATE MATERIALIZED VIEW mv_stock AS
SELECT store_id, product_id, value
, daterange (day, lead(day, 1, now()::date) OVER (PARTITION BY store_id, product_id
ORDER BY day)) AS day_range
FROM stock;
फिर आप एक GiST इंडेक्स जोड़ सकते हैं जो संबंधित ऑपरेटर का समर्थन करता है &&
:
CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);
बड़ा परीक्षण मामला
मैंने 200k पंक्तियों के साथ अधिक यथार्थवादी परीक्षण चलाया। एमवी का उपयोग करने वाली क्वेरी लगभग 6 गुना तेज थी, जो कि @ जोप की क्वेरी से ~ 10x तेज थी। प्रदर्शन काफी हद तक डेटा वितरण पर निर्भर करता है। एक एमवी बड़ी टेबल और प्रविष्टियों की उच्च आवृत्ति के साथ सबसे अधिक मदद करता है। साथ ही, यदि तालिका में ऐसे कॉलम हैं जो इस क्वेरी के लिए प्रासंगिक नहीं हैं, तो एमवी छोटा हो सकता है। लागत बनाम लाभ का प्रश्न।
मैंने अब तक पोस्ट किए गए सभी समाधानों (और अनुकूलित) को खेलने के लिए एक बड़ी पहेली में डाल दिया है:
SQL Fiddle with big test case.
SQL Fiddle केवल 40k पंक्तियों के साथ
- sqlfiddle.com पर टाइमआउट से बचने के लिए