はじめに
2009年7月に正式リリースされたPostgreSQL 8.4で、分析関数(window関数)がサポートされました。本連載では、分析関数の衝撃シリーズをPostgreSQL用にアレンジした内容と、OracleやDB2の分析関数をPostgreSQL 8.4で代用する方法を扱います。
本稿では、『分析関数の衝撃3 (後編)』をPostgreSQL 8.4用にリニューアルした内容を扱います。
対象読者
- PostgreSQLでwindow関数を使ってみたい方
- 分析関数の理解を深めたい方
『SQLで数列を扱う』と 『相関サブクエリで行と行を比較する』に記載されているSQLをwindow関数を使って記述していきますので、『SQLで数列を扱う』と『相関サブクエリで行と行を比較する』を読まれてからのほうが理解しやすいと思います。
必要な環境
本稿で扱うSQLは、PostgreSQL 8.4 beta2で動作確認しました。その他、次の環境でも応用が可能です。
- Oracle
- DB2
- SQL Server
1. 「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 <> '空' );
これをwindow
関数で書き換えてみます。まずは、テーブルのデータと、出力結果を考えます。
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 |
『分析関数の衝撃3 (後編)』では、count(nullif(status,'占')) over(order by seat Rows between current row and 2 following)
を使いましたが、 PostgreSQL 8.4では文法エラーになりますので、答えは下記となります。
select SeatStart,SeatEnd from (select seat as SeatStart, Lead(seat,2) over(order by seat) as SeatEnd, case when status='空' then 1 else 0 end+ case when Lead(status) over(order by seat) ='空' then 1 else 0 end+ case when Lead(status,2) over(order by seat) ='空' then 1 else 0 end as SeatCount from Seats) a where SeatCount = 3 order by SeatStart;
インラインビューの中のselect
文にstatus列を追加したSQLのイメージは、下記となります。
Lead
関数で、2行後のseatをSeatEndとして求めてます。また、3つの検索case
式でstatus='空'
の行の数を足し算で求め、それが3であることを、外側のselect
文のwhere
句で条件としてます。
PostgreSQLは、select
文の値としてのboolean
型をサポートしますので、下記のselect
文でもよいです。
select SeatStart,SeatEnd from (select seat as SeatStart, Lead(seat,2) over(order by seat) as SeatEnd, status='空' and Lead(status='空') over(order by seat) and Lead(status='空',2) over(order by seat) as willOut from Seats) a where willOut order by SeatStart;
Lead
関数を何度も使いたくないのであれば、window
関数を使わない下記のSQLでもよいです。
select a.seat as start_seat,max(b.seat) as end_seat from Seats a,Seats b where b.seat between a.seat and a.seat+(3-1) group by a.seat having count(nullif(b.status,'占')) = 3 order by a.seat;
select seat as start_seat,seat+(3-1) as end_seat from Seats a where exists(select 1 from Seats b where b.seat between a.seat and a.seat+(3-1) having count(nullif(b.status,'占')) = 3) order by seat;