はじめに
「分析関数の衝撃(中編)」に引き続き、CodeZineのミック氏の記事で記載されたSQLを、分析関数を使って記述していきます。また、分析関数のorder by
句でのrange
の使用例も解説します。
対象読者
- SQLの可読性を向上させたい方
- SQLのパフォーマンスを向上させたい方
この記事では、「SQLで数列を扱う」と「相関サブクエリで行と行を比較する」に記載されているSQLを、分析関数を使って記述していきますので、先に読んでおくと理解しやすいと思います。
必要な環境
本稿で扱うSQLは、Oracle 10.2.0.1.0で動作確認しました。ソースコードはDB2 V9.1でも動作確認しました。その他、
- Oracle9i以降
- DB2
- SQL Server 2005
でも応用できます。
3人なんですけど座れますか? - その1:行の折り返しを考慮しない
まずは人数分の空席を探すSQL(行の折り返しを考慮しない)についてです。「SQLで数列を扱う」では、以下のSQLが提示されています。
SELECT S1.seat AS start_seat, '~' , S2.seat AS end_seat FROM Seats S1, Seats S2 WHERE S2.seat = S1.seat + (:head_cnt -1) --始点と終点を決める AND NOT EXISTS (SELECT * FROM Seats S3 WHERE S3.seat BETWEEN S1.seat AND S2.seat AND S3.status <> '空' );
これを分析関数で書き換えてみます。まずは、テーブルのデータと、出力結果を考えます。
seat | status |
1 | 占 |
2 | 占 |
3 | 空 |
4 | 空 |
5 | 空 |
6 | 占 |
7 | 空 |
8 | 空 |
9 | 空 |
10 | 空 |
11 | 空 |
12 | 占 |
13 | 占 |
14 | 空 |
15 | 空 |
SeatStart | SeatEnd |
3 | 5 |
7 | 9 |
8 | 10 |
9 | 11 |
手続き型の言語であれば、
- seatの昇順にソート
- 各値を、配列に保存
- 配列の添字の最小値からループ
- 配列の添字を元に、3つの連続した空席を探す
といった処理を行うと思いますが、分析関数を使ったSQLでも似たような考え方を使います。
select SeatStart,SeatEnd from (select seat as SeatStart, Lead(seat,3-1) over(order by seat) as SeatEnd, count(nullif(status,'占')) over(order by seat Rows between current row and (3-1) following) as SeatCount from Seats) where SeatCount = 3;
インラインビューの中のselect
文にstatus列を追加した、SQLのイメージはこうなります。
Lead
関数では、2行後の「seat」をSeatEndとして求めてます。count
関数では、引数にnullif((status,'占'))
を指定しています。集合関数のcount
関数と同様に、分析関数のcount
関数も、nullを数えません。このことと、「status」は占または空であることを使って、nullif
関数で「status」が占ならnullを返すようにして、「status」が空の行を数えるようにしてます。
count
関数とnullif
関数の組み合わせは、「HAVING句の力」の、nullを含まない集合を探す、と同じような考え方と言えるでしょう。
count
関数のwindowing_clause
は、下記のように解釈すると分かりやすいと思います。
order by seat --seatの昇順で、 Rows between --行の範囲は、 current row --小さいほうは、現在の行から and (3-1) following --大きいほうは、2行後まで
後は、count
関数の結果であるSeatCountが3であることを、外側のselect
文のwhere
句で条件としてます。
DB2では、下記のSQLとなります。
select SeatStart,SeatEnd from (select seat as SeatStart, max(seat) over(order by seat Rows between 2 following and 2 following) as SeatEnd, count(nullif(status,'占')) over(order by seat Rows between current row and 2 following) as SeatCount from Seats) dummy where SeatCount = 3; --これでも可 select SeatStart,SeatEnd from (select seat as SeatStart, max(seat) over(order by seat Rows between 2 following and 2 following) as SeatEnd, count(nullif(status,'占')) over(order by seat Rows 2 following) as SeatCount from Seats) dummy where SeatCount = 3;