2012-06-19 44 views
6

Tôi có hai bảng: "máy chủ" và "thống kê"MySQL - Các cột gần đây nhất trong bảng khác

máy chủ có cột được gọi là "id" tự động tăng. thống kê có cột được gọi là "máy chủ" tương ứng với một hàng trong bảng máy chủ, một cột được gọi là "thời gian" đại diện cho thời gian được thêm vào và cột được gọi là "phiếu bầu" mà tôi muốn có mức trung bình.

Tôi muốn tìm nạp tất cả các máy chủ (SELECT * FROM servers) cùng với số phiếu trung bình của 24 hàng gần đây nhất tương ứng với mỗi máy chủ. Tôi tin rằng đây là câu hỏi "lớn nhất trên mỗi nhóm".

Đây là những gì tôi đã cố gắng để làm, nhưng nó đã cho tôi tổng cộng 24 dòng, không 24 hàng mỗi nhóm:

SELECT servers.*, 
     IFNULL(AVG(stats.votes), 0) AS avgvotes 
FROM servers 
LEFT OUTER JOIN 
    (SELECT server, 
      votes 
    FROM stats 
    GROUP BY server 
    ORDER BY time DESC LIMIT 24) AS stats ON servers.id = stats.server 
GROUP BY servers.id 

Như tôi đã nói, tôi muốn để có được 24 hàng gần đây nhất cho mỗi máy chủ , không phải 24 hàng gần đây nhất.

+1

