11

私の現在のアプリケーションは、各ユーザーのすべてのレコードに基づいてポイント平均を計算します。

SELECT `user_id`, AVG(`points`) AS pts 
FROM `players` 
WHERE `points` != 0 
GROUP BY `user_id`

ビジネス要件が変更されたため、各ユーザーの最新の 30 件のレコードに基づいて平均を計算する必要があります。

関連するテーブルの構造は次のとおりです。

テーブル: プレイヤー; 列: player_id、user_id、match_id、ポイント

テーブル: ユーザー; 列: user_id

次のクエリは機能しませんが、実装しようとしているロジックを示しています。

SELECT @user_id := u.`id`, (
    -- Calculate the average for last 30 records
    SELECT AVG(plr.`points`) 
    FROM (
        -- Select the last 30 records for evaluation
        SELECT p.`points` 
        FROM `players` AS p 
        WHERE p.`user_id`=@user_id 
        ORDER BY `match_id` DESC 
        LIMIT 30
    ) AS plr
) AS avg_points 
FROM `users` AS u

各ユーザーの最新の 30 件のレコードに基づいて平均を計算するかなり効率的な方法はありますか?

4

5 に答える 5

11

車輪を再発明して、バグのある、最適ではないコードを作成する危険を冒す理由はありません。あなたの問題は、一般的なグループごとの制限の問題の簡単な拡張です。この問題を解決するためのテスト済みの最適化されたソリューションが既にあります。このリソースから、次の 2 つのソリューションから選択することをお勧めします。これらのクエリは、プレーヤーごとに最新の 30 レコードを生成します (テーブル用に書き直します)。

select user_id, points
from players
where (
   select count(*) from players as p
   where p.user_id = players.user_id and p.player_id >= players.player_id
) <= 30;

(私があなたの構造を理解していることを確認するために:player_idはプレーヤー テーブルの一意のキーであり、1 人のユーザーがこのテーブルに複数のプレーヤーとして存在できると思います。)

2 番目にテストおよび最適化されたソリューションは、MySQL 変数を使用することです。

set @num := 0, @user_id := -1;

select user_id, points,
      @num := if(@user_id = user_id, @num + 1, 1) as row_number,
      @user_id := user_id as dummy
from players force index(user_id) /* optimization */
group by user_id, points, player_id /* player_id should be necessary here */
having row_number <= 30;

最初のクエリはそれほど最適ではありませんが (2 次)、2 番目のクエリは最適です (ワンパス) が、MySQL でのみ機能します。選択はあなた次第です。2 番目の手法を使用する場合は、注意して、キーとデータベースの設定で適切にテストしてください。彼らは、状況によっては機能しなくなる可能性があることを示唆しています。

最後のクエリは簡単です。

select user_id, avg(points)
from ( /* here goes one of the above solutions; 
          the "set" commands should go before this big query */ ) as t
group by user_id

(points != 0)私はあなたの要件をよく理解していないため(あなたはそれを説明していません)、最初のクエリにあなたが持っている条件を組み込んでいないことに注意してください.

于 2013-06-08T17:12:14.823 に答える
0
SELECT 
u.`id`, 
(SELECT AVG(p.`points`) FROM FROM `players` AS p WHERE p.`user_id`=u.`id` 
ORDER BY p.`user_id` DESC LIMIT 30) AS AVG
FROM `users` AS u Group by u.`id`

また、これも試してみてください...

于 2013-06-08T06:45:02.993 に答える
0

これはうまくいくはずです:

SELECT p1.user_id, avg(points) as pts
  FROM players p1, (
    SELECT u.user_id, (
         SELECT match_id
           FROM players p2
          WHERE p2.user_id = u.user_id
          ORDER BY match_id DESC
          LIMIT 29, 1 ) mid
      FROM users u
    HAVING mid IS NOT NULL) m
 WHERE p1.user_id = m.user_id
   AND p1.match_id >= m.mid
 GROUP BY p1.user_id

 UNION ALL

SELECT user_id, avg(points) AS pts 
  FROM players
 GROUP BY user_id
HAVING count(*) < 30

の後の部分UNION ALLは、30 レコード未満のユーザーを含める必要がある場合にのみ必要です。

于 2013-06-07T23:06:05.030 に答える