6

「サーバー」と「統計」の2つのテーブルがあります

サーバーには、自動インクリメントする「id」という列があります。statsには、serversテーブルの行に対応する「server」という列、追加された時刻を表す「time」という列、および平均を取得したい「votes」という列があります。

SELECT * FROM serversすべてのサーバー( )と、各サーバーに対応する最新の24行の平均投票数を取得したいと思います。これは「グループあたり最大の」質問だと思います。

これは私がやろうとしたことですが、グループごとに24行ではなく、合計24行になりました。

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

私が言ったように、私は各サーバーの最新の24行を取得したいのですが、最新の24行の合計ではありません。

4

3 に答える 3

2

この素晴らしい投稿をありがとう。

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

編集1

上記のクエリが各グループの最も古い24レコードを提供することに気づきました。

 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

これにより、各グループの最新の24のエンティティの平均が得られます。

Edit2

@DrAgonmorayでは、最初に内部クエリパーツを試して、各グループの最新の24レコードが返されるかどうかを確認できます。私のmysql5.5では、正しく動作します。

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
于 2012-06-23T06:46:05.070 に答える
1

これは別のアプローチです。

このクエリの実行プランでは、統計テーブルのすべての行でSORT操作が必要になるため、このクエリでは、正しい結果を返す他のクエリと同じパフォーマンスの問題が発生します。時間列には述語(制限)がないため、統計テーブルのすべての行が考慮されます。本当に大きなstatsテーブルの場合、これは恐ろしい死を迎える前に、利用可能なすべての一時的なスペースを吹き飛ばします。(以下のパフォーマンスに関するその他の注意事項。)

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

このクエリが実行しているのは、統計テーブルをサーバー別に、時間列の降順で並べ替えることです。(インラインビューのエイリアスはu。)

ソートされた結果セットを使用して、各サーバーの各行に行番号1、2、3などを割り当てます。(インラインビューのエイリアスはt。)

その結果セットを使用して、行番号が24を超えるすべての行を除外し、votes各サーバーの「最新の」24行の列の平均を計算します。(インラインビューのエイリアスはs。)

最後のステップとして、それをserversテーブルに結合して、要求された結果セットを返します。


ノート:

このクエリの実行プランは、テーブル内の多数の行に対してCOSTLYになりstatsます。

パフォーマンスを向上させるために、いくつかのアプローチをとることができます。

最も単純なのは、述語にstatsテーブルからかなりの数の行を除外することです(たとえばtime、2日以上前または2週間以上前の値を持つ行)。これにより、「最新の」24行を判別するために、ソートする必要のある行の数が大幅に削減されます。

また、インデックスをオンstats(server,time)にすると、MySQLがインデックスに対して比較的効率的な「逆スキャン」を実行し、ソート操作を回避できる可能性もあります。

の統計テーブルにインデックスを実装することも検討できます(server,"reverse_time")。MySQLはまだ降順のインデックスをサポートしていないため、実装は実際には派生値の通常の(昇順の)インデックスになります(たとえば、またはrtimeの降順の値に対して昇順の「逆時間」式です。time-1*UNIX_TIMESTAMP(my_timestamp)-1*TIMESTAMPDIFF('1970-01-01',my_datetime)

パフォーマンスを向上させる別のアプローチは、サーバーごとに最新の24行を含むシャドウテーブルを保持することです。stats「最新の行」がテーブルから削除されないことを保証できれば、これを実装するのが最も簡単です。そのテーブルをトリガーで維持できます。基本的に、行がテーブルに挿入されるたびに、新しい行のがシャドウテーブルのサーバーに格納されている最も早い行よりも遅いstatsかどうかを確認します。遅い場合は、シャドウテーブルの最も早い行を新しい行に置き換えます。 、各サーバーのシャドウテーブルに24行を超えないようにしてください。timetime

そして、さらに別のアプローチは、結果を取得するプロシージャまたは関数を作成することです。ここでのアプローチは、各サーバーをループし、統計テーブルに対して個別のクエリを実行してvotes、最新の24行の平均を取得し、それらすべての結果をまとめることです。(このアプローチは、結果セットを返すことを可能にするためだけに、巨大な一時セットの並べ替えを回避するための回避策であり、必ずしも結果セットの戻りを非常に高速にするわけではありません。)

LARGEテーブルでのこのタイプのクエリのパフォーマンスの要点は、クエリで考慮される行数を制限し、大きなセットでの並べ替え操作を回避することです。このようにして、このようなクエリを実行します。


補遺

「逆インデックススキャン」操作(statsファイルソート操作なしでインデックスを使用して順序付けされた行を取得する)を取得するには、ORDERBY句の両方の式でDESCENDINGを指定する必要がありました。上記のクエリには以前にがあり、MySQLは常に、ヒントORDER BY server ASC, time DESCを指定する場合でもファイルソートを実行したいと考えていました。FORCE INDEX FOR ORDER BY (stats_ix1)

統計テーブルに少なくとも24の関連する行がある場合にのみ、サーバーの「平均投票数」を返すことが要件である場合は、少し面倒であっても、より効率的なクエリを実行できます。(ネストされたIF()関数の乱雑さのほとんどは、平均に含まれないNULL値を処理することです。NULLでない保証があるvotes場合、または行を除外する場合は、乱雑さははるかに少なくなります。ここで、votesは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

EXPLAINは、カバーリングインデックスをオンstats(server,time,votes)にすると、MySQLがファイルソート操作を回避したことを示したため、「逆インデックススキャン」を使用して行を順番に返す必要がありました。カバーするインデックスがなく、'(server、time), MySQL used the index if I included an index hint, with theFORCE INDEX FOR ORDER BY(stats_ix1)`ヒントのインデックスがないため、MySQLはファイルソートも回避しました。(しかし、私のテーブルの行数は100行未満であるため、MySQLはファイルソート操作の回避にあまり重点を置いていないと思います。)

時間、投票、およびavg_sofar式はコメント化されています(インラインビューでは)としてエイリアスされtます。これらは必要ありませんが、デバッグ用です。

クエリの現状では、平均を返すために、サーバーごとに少なくとも24行の統計が必要です。(それは許容できるかもしれません。)しかし、私は一般的に、現在の合計、これまでの合計(tot)、および現在のカウント(cnt)を返すことができると考えていました。

(をに置き換えるWHERE t.num = 24WHERE t.num <= 24、移動平均の動作を確認できます。)

統計に少なくとも24行がない場合の平均を返すには、実際には、numの最大値が24未満の行(サーバーごと)を識別する必要があります。

于 2012-06-29T23:27:04.080 に答える
0

ビル・カーウィンとそれに関する彼の投稿にクレジットされているサブセレクトのグループごとのトップアンドテクニックを使用して、このソリューションを試してくださいINNER JOIN

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
于 2012-06-23T09:39:51.827 に答える