documents:proglang:sql:sql-0008
差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン | |||
| documents:proglang:sql:sql-0008 [2026/05/08 12:19] – ↷ 移動操作に合わせてリンクを書き換えました。 k896951 | documents:proglang:sql:sql-0008 [2026/05/16 11:35] (現在) – [SQLでTOP3ランキングをやりたい] k896951 | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| + | ====== 003.SQLでTOP3ランキングをやりたい ====== | ||
| + | |||
| + | 2016年8月14日\\ | ||
| + | PostgreSQL 9.5.4 で説明してますが、Oracle でも可能な筈。 | ||
| + | |||
| + | 夏休み直前に後輩Hシー氏から振られた話題を記録しておく。 | ||
| + | |||
| + | {{description> | ||
| + | |||
| + | ===== 下準備 ===== | ||
| + | |||
| + | 今回のデータは、[[documents: | ||
| + | |||
| + | <code sql> | ||
| + | $ psql -h 192.168.1.28 kouhai kouhai | ||
| + | psql (9.5.3, サーバー 9.5.4) | ||
| + | " | ||
| + | |||
| + | kouhai=> \d | ||
| + | | ||
| + | | ||
| + | ----------+------+----------+-------- | ||
| + | | ||
| + | (1 行) | ||
| + | |||
| + | kouhai=> \d 明細 | ||
| + | | ||
| + | | ||
| + | --------+-----------------------+-------- | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | kouhai=> select * from 明細; | ||
| + | | ||
| + | --------+------------+------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (17 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | |||
| + | ===== ベースとなるクエリ ===== | ||
| + | |||
| + | ^ 商品毎の売上合計 | ||
| + | |<code sql> | ||
| + | SELECT 商品 | ||
| + | , | ||
| + | FROM 明細 | ||
| + | GROUP BY 商品 | ||
| + | ORDER BY 合計 DESC | ||
| + | ; | ||
| + | </ | ||
| + | SELECT 売上年 | ||
| + | , | ||
| + | FROM 明細 | ||
| + | GROUP BY 売上年 | ||
| + | ORDER BY 合計 DESC | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | 商品 | ||
| + | ------------+------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (10 行) | ||
| + | </ | ||
| + | | ||
| + | --------+------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (4 行) | ||
| + | </ | ||
| + | |||
| + | これらのクエリがベースとなります。 | ||
| + | |||
| + | |||
| + | ===== 商品毎のランキング ===== | ||
| + | |||
| + | 商品毎の結果にランキングを付与してみます。 | ||
| + | |||
| + | ^ 売上トップのランキング | ||
| + | |<code sql> | ||
| + | SELECT 商品 | ||
| + | , | ||
| + | ,RANK() OVER ( ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 商品 | ||
| + | ; | ||
| + | </ | ||
| + | SELECT 商品 | ||
| + | , | ||
| + | ,RANK() OVER ( ORDER BY SUM(売上) ) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 商品 | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | 商品 | ||
| + | ------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (10 行) | ||
| + | </ | ||
| + | 商品 | ||
| + | ------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (10 行) | ||
| + | </ | ||
| + | |||
| + | RANK()関数はウインドウ関数で、指定された範囲内(ウインドウ内)での現在行の位置に対応した値を返します。 | ||
| + | ウインドウの範囲はOVER句で指定されますが、このクエリにはPARTITION BY句による範囲指定がないので、テーブル内のレコード全体が対象になります。 | ||
| + | |||
| + | ウインドウ範囲の行並びについては、OVER句の指定にある ORDER BY句でソートをさせて決めています。この例ではSUM(売上) の結果をソートさせています。 | ||
| + | このソートはあくまでウインドウ範囲内でのソートで、この例ではウインドウ範囲がテーブル内のレコード全体なので、結果は合計でソートされていますが、慎重を期すならベースクエリにあった“ORDER BY 合計 DESC”の記述も行った方が良いです。 | ||
| + | |||
| + | |||
| + | ===== 売上年別でランキング ===== | ||
| + | |||
| + | 次に売上年別の結果にランキングを付与してみます。 | ||
| + | ^ 売上年別のランキング | ||
| + | |<code sql> | ||
| + | SELECT 売上年 | ||
| + | , | ||
| + | ,RANK() OVER ( ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 売上年 | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | | ||
| + | --------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (4 行) | ||
| + | </ | ||
| + | |||
| + | 集計単位を商品から売上年に切り替えただけで、結果は予想通りですね。 | ||
| + | こちらも、慎重を期すならベースクエリにあった“ORDER BY 合計 DESC”の記述も行った方が良いです。 | ||
| + | |||
| + | ===== 売上年別の商品のランキング ===== | ||
| + | |||
| + | 売上年別の商品売上ランキングを出してみます。 | ||
| + | ^ 売上年別の商品売上ランキング | ||
| + | |<code sql> | ||
| + | SELECT 売上年 | ||
| + | ,商品 | ||
| + | , | ||
| + | ,RANK() OVER ( PARTITION BY 売上年 ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 売上年, | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (17 行) | ||
| + | </ | ||
| + | |||
| + | ORVER句で指定しているPARTITION BY句で、RANK()関数が算出する行位置の範囲を指定します。 | ||
| + | ここでは 売上年 を指定するので、売上年毎にSUM(売上)でソートが行われ、RANK()関数は売上年が変わると改めて行位置を1から計算して返すことになります。 | ||
| + | |||
| + | ===== TOP3を出すにはどうするのさ? ===== | ||
| + | |||
| + | もうお分かりの通り、RANK()関数の結果でフィルターすれば良いのです。 | ||
| + | |||
| + | 例えば売上年別の商品売上ランキングTOP3なら以下のようなクエリがあります。 | ||
| + | |||
| + | ^ サブクエリ版 | ||
| + | |<code sql> | ||
| + | SELECT M.* | ||
| + | FROM (SELECT 売上年 | ||
| + | ,商品 | ||
| + | , | ||
| + | ,RANK() OVER ( | ||
| + | PARTITION BY 売上年 | ||
| + | ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 売上年, | ||
| + | ) AS M | ||
| + | WHERE M.ランキング < 4 | ||
| + | ; | ||
| + | </ | ||
| + | WITH M AS ( | ||
| + | SELECT 売上年 | ||
| + | ,商品 | ||
| + | , | ||
| + | ,RANK() OVER ( | ||
| + | PARTITION BY 売上年 | ||
| + | ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | GROUP BY 売上年, | ||
| + | ) | ||
| + | SELECT * | ||
| + | FROM M | ||
| + | WHERE M.ランキング < 4 | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (11 行) | ||
| + | </ | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (11 行) | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== RANK()じゃ駄目な場合 ===== | ||
| + | |||
| + | 売上合計が同じ値になる商品だって当然出てきます。そうした場合、RANK()関数を使うと意図した出力にならないかもしれません。 | ||
| + | |||
| + | 例えば、データに漏れがあったことが判明し、‟すいか”、‟もも”の情報を追加したとしましょう。 | ||
| + | <code sql> | ||
| + | kouhai=> INSERT INTO 明細 VALUES(2006,' | ||
| + | INSERT 0 1 | ||
| + | kouhai=> INSERT INTO 明細 VALUES(2006,' | ||
| + | INSERT 0 1 | ||
| + | kouhai=> WITH M AS ( | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai(> ) | ||
| + | kouhai-> SELECT * | ||
| + | kouhai-> | ||
| + | kouhai-> | ||
| + | kouhai-> ; | ||
| + | | ||
| + | --------+----------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (12 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | |||
| + | あれ?さくらんぼう…… | ||
| + | |||
| + | 売上年が 2006 のものを見てみます。ついでなので、DENSE_RANK()関数、ROW_NUMBER()関数、についても確認してみます。 | ||
| + | |||
| + | ^ RANK()関数 | ||
| + | |<code sql> | ||
| + | SELECT | ||
| + | | ||
| + | ,商品 | ||
| + | , | ||
| + | ,RANK() OVER ( | ||
| + | | ||
| + | ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | WHERE 売上年=' | ||
| + | GROUP BY 売上年, | ||
| + | ; | ||
| + | </ | ||
| + | SELECT | ||
| + | | ||
| + | ,商品 | ||
| + | , | ||
| + | , | ||
| + | | ||
| + | ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | WHERE 売上年=' | ||
| + | GROUP BY 売上年, | ||
| + | ; | ||
| + | </ | ||
| + | SELECT | ||
| + | | ||
| + | ,商品 | ||
| + | , | ||
| + | , | ||
| + | | ||
| + | ORDER BY SUM(売上) DESC) AS ランキング | ||
| + | FROM 明細 | ||
| + | WHERE 売上年=' | ||
| + | GROUP BY 売上年, | ||
| + | ; | ||
| + | </ | ||
| + | |<code sql> | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (7 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (7 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (7 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | |||
| + | すいか、まんごー、もも、の売上合計が同一値のため同順タイになり、 | ||
| + | * RANK()関数では同順タイの部分の幅を飛ばしてナンバリングするので、さくらんぼうが5位になってしまいました。 | ||
| + | * DENSE_RANK()関数を使った場合は同順タイがあっても飛ばさないでナンバリングするため、さくらんぼうは3位となります。 | ||
| + | * ROW_NUMBER()関数はレコードの並びにそのまま連番を振るので、さくらんぼうは5位になります。\\ また、商品名によるソートが行われないので、ROW_NUMBER()関数の時は、すいか、まんごー、もも、の順位が変わる可能性が出てきます。 | ||
| + | |||
| + | どういった結果を望むのかで関数をチョイスする必要があります。 | ||
| + | |||
| + | ===== 追記:ソート順が変わる話 ===== | ||
| + | |||
| + | ROW_NUMBER()の説明で「すいか、まんごー、もも、の順位が変わる可能性」のところがよう分からん、と言われたので。 | ||
| + | |||
| + | 先のSQL実行結果の並びをもう一度確認します。 | ||
| + | <code sql> | ||
| + | kouhai=> SELECT | ||
| + | kouhai-> | ||
| + | kouhai-> ,商品 | ||
| + | kouhai-> , | ||
| + | kouhai-> , | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai-> | ||
| + | kouhai-> | ||
| + | kouhai-> | ||
| + | kouhai-> ; | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (7 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | |||
| + | 次に、売上年でのフィルターを外して実行します。 | ||
| + | |||
| + | <code sql> | ||
| + | kouhai=> SELECT | ||
| + | kouhai-> | ||
| + | kouhai-> ,商品 | ||
| + | kouhai-> , | ||
| + | kouhai-> , | ||
| + | kouhai(> | ||
| + | kouhai(> | ||
| + | kouhai-> | ||
| + | kouhai-> | ||
| + | kouhai-> ; | ||
| + | | ||
| + | --------+------------+------+------------ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | (19 行) | ||
| + | |||
| + | kouhai=> | ||
| + | </ | ||
| + | |||
| + | 表示された 売上年=2006 の並びに差異があることがわかります。 | ||
| + | ^ フィルタあり | ||
| + | |<code sql> | ||
| + | | ||
| + | | ||
| + | | ||
| + | </ | ||
| + | | ||
| + | | ||
| + | | ||
| + | </ | ||
| + | |||
| + | これは表示順を明示していないので、実行するSQLによって表示順は変わる、という例です。\\ | ||
| + | 表示順は変わらないかもしれませんし、暗黙でソートがかかっているデータベース製品もあるかもしれません。 | ||
| + | |||
| + | {{tag> SQL PostgreSQL Oracle rank dense_rank database 技術資料}} | ||
| + | |||
documents/proglang/sql/sql-0008.txt · 最終更新: by k896951
