2012-06-26 10 views
7

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

तो, उदाहरण के लिए, इस प्रकार अगर मैं निम्नलिखित प्रारंभिक डेटा, एक 5 मिनट के अंतराल के साथ जिसके परिणामस्वरूप उत्पादन होगा था:

time        value 
------------------------------- ----- 
06-JUN-12 12.40.00.000000000 PM  2 
06-JUN-12 12.41.35.000000000 PM  3 
06-JUN-12 12.43.22.000000000 PM  4 
06-JUN-12 12.47.55.000000000 PM  5 
06-JUN-12 12.52.00.000000000 PM  2 
06-JUN-12 12.54.59.000000000 PM  3 
06-JUN-12 12.56.01.000000000 PM  4 

OUTPUT: 

start_time       avg_value 
------------------------------- --------- 
06-JUN-12 12.40.00.000000000 PM  3 
06-JUN-12 12.45.00.000000000 PM  5 
06-JUN-12 12.50.00.000000000 PM  2.5 
06-JUN-12 12.55.00.000000000 PM  4 

ध्यान दें कि यह एक Oracle डाटाबेस है, इसलिए ओरेकल-विशिष्ट समाधान ठीक काम करेगा। यह निश्चित रूप से एक संग्रहीत प्रक्रिया के साथ किया जा सकता है, लेकिन मैं एक ही प्रश्न में कार्य को पूरा करने की उम्मीद कर रहा था।

+0

ओरेकल संस्करण 10 जी +? – Sebas

+1

हां, क्षमा करें - 10 जी – Nick

उत्तर

8
CREATE TABLE tt (time TIMESTAMP, value NUMBER); 

INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.40.00.000000000 PM', 2); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.41.35.000000000 PM', 3); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.43.22.000000000 PM', 4); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.47.55.000000000 PM', 5); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.52.00.000000000 PM', 2); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.54.59.000000000 PM', 3); 
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.56.01.000000000 PM', 4); 


WITH tmin AS (
    SELECT MIN(time) t FROM tt 
), tmax AS (
    SELECT MAX(time) t FROM tt 
) 
SELECT ranges.inf, ranges.sup, AVG(tt.value) 
FROM 
    (
     SELECT 
      5*(level-1)*(1/24/60) + tmin.t as inf, 
      5*(level)*(1/24/60) + tmin.t as sup 
     FROM tmin, tmax 
     CONNECT BY (5*(level-1)*(1/24/60) + tmin.t) < tmax.t 
    ) ranges JOIN tt ON tt.time BETWEEN ranges.inf AND ranges.sup 
GROUP BY ranges.inf, ranges.sup 
ORDER BY ranges.inf 

बेला: http://sqlfiddle.com/#!4/9e314/11

संपादित करें: हमेशा की तरह जस्टिन द्वारा beated, ... :-)

+0

धन्यवाद मदद के लिए एक टन - भयानक जवाब! – Nick

+0

यह समाधान मेरे लिए धीमा है, 100k रिकॉर्ड के लिए इसे निष्पादित करने में 5 मिनट लगते हैं, हालांकि यह काम करता है। – tosi

+0

tt.time अनुक्रमित? – Sebas

5

तरह

with st 
    as (SELECT to_timestamp('2012-06-06 12:40:00', 'yyyy-mm-dd hh24:mi:ss') + 
       numtodsinterval((level-1)*5, 'MINUTE') start_time, 
      to_timestamp('2012-06-06 12:40:00', 'yyyy-mm-dd hh24:mi:ss') + 
       numtodsinterval(level*5, 'MINUTE') end_time 
     from dual 
    connect by level <= 10) 
SELECT st.start_time, avg(yt.value) 
    FROM your_table yt, 
     st 
WHERE yt.time between st.start_time and st.end_time 

काम करना चाहिए कुछ। 10 अंतराल उत्पन्न करने और निम्नतम अंतराल को हार्ड-कोडिंग करने के बजाय, आप प्रारंभिक बिंदु और तालिका में MIN(time) और MAX(time) से पंक्तियों की संख्या प्राप्त करने के लिए क्वेरी को बढ़ा सकते हैं।

+0

सहायता के लिए धन्यवाद - आप निश्चित रूप से अपने शिल्प के मालिक हैं। – Nick

1

यह एसक्यूएल सर्वर के लिए एक समाधान है:

declare @startDate datetime = '2000-01-01T00:00:00' 

declare @interval int = 5 

select 
    DATEADD(mi, DATEDIFF(mi, @startDate, time)/@interval, @startDate), 
    AVG(value) 
from 
    table 
group by 
    DATEDIFF(mi, @startDate, s_modifiedDate)/@interval 
order by 
    DATEDIFF(mi, @startDate, s_modifiedDate)/@interval 

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

यह आसानी से DATEADD और DATEDIFF

+0

को इंगित करता है जो मेरे उत्तर में एक परिशिष्ट के रूप में ओरेकल में अनुकूलित किया गया है। ओरेकल DATEADD या DATEDIFF फ़ंक्शंस प्रदान नहीं करता है, लेकिन इसके बजाय सरल अंकगणित का उपयोग करता है। – spencer7593

+0

कृपया आप बता सकते हैं कि 's_modifiedDate' वास्तव में क्या दर्शाता है? – zvonicek

+0

यह कैसे करता है? क्योंकि यदि मेरे पास प्रति सेकंड डेटा है और फिर मैं इसे 1 साल से अधिक कर देता हूं। वह 'एवीजी()' समारोह कुछ गंभीर काम करेगा? – Zapnologica

3

जस्टिन और Sebas के उत्तरों की एक वाम के साथ बढ़ाया जा सकता है शामिल हों के लिए बराबर का उपयोग कर, "अंतराल" को खत्म करने की जो अक्सर वांछनीय है ओरेकल के लिए अनुकूल होना चाहिए।

अगर ऐसा आवश्यक नहीं है, एक विकल्प के रूप में, हम पुराने स्कूल ओरेकल, DATE अंकगणित जा सकते हैं ...

SELECT TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400 AS time 
    , AVG(t.value) AS avg_value 
    FROM foo t 
WHERE t.time IS NOT NULL 
GROUP BY TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400 
ORDER BY TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400 

के एक सा है कि खोल दें। हम दिनांक भाग प्राप्त करने के लिए TRUNC का उपयोग करके दिनांक और समय घटकों को अलग कर सकते हैं, और मध्यरात्रि से सेकेंड की संख्या वापस करने के लिए TO_CHAR का उपयोग कर सकते हैं। हम जानते हैं कि 5 मिनट 300 सेकंड हैं, और हम जानते हैं कि दिन में 86400 सेकेंड हैं। इसलिए हम 300 से सेकंड की संख्या को विभाजित कर सकते हैं, और उस (केवल पूर्णांक भाग) का फ़्लोर ले सकते हैं, जो हमें निकटतम 5 मिनट की सीमा तक ले जाता है। हम सेकंड को फिर से प्राप्त करने के लिए उस बैक (300 से) गुणा करते हैं, और फिर उस दिन को विभाजित करते हैं (86400), और हम इसे वापस (संक्षिप्त) दिनांक भाग में जोड़ सकते हैं।

दर्दनाक, हाँ। लेकिन तेजस्वी तेजी से।

नोट: यह गोलाकार समय मान DATE के रूप में देता है, इसे आवश्यक होने पर टाइमस्टैम्प पर वापस लाया जा सकता है, लेकिन 5 मिनट की सीमाओं के लिए, DATE में पर्याप्त रिज़ॉल्यूशन है।

CREATE INDEX foo_FBX1 
ON foo (TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400,value); 

परिशिष्ट:

इस दृष्टिकोण का एक लाभ के रूप में, एक बड़ी मेज के लिए, हम क्वेरी के प्रदर्शन इस प्रश्न के लिए एक कवर सूचकांक जोड़कर बढ़ा सकते हैं MiMo ने SQL सर्वर के लिए एक उत्तर प्रदान किया, यह सुझाव दिया कि यह ओरेकल के लिए अनुकूलनीय होगा। ओरेकल में उस दृष्टिकोण का एक अनुकूलन यहां दिया गया है। ध्यान दें कि ओरेकल DATEDIFF और DATEADD फ़ंक्शंस के लिए समकक्ष प्रदान नहीं करता है। ओरेकल इसके बजाय सरल अंकगणित का उपयोग करता है।

SELECT TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288 
     AS time 
    , AVG(t.value) AS avg_value 
    FROM foo t 
WHERE t.time IS NOT NULL 
GROUP BY TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288 
ORDER BY TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288 

1 जनवरी विकल्प है, एक आधार तिथि के रूप में 0001 ई मनमाना है, लेकिन मैं नकारात्मक मूल्यों के साथ गड़बड़ नहीं करना चाहता था, और पता लगाना है, तो मंजिल सही हो सकता है, या कि क्या हम उनका उपयोग करना होगा नकारात्मक संख्या के साथ सीईआईएल। (जादू संख्या 288 540 से विभाजित दिन में 1440 मिनट का परिणाम है)। इस मामले में, हम आंशिक दिन ले रहे हैं, 1440 तक गुणा कर रहे हैं और 5 से विभाजित कर रहे हैं, और इसके पूर्णांक हिस्से को ले रहे हैं, और फिर इसे वापस आंशिक दिनों में डाल रहे हैं।

यह पीएल/एसक्यूएल पैकेज से "बेस डेट" खींचने के लिए मोहक है, या इसे सबक्वायरी से प्राप्त करें, लेकिन इनमें से कोई भी इस अभिव्यक्ति को निर्धारक होने से रोक सकता है। और हम वास्तव में फ़ंक्शन आधारित इंडेक्स बनाने का विकल्प खोलना चाहते हैं।

मेरी प्राथमिकता गणना में "आधार तिथि" को शामिल करने की आवश्यकता से बचने के लिए है।

+0

टूटने के लिए धन्यवाद! बहुत जानकारीपूर्ण और सहायक। – Nick

संबंधित मुद्दे