SQL Serverには、データベースへの呼び出しを繰り返すことなく、一連の行にわたって計算を実行するのに役立つ多数のウィンドウ関数が用意されています。標準の集計関数とは異なり、ウィンドウ関数は行を単一の出力行にグループ化せず、行ごとに単一の集計値を返し、それらの行の個別のIDを保持します。ここでのウィンドウの用語は、MicrosoftWindowsオペレーティングシステムとは関係ありません。関数が処理する一連の行を表します。
最も有用なタイプのウィンドウ関数の1つは、特定のフィールド値をランク付けし、各行のランクに従ってそれらを分類するために使用されるランク付けウィンドウ関数です。これにより、参加する行ごとに1つの集計値が得られます。 SQLServerでサポートされているランキングウィンドウ関数は4つあります。 ROW_NUMBER()、RANK()、DENSE_RANK()、およびNTILE()。これらの関数はすべて、提供された行ウィンドウのROWIDを独自の方法で計算するために使用されます。
4つのランキングウィンドウ関数は、クエリ結果セット内のユーザー指定の行セットを定義するOVER()句を使用します。 OVER()句を定義することにより、パーティションを定義するための列またはコンマ区切りの列を提供することにより、ウィンドウ関数が処理する行のセットを決定するPARTITIONBY句を含めることもできます。さらに、ORDER BY句を含めることができます。これは、関数が処理中に行を通過するパーティション内の並べ替え基準を定義します。
この記事では、ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE()の4つのランキングウィンドウ関数の実際の使用方法と、それらの違いについて説明します。
デモを提供するために、新しい単純なテーブルを作成し、以下のT-SQLスクリプトを使用してテーブルにいくつかのレコードを挿入します。
CREATE TABLE StudentScore
(
Student_ID INT PRIMARY KEY,
Student_Name NVARCHAR (50),
Student_Score INT
)
GO
INSERT INTO StudentScore VALUES (1,'Ali', 978)
INSERT INTO StudentScore VALUES (2,'Zaid', 770)
INSERT INTO StudentScore VALUES (3,'Mohd', 1140)
INSERT INTO StudentScore VALUES (4,'Jack', 770)
INSERT INTO StudentScore VALUES (5,'John', 1240)
INSERT INTO StudentScore VALUES (6,'Mike', 1140)
INSERT INTO StudentScore VALUES (7,'Goerge', 885)
次のSELECTステートメントを使用して、データが正常に挿入されたことを確認できます。
SELECT * FROM StudentScore ORDER BY Student_Score
ソートされた結果を適用すると、結果セットは次のようになります。
ROW_NUMBER()
ROW_NUMBER()ランキングウィンドウ関数は、指定されたウィンドウのパーティション内の各行に一意の連続番号を返します。各パーティションの最初の行は1から始まり、各パーティションのランキング結果で番号を繰り返したりスキップしたりすることはありません。行セット内に重複する値がある場合、ランキングID番号は任意に割り当てられます。 PARTITION BY句を指定すると、パーティションごとにランキング行番号がリセットされます。以前に作成したテーブルで、以下のクエリは、ROW_NUMBERランキングウィンドウ関数を使用して、各学生のスコアに従ってStudentScoreテーブルの行をランク付けする方法を示しています。
SELECT *, ROW_NUMBER() OVER( ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
以下の結果セットから、ROW_NUMBERウィンドウ関数は、重複やギャップなしで番号1から始まるStudent_Scoreランキングを反映する各行の一意の番号を生成することにより、各行のStudent_Score列値に従ってテーブル行をランク付けすることが明らかです。すべての行を1つのパーティションとして処理します。重複するスコアがランダムに異なるランクに割り当てられていることもわかります。
以下のT-SQLクエリに示すように、PARTITION BY句を含めて前のクエリを変更し、複数のパーティションを作成した場合:
SELECT *, ROW_NUMBER() OVER(PARTITION BY Student_Score ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
結果は、ROW_NUMBERウィンドウ関数が各行のStudent_Score列の値に従ってテーブルの行をランク付けすることを示していますが、1つのパーティションと同じStudent_Score値を持つ行を処理します。同じパーティション内で重複やギャップのない番号1から始まり、別のStudent_Score値に移動するとランク番号がリセットされる、Student_Scoreランキングを反映する一意の番号が各行に生成されることがわかります。
たとえば、スコア770の生徒は、ランク番号を割り当てることにより、そのスコア内でランク付けされます。ただし、スコア885の生徒に移動すると、以下に示すように、ランクの開始番号がリセットされ、1から再開されます。
RANK()
RANK()ランキングウィンドウ関数は、指定された列値に従って、パーティション内の個別の行ごとに一意のランク番号を返します。各パーティションの最初の行は1から始まり、重複する値は同じランクで、ランク間にギャップが残ります。;このギャップは、重複した値の後にシーケンスに表示されます。つまり、RANK()ランキングウィンドウ関数は、同じランクIDでランク付けされ、その後にギャップが生成される、値が等しい行を除いて、ROW_NUMBER()関数と同じように動作します。前のランキングクエリを変更してRANK()ランキング関数を使用する場合:
SELECT *, RANK () OVER( ORDER BY Student_Score) AS RankRank
FROM StudentScore
結果から、RANKウィンドウ関数が各行のStudent_Score列の値に従ってテーブルの行をランク付けし、ランク付け値が番号1から始まるStudent_Scoreを反映し、同じStudent_Scoreを持つ行をランク付けすることがわかります。同じランク値。また、Student_Scoreが770に等しい2つの行が同じ値でランク付けされ、2番目にランク付けされた行の後に2番目の欠落であるギャップが残っていることがわかります。以下に示すように、Student_Scoreが1140に等しく、同じ値でランク付けされている行でも同じことが起こり、2番目の行の後に6番目の欠落しているギャップが残ります。
以下のT-SQLクエリに示すように、PARTITION BY句を含めて前のクエリを変更し、複数のパーティションを作成します。
SELECT *, RANK() OVER(PARTITION BY Student_Score ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
ランク付けは各パーティションのStudent_Score値に従って行われ、データはStudent_Score値に従ってパーティション化されるため、ランク付けの結果には意味がありません。また、各パーティションには同じStudent_Score値の行があるため、同じパーティション内の同じStudent_Score値の行は、1に等しい値でランク付けされます。したがって、2番目のパーティションに移動すると、ランクは次のようになります。以下に示すように、すべてのランキング値が1に等しい、番号1から再開してリセットします。
DENSE_RANK()
DENSE_RANK()ランキングウィンドウ関数は、RANK()関数に似ており、指定された列値に従って、パーティション内の個別の行ごとに一意のランク番号を生成します。各パーティションの最初の行は1から始まり、次の行をランク付けします。ランクをスキップせず、ランク間にギャップがないことを除いて、同じランク番号の等しい値。
DENSE_RANK()ランキング関数を使用するように前のランキングクエリを書き直した場合:
繰り返しますが、以下のT-SQLクエリに示すように、PARTITION BY句を含めて複数のパーティションを持つように、前のクエリを変更します。
SELECT *, DENSE_RANK() OVER(PARTITION BY Student_Score ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
以下に示すように、重複する値を同じランキング値に割り当て、新しいパーティションを処理するときにランク開始IDをリセットするため、ランキング値には意味がありません。すべての行が値1でランク付けされます。
>NTILE(N)
NTILE(N)ランキング・ウィンドウ関数は、行セット内の行を指定された数のグループに分散するために使用され、行セット内の各行に、この行が属するグループを示す番号1から始まる一意のグループ番号を提供します。 to、ここでNは正の数であり、行セットを分散するために必要なグループの数を定義します。
つまり、テーブルの特定のデータ行を特定の列の値に基づいて3つのグループに分割する必要がある場合、NTILE(3)ランキングウィンドウ関数を使用すると、これを簡単に実現できます。
各グループの行数は、行数を必要なグループ数に分割することで計算できます。以下のT-SQLクエリのように、NTILE(4)ランキングウィンドウ関数を使用して7つのテーブル行を4つのグループにランク付けするように前のランキングクエリを変更すると、次のようになります。
SELECT *, NTILE(4) OVER( ORDER BY Student_Score) AS NTILERank
FROM StudentScore
行数は、各グループに(7/4 =1.75)行である必要があります。以下の結果セットに示すように、SQL ServerエンジンはNTILE()関数を使用して、最初の3つのグループに2行、最後のグループに1行を割り当て、すべての行をグループに含めます。
>以下のT-SQLクエリに示すように、PARTITION BY句を含めて前のクエリを変更し、複数のパーティションを作成します。
SELECT *, NTILE(4) OVER(PARTITION BY Student_Score ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
行は、各パーティションで4つのグループに分散されます。たとえば、Student_Scoreが770に等しい最初の2行は同じパーティションにあり、以下の結果セットに示すように、それぞれを一意の番号でランク付けするグループ内に分散されます。
すべてをまとめる
より明確な比較シナリオを作成するために、前のテーブルを切り捨て、生徒のクラスである別の分類基準を追加し、最後に以下のT-SQLスクリプトを使用して新しい7行を挿入します。
TRUNCATE TABLE StudentScore
GO
ALTER TABLE StudentScore ADD CLASS CHAR(1)
GO
INSERT INTO StudentScore VALUES (1,'Ali', 978,'A')
INSERT INTO StudentScore VALUES (2,'Zaid', 770,'B')
INSERT INTO StudentScore VALUES (3,'Mohd', 1140,'A')
INSERT INTO StudentScore VALUES (4,'Jack', 879,'B')
INSERT INTO StudentScore VALUES (5,'John', 1240,'C')
INSERT INTO StudentScore VALUES (6,'Mike', 1100,'B')
INSERT INTO StudentScore VALUES (7,'Goerge', 885,'C')
その後、各生徒のスコアに応じて7行をランク付けし、クラスに応じて生徒を分割します。つまり、各パーティションには1つのクラスが含まれ、学生の各クラスは、以下のT-SQLスクリプトに示すように、前述の4つのランキングウィンドウ関数を使用して、同じクラス内のスコアに従ってランク付けされます。
SELECT *, ROW_NUMBER() OVER(PARTITION BY CLASS ORDER BY Student_Score) AS RowNumberRank,
RANK () OVER(PARTITION BY CLASS ORDER BY Student_Score) AS RankRank,
DENSE_RANK () OVER(PARTITION BY CLASS ORDER BY Student_Score) AS DenseRankRank,
NTILE(7) OVER(PARTITION BY CLASS ORDER BY Student_Score) AS NTILERank
FROM StudentScore
GO
重複する値がないため、以下の結果セットに示すように、4つのランキングウィンドウ関数が同じように機能し、同じ結果を返します。
次のINSERTステートメントを使用して、別の生徒がスコアとともにクラスAに含まれている場合、同じクラスの別の生徒がすでに持っているもの:
INSERT INTO StudentScore VALUES (8,'Faisal', 978,'A')
ROW_NUMBER()およびNTILE()ランキングウィンドウ関数については何も変更されません。 RANK関数とDENSE_RANK()関数は、同じスコアの学生に同じランクを割り当てます。RANK関数を使用すると、重複ランクの後のランクにギャップがあり、DENSE_RANK(を使用すると、重複ランクの後のランクにギャップはありません。 )、以下の結果に示すように:
実用的なシナリオ
ランキングウィンドウ関数は、SQLServer開発者によって広く使用されています。ランキング関数の使用に関する一般的なシナリオの1つで、特定の行をフェッチして他の行をスキップする場合、CTE内でROW_NUMBER(、)ランキングウィンドウ関数を使用します。これは、以下のT-SQLスクリプトのように、ランクが2と5をスキップし、他をスキップします:
WITH ClassRanks AS
(
SELECT *, ROW_NUMBER() OVER( ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
)
SELECT Student_Name , Student_Score
FROM ClassRanks
WHERE RowNumberRank >= 2 and RowNumberRank <=5
ORDER BY RowNumberRank
結果は、ランクが2から5の学生のみが返されることを示しています。
SQL Server 2012以降、新しい便利なコマンド OFFSET FETCH 以下のT-SQLスクリプトを使用して、特定のレコードをフェッチし、他のレコードをスキップすることで、同じ前のタスクを実行するために使用できるが導入されました。
WITH ClassRanks AS
(
SELECT *, ROW_NUMBER() OVER( ORDER BY Student_Score) AS RowNumberRank
FROM StudentScore
)
SELECT Student_Name , Student_Score
FROM ClassRanks
ORDER BY
RowNumberRank OFFSET 1 ROWS FETCH NEXT 4 ROWS ONLY;
以下に示すのと同じ以前の結果を取得します:
結論
SQL Serverには、特定の列値に従って提供された行セットをランク付けするのに役立つ4つのランク付けウィンドウ関数が用意されています。これらの関数は、ROW_NUMBER()、RANK()、DENSE_RANK()、およびNTILE()です。これらのランク付け関数はすべて、独自の方法でランク付けタスクを実行し、行に重複する値がない場合に同じ結果を返します。行セット内に重複する値がある場合、RANK関数は、同じ値を持つすべての行に同じランキングIDを割り当て、重複した後のランク間にギャップを残します。 DENSE_RANK関数は、同じ値を持つすべての行に同じランキングIDも割り当てますが、重複後にランク間にギャップを残すことはありません。この記事では、ランキングウィンドウ関数を実際に理解するのに役立つ可能性のあるすべてのケースをカバーするために、さまざまなシナリオを実行します。
参照:
- ROW_NUMBER(Transact-SQL)
- RANK(Transact-SQL)
- DENSE_RANK(Transact-SQL)
- NTILE(Transact-SQL)
- OFFSET FETCH句(SQL Server Compact)