Tôi tin rằng [this] (http://sqlfiddle.com/#!2/d908f/5) là cấu trúc bảng của bảng của bạn . Đúng? –

Trả lời

1

Đây là một cách tiếp cận khác.

Truy vấn này sẽ gặp vấn đề về hiệu năng giống như các truy vấn khác ở đây trả về kết quả chính xác, vì kế hoạch thực hiện cho truy vấn này sẽ yêu cầu thao tác SORT trên hàng MỌI trong bảng thống kê. Vì không có biến vị ngữ (giới hạn) trên cột thời gian, nên mọi hàng trong bảng thống kê sẽ được xem xét. Đối với một bảng lớn stats thực sự, điều này sẽ thổi ra tất cả không gian tạm thời có sẵn trước khi nó chết một cái chết khủng khiếp. (Thêm ghi chú về hiệu suất bên dưới.)

SELECT r.* 
    , IFNULL(s.avg_votes,0) 
    FROM servers r 
    LEFT 
    JOIN (SELECT t.server 
       , AVG(t.votes) AS avg_votes 
      FROM (SELECT CASE WHEN u.server = @last_server 
          THEN @i := @i + 1 
          ELSE @i := 1 
         END AS i 
         , @last_server := u.server AS `server` 
         , u.votes AS votes 
        FROM (SELECT @i := 0, @last_server := NULL) i 
        JOIN (SELECT v.server, v.votes 
          FROM stats v 
          ORDER BY v.server DESC, v.time DESC 
         ) u 
       ) t 
      WHERE t.i <= 24 
      GROUP BY t.server 
     ) s 
    ON s.server = r.id 

Truy vấn này đang sắp xếp bảng thống kê, theo máy chủ và theo thứ tự giảm dần trên cột thời gian. (Chế độ xem nội tuyến được đặt tên là u.)

Với tập hợp kết quả được sắp xếp, chúng tôi gán một số hàng 1,2,3, v.v ... cho mỗi hàng cho mỗi máy chủ. (Chế độ xem nội dòng được đặt biệt hiệu là t.)

Với tập hợp kết quả đó, chúng tôi lọc ra bất kỳ hàng nào có giá trị> 24 và chúng tôi tính trung bình của cột votes cho 24 hàng "mới nhất" cho mỗi máy chủ. (Chế độ xem nội tuyến được đặt tên là s.)

Bước cuối cùng, chúng tôi kết hợp với bảng máy chủ để trả về kết quả được yêu cầu.


LƯU Ý:

Các kế hoạch thực hiện cho truy vấn này sẽ tốn kém cho một số lượng lớn các hàng trong bảng stats.

Để cải thiện hiệu suất, có một số phương pháp chúng tôi có thể thực hiện.

Đơn giản nhất có thể được bao gồm trong truy vấn vị từ EXCLUDES một số lượng đáng kể các hàng từ bảng stats (ví dụ: các hàng có giá trị trên 2 ngày hoặc hơn 2 tuần). Điều đó sẽ làm giảm đáng kể số hàng cần được sắp xếp, để xác định 24 hàng mới nhất.

Ngoài ra, với chỉ mục trên stats(server,time), cũng có thể là MySQL có thể thực hiện "quét đảo ngược" tương đối hiệu quả trên chỉ mục, tránh hoạt động sắp xếp.

Chúng tôi cũng có thể xem xét triển khai chỉ mục trên bảng thống kê trên (server,"reverse_time"). Vì MySQL chưa hỗ trợ các chỉ số giảm dần, việc triển khai thực sự sẽ là chỉ số thường xuyên (tăng dần) trên giá trị rtime bắt nguồn (biểu thức "đảo ngược thời gian" tăng dần cho giá trị giảm dần của time (ví dụ: -1*UNIX_TIMESTAMP(my_timestamp) hoặc -1*TIMESTAMPDIFF('1970-01-01',my_datetime).

Một cách tiếp cận khác để cải thiện hiệu suất là giữ một bảng bóng chứa 24 hàng gần đây nhất cho mỗi máy chủ. Về cơ bản, bất cứ khi nào một hàng được chèn vào bảng stats, chúng tôi kiểm tra xem time trên các hàng mới có muộn hơn time sớm nhất được lưu trữ cho máy chủ trong bóng tối hay không. bảng, nếu có, chúng tôi thay thế hàng sớm nhất trong bảng bóng với hàng mới, đảm bảo giữ không quá 24 hàng trong bảng bóng cho mỗi máy chủ.

Và, cách tiếp cận khác là viết thủ tục hoặc chức năng nhận kết quả. Cách tiếp cận ở đây sẽ là lặp qua mỗi máy chủ và chạy truy vấn riêng biệt với bảng thống kê để nhận số trung bình votes cho 24 hàng mới nhất và thu thập tất cả các kết quả đó cùng nhau. (Đó là cách tiếp cận hùng mạnh thực sự được nhiều hơn một cách giải quyết để tránh một loại trên bộ tạm thời rất lớn, chỉ để cho phép resultset được trả lại, không nhất thiết làm cho sự trở lại của resultset blazingly nhanh.)

Dòng dưới cùng cho hiệu suất của kiểu truy vấn này trên bảng LARGE đang hạn chế số hàng được truy vấn xem xét và tránh thao tác sắp xếp trên một tập hợp lớn. Đó là cách chúng tôi nhận được một truy vấn như thế này để thực hiện.


PHỤ LỤC

Để có được một "ngược chỉ số quét" hoạt động (để lấy hàng từ stats ra lệnh sử dụng một chỉ số KHÔNG một hoạt động filesort), tôi đã xác định giảm dần ở cả hai biểu thức trong Mệnh đề ORDER BY. Truy vấn ở trên trước đây có ORDER BY server ASC, time DESC và MySQL luôn muốn thực hiện một tệp, thậm chí chỉ định gợi ý FORCE INDEX FOR ORDER BY (stats_ix1).

Nếu yêu cầu trả lại 'số phiếu trung bình' cho máy chủ chỉ nếu có ít nhất 24 hàng được liên kết trong bảng thống kê, thì chúng tôi có thể thực hiện truy vấn hiệu quả hơn, ngay cả khi lộn xộn. (Hầu hết sự lộn xộn trong hàm IF() lồng nhau là đối phó với các giá trị NULL, không được bao gồm trong giá trị trung bình, có thể ít lộn xộn hơn nếu chúng ta bảo đảm rằng votes là NOT NULL, hoặc nếu chúng ta loại trừ bất kỳ hàng nơi votes là NULL.)

SELECT r.* 
    , IFNULL(s.avg_votes,0) 
    FROM servers r 
    LEFT 
    JOIN (SELECT t.server 
       , t.tot/NULLIF(t.cnt,0) AS avg_votes 
      FROM (SELECT IF(v.server = @last_server, @num := @num + 1, @num := 1) AS num 
         , @cnt := IF(v.server = @last_server,IF(@num <= 24, @cnt := @cnt + IF(v.votes IS NULL,0,1),@cnt := 0),@cnt := IF(v.votes IS NULL,0,1)) AS cnt 
         , @tot := IF(v.server = @last_server,IF(@num <= 24, @tot := @tot + IFNULL(v.votes,0)  ,@tot := 0),@tot := IFNULL(v.votes,0)  ) AS tot 
         , @last_server := v.server AS SERVER 
        -- , v.time 
        -- , v.votes 
        -- , @tot/NULLIF(@cnt,0) AS avg_sofar 
        FROM (SELECT @last_server := NULL, @num:= 0, @cnt := 0, @tot := 0) u 
        JOIN stats v FORCE INDEX FOR ORDER BY (stats_ix1) 
        ORDER BY v.server DESC, v.time DESC 
       ) t 
      WHERE t.num = 24 
     ) s 
    ON s.server = r.id 

với chỉ số bao phủ trên stats(server,time,votes), các giải thích cho thấy MySQL tránh một hoạt động filesort, vì vậy nó phải đã sử dụng một "ngược chỉ số quét" để trả lại hàng trong trật tự. Không có chỉ mục bao gồm, và chỉ mục trên '(máy chủ, thời gian) , MySQL used the index if I included an index hint, with the FORCE INDEX FOR ORDER BY (stats_ix1) `gợi ý, MySQL tránh một filesort là tốt. (Nhưng kể từ khi bảng của tôi có ít hơn 100 hàng, tôi không nghĩ rằng MySQL chú trọng nhiều vào việc tránh một hoạt động tập hợp.)

Biểu thức thời gian, phiếu bầu và avg_sofar được nhận xét (trong chế độ xem nội dòng được đặt tên là t); chúng không cần thiết, nhưng chúng là để gỡ lỗi.

Cách truy vấn đó đứng, nó cần ít nhất 24 hàng trong thống kê cho mỗi máy chủ, để trả về mức trung bình. (Điều đó có thể chấp nhận được.) Nhưng tôi đã nghĩ rằng nói chung, chúng ta có thể trả về tổng số đang chạy, tổng cộng cho đến nay (tot) và một số đếm (cnt).

(Nếu chúng ta thay thế WHERE t.num = 24 với WHERE t.num <= 24, chúng ta có thể thấy tỷ lệ trung bình chạy trong hành động.)

Để trở về mức trung bình mà không có ít nhất 24 dòng trong số liệu thống kê, đó là thực sự là một vấn đề xác định hàng (cho mỗi máy chủ) với giá trị tối đa là num là < = 24.

+0

Xin lỗi vì phản hồi muộn. Truy vấn này hoạt động và hoạt động nhanh hơn các câu trả lời trước.Tôi cũng rất cảm kích lời giải thích chi tiết của bạn và nhiều giải pháp của bạn để tăng tốc độ. Hiện tại có 40.000 hàng, tuy nhiên có tiềm năng tăng lên tới vài triệu. Tôi sẽ sử dụng một chỉ số ('số liệu thống kê (máy chủ, thời gian)') cho bây giờ và nếu có một hiệu suất đáng kể giảm tôi sẽ có khả năng thực hiện gợi ý bảng bóng tối của bạn. Cảm ơn bạn rât nhiêu! – fruitcup

+0

Chỉ số bao trùm trên 'số liệu thống kê (máy chủ, thời gian, phiếu bầu)' sẽ tốt hơn cho hiệu suất. Tôi đã thêm một phụ lục vào câu trả lời của tôi, với một truy vấn khác có thể nhanh hơn. Nó có một giới hạn (như văn bản của nó) rằng cần phải có ít nhất 24 hàng trong bảng thống kê cho một máy chủ cho một trung bình được trả về. – spencer7593

2

Cảm ơn vì điều này tuyệt vời post.

alter table add index(server, time) 
set @num:=0, @server:=''; 
select servers.*, IFNULL(AVG(stats.votes), 0) AS avgvotes 
from servers left outer join (
select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from stats force index(server) 
group by server, time 
having row_number < 25) as stats 
on servers.id = stats.server 
group by servers.id 

chỉnh sửa 1

Tôi chỉ nhận thấy rằng truy vấn trên cho 24 hồ sơ lâu đời nhất cho mỗi nhóm.

set @num:=0, @server:=''; 
select servers.*, IFNULL(AVG(stats.votes), 0) AS avgvotes 
from servers left outer join (
select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from (select * from stats order by server, time desc) as t 
group by server, time 
having row_number < 25) as stats 
on servers.id = stats.server 
group by servers.id 

mà sẽ cung cấp cho các trung bình của thực thể mới nhất 24 đối với từng nhóm

Edit2

@DrAgonmoray bạn có thể thử phần truy vấn nội đầu tiên và xem nếu nó trả về 24 hồ sơ mới nhất cho mỗi nhóm. Trong mysql 5.5, nó hoạt động chính xác.

select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from (select * from stats order by server, time desc) as t 
group by server, time 
having row_number < 25 
+0

Tôi nhận được lỗi cú pháp ở đây: 'select servers. *, IFNULL (AVG (stats.votes), 0) AS avgvotes từ máy chủ lef' tại dòng 2 – fruitcup

+1

@DrAgonmoray, đặt một ';' sau bảng 'thay đổi thêm ... 'dòng cũng như dòng' set @num ... 'khi chúng là các lệnh riêng biệt từ truy vấn thực tế. –

+0

Bây giờ mã hoạt động, tuy nhiên nó dường như mang lại cho tôi mức trung bình của tất cả các bản ghi cho mỗi máy chủ, thay vì chỉ là 24. Tôi đã thử nghiệm điều này bằng cách sử dụng một số máy chủ khác nhau. – fruitcup

0

Hãy thử giải pháp này, với top-n-per-group kỹ thuật trong INNER JOIN subselect ghi có vào Bill Karwin và sau mình về nó here.

SELECT 
    a.*, 
    AVG(b.votes) AS avgvotes 
FROM 
    servers a 
INNER JOIN 
    (
     SELECT 
      aa.server, 
      aa.votes 
     FROM 
      stats aa 
     LEFT JOIN stats bb ON 
      aa.server = bb.server AND 
      aa.time < bb.time 
     GROUP BY 
      aa.time 
     HAVING 
      COUNT(*) < 24 
    ) b ON a.id = b.server 
GROUP BY 
    a.id 
+0

Truy vấn này cực kỳ chậm vì một lý do nào đó. Tôi thực hiện nó và để cho nó ngồi trong vài phút và nó không kết thúc. Tôi không cần tốc độ cực cao, nhưng điều này quá dài. – fruitcup

+0

@DrAgonmoray Được rồi, tôi hiểu. Tôi sẽ cố gắng giải quyết tốt hơn. Cấu trúc lập chỉ mục của bạn như thế nào? Bạn có chỉ mục được thiết lập trên trường 'time' không? –

+0

Không, tôi không có chỉ mục được thiết lập trên trường thời gian, tuy nhiên tôi có thể thêm/xóa chỉ mục làm giải pháp yêu cầu. Hiện tại không có chỉ mục nào được xác định cho số liệu thống kê. – fruitcup

Các vấn đề liên quan