2012-06-26 13 views
7

SQL của tôi hơi bị gỉ và tôi gặp khá nhiều khó khăn với vấn đề này. Giả sử tôi có một bảng với cột Dấu thời gian và cột Số. Mục đích là trả về một tập kết quả có chứa giá trị trung bình cho một số khoảng thời gian thường xuyên được chọn tùy ý.Chức năng tổng hợp trong một khoảng thời gian nhất định

Vì vậy, ví dụ, nếu tôi có dữ liệu ban đầu sau đó, kết quả đầu ra với một khoảng thời gian 5 phút sẽ như sau:

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 

Lưu ý rằng đây là một cơ sở dữ liệu Oracle, vì vậy giải pháp Oracle-cụ thể sẽ hoạt động tốt. Điều này có thể, tất nhiên, được thực hiện với một thủ tục được lưu trữ nhưng tôi đã hy vọng để hoàn thành nhiệm vụ trong một truy vấn duy nhất.

+0

Phiên bản Oracle 10g +? – Sebas

+1

Có, xin lỗi - 10g – Nick

Trả lời

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 

fiddle: http://sqlfiddle.com/#!4/9e314/11

chỉnh sửa: beated bởi Justin, như thường lệ ... :-)

+0

Cảm ơn một tấn cho sự giúp đỡ - câu trả lời tuyệt vời! – Nick

+0

giải pháp này là chậm đối với tôi, cho 100k hồ sơ phải mất 5 phút để thực thi, mặc dù nó không hoạt động. – tosi

+0

tt.time được lập chỉ mục? – Sebas

5

Something như

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 

nên làm việc. Thay vì tạo 10 khoảng thời gian và mã hóa cứng khoảng thời gian thấp nhất, bạn có thể nâng cao truy vấn để lấy được điểm xuất phát và số hàng từ MIN(time)MAX(time) trong bảng.

+0

Cảm ơn sự giúp đỡ - bạn chắc chắn là một bậc thầy của nghề thủ công của bạn. – Nick

1

Đây là một giải pháp cho SQL Server:

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 

Ngày bắt đầu là tùy ý. Ý tưởng là bạn tính toán số phút từ ngày bắt đầu, sau đó nhóm theo số này chia cho khoảng thời gian.

Nó phải thích nghi với Oracle dễ dàng bằng cách sử dụng tương đương cho DATEADDDATEDIFF

+0

Thích nghi với Oracle như một phụ lục cho câu trả lời của tôi. Oracle không cung cấp các hàm DATEADD hoặc DATEDIFF, nhưng thay vào đó sử dụng số học đơn giản. – spencer7593

+0

Bạn có thể giải thích chính xác những gì 's_modifiedDate' đại diện chính xác không? – zvonicek

+0

Điều này thực hiện như thế nào? Bởi vì nếu tôi có một dữ liệu thứ hai và sau đó tôi đi và tổng hợp nó hơn 1 năm. Đó là chức năng 'AVG()' sẽ thực hiện một số công việc nghiêm túc? – Zapnologica

3

câu trả lời Sebas' Justin và có thể được mở rộng với một LEFT JOIN để loại bỏ 'khoảng trống', mà là thường mong muốn.

Nếu đó là không cần thiết, như một sự thay thế, chúng ta có thể đi học cũ Oracle NGÀY số học ...

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 

Hãy giải nén một chút. Chúng ta có thể tách các thành phần ngày và giờ, sử dụng TRUNC để lấy phần ngày tháng và sử dụng TO_CHAR để trả về số giây kể từ nửa đêm. Chúng tôi biết 5 phút là 300 giây và chúng tôi biết có 86400 giây trong một ngày. Vì vậy, chúng ta có thể chia số giây cho 300, và lấy FLOOR (chỉ là phần nguyên), làm tròn chúng ta xuống ranh giới 5 phút gần nhất. Chúng ta nhân số đó lại (bằng 300), để lấy lại giây, và sau đó chia số giây đó trong một ngày (86400), và chúng ta có thể thêm nó vào phần ngày tháng (cắt ngắn).

Đau đớn, vâng. Nhưng nhanh quá.

LƯU Ý: điều này trả về giá trị thời gian làm tròn là DATE, điều này có thể được đưa trở lại dấu thời gian nếu cần, nhưng đối với ranh giới thậm chí 5 phút, DATE có đủ độ phân giải.

Là một lợi ích của phương pháp này, cho một bảng lớn, chúng ta có thể tăng hiệu suất của truy vấn bằng cách thêm một chỉ số bao phủ cho truy vấn này:

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

PHỤ LỤC:

MiMo cung cấp câu trả lời cho SQL Server, cho thấy rằng nó có thể thích ứng với Oracle. Đây là một sự thích nghi của cách tiếp cận đó trong Oracle. Lưu ý rằng Oracle không cung cấp các hàm tương đương cho các hàm DATEDIFF và DATEADD. Oracle sử dụng số học đơn giản để thay thế.

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 

Sự lựa chọn của Jan 1, 0001 AD là một ngày cơ sở là tùy ý, nhưng tôi không muốn gây rối với các giá trị âm, và tìm hiểu xem SÀN sẽ là đúng, hay là chúng ta sẽ cần phải sử dụng CEIL với số âm. (Số ma thuật 288 là kết quả của 1440 phút trong một ngày chia cho 5). Trong trường hợp này, chúng tôi đang lấy ngày phân số, nhân với 1440 và chia cho 5, và lấy phần nguyên của nó, và sau đó đưa nó trở lại ngày phân số. Nó là hấp dẫn để kéo "cơ sở ngày" từ một gói PL/SQL, hoặc nhận được nó từ một truy vấn phụ, nhưng làm một trong những người có thể ngăn chặn biểu thức này được xác định. Và chúng tôi thực sự muốn tiếp tục mở tùy chọn tạo chỉ mục dựa trên chức năng.

Tùy chọn của tôi là để tránh nhu cầu bao gồm "ngày cơ sở" trong tính toán.

+0

Cảm ơn sự cố! Rất thông tin và hữu ích. – Nick