select*from goats;
+----+--------+------------+-------+-------+
| id | weight | birthday | color | name |+----+--------+------------+-------+-------+
|1|63|2016-03-02| W | Alice ||2|79|2012-06-25| W | Bob ||3|76|2020-02-01| W | Carl ||4|75|2022-11-27| W | Dan ||5|70|2014-08-29| B | Elie ||6|69|2013-06-07| B | Fai ||7|67|2013-01-16| B | Gabi ||8|63|2014-05-14| B | Helen |+----+--------+------------+-------+-------+
SELECT id,
weight,
color,
CASEWHEN (color ='W') AND (weight BETWEEN65AND75) THEN'OK'WHEN (color ='B') AND (weight BETWEEN68AND72) THEN'OK'ELSE'NG'ENDas status
FROM goats;
+----+--------+-------+--------+
| id | weight | color | status |+----+--------+-------+--------+
|1|63| W | NG ||2|79| W | NG ||3|76| W | NG ||4|75| W | OK ||5|70| B | OK ||6|69| B | OK ||7|67| B | NG ||8|63| B | NG |+----+--------+-------+--------+
この case 式の値によってグループ化することも可能です. 例えば OK/NG の個体数を調べるには以下のようにします.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECTCASEWHEN (color ='W') AND (weight BETWEEN65AND75) THEN'OK'WHEN (color ='B') AND (weight BETWEEN68AND72) THEN'OK'ELSE'NG'ENDas status,
count(*)
FROM goats
GROUPBY status;
+--------+----------+
| status |count(*) |+--------+----------+
| NG |5|| OK |3|+--------+----------+
case式というのがポイントで, 式を書けるところにはどこにでも書けます. 例えば SELECT 句以外にも, UPDATE で SET col = CASE WHEN ... なども可です.
case 式は汎用性, 利便性の高さから本書では 1 章で紹介され, その後随所に登場します.
まず異なる行同士を比較する例を考えます. 先程のヤギのテーブルで, 1 つ前の id のヤギより若いヤギを取り出します. このテーブルだと, id が 3, 4, 8 の行が求める結果です.
1
2
3
4
5
6
7
8
9
10
11
12
13
select*from goats;
+----+--------+------------+-------+-------+
| id | weight | birthday | color | name |+----+--------+------------+-------+-------+
|1|63|2016-03-02| W | Alice ||2|79|2012-06-25| W | Bob ||3|76|2020-02-01| W | Carl ||4|75|2022-11-27| W | Dan ||5|70|2014-08-29| B | Elie ||6|69|2013-06-07| B | Fai ||7|67|2013-01-16| B | Gabi ||8|63|2014-05-14| B | Helen |+----+--------+------------+-------+-------+
SELECT id,
birthday,
MAX(birthday) OVER(
ORDERBY id
ROWSBETWEEN1 PRECEDING AND1 PRECEDING) as prev_birthday
FROM goats;
+----+------------+---------------+
| id | birthday | prev_birthday |+----+------------+---------------+
|1|2016-03-02|NULL||2|2012-06-25|2016-03-02||3|2020-02-01|2012-06-25||4|2022-11-27|2020-02-01||5|2014-08-29|2022-11-27||6|2013-06-07|2014-08-29||7|2013-01-16|2013-06-07||8|2014-05-14|2013-01-16|+----+------------+---------------+
上記のクエリの以下の部分がウィンドウ関数です.
1
2
3
MAX(birthday) OVER(
ORDERBY id
ROWSBETWEEN1 PRECEDING AND1 PRECEDING)
処理は以下のような段階を踏みます.
id でソート
1 つ前の行から 1 つ前の行までの行 (=1 つ前の行だけ) を取ってくる
取ってきた範囲の行から, birthday の最大値を求める
今回は直前の 1 行のみを取り出せば良いので, 2 で指定する範囲は, 要は直前の 1 行のみです. 1 行しかないので MAX しなくてもよいはずですが, 構文の制限上集約しなければならないので MAX を使っています. MIN でも構いません.
さて, ここまで来ればあとは簡単です. 上記のテーブルを一時テーブルに置いて, 比較をします.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT id,
birthday,
prev_birthday
FROM (SELECT id,
birthday,
MAX(birthday) OVER(
ORDERBY id
ROWSBETWEEN1 PRECEDING AND1 PRECEDING) as prev_birthday
FROM goats) as TMP
WHERE birthday > prev_birthday;
SELECT id,
birthday,
MAX(birthday) OVER(
ORDERBY id
ROWSBETWEEN1 PRECEDING AND1 PRECEDING) as prev_birthday
FROM goats
WHERE birthday > prev_birthday; -- WHERE の中で prev_birthday は参照できない.
なぜ SELECT で付けた名前を WHERE の中で参照できないのかというと, SQL は
FROM
WHERE
SELECT
の順番で処理されるからです. SELECT は見た目上は一番最初に来ますが, 処理順序は一番最後なのです. という説明が本書に書いてあり, なるほどと思いました. とはいえ, 少し気を利かせて SELECT の中で定義された名前を先に見てくれてもいいんじゃないかとは思いますが (ちなみに, case 式の例で紹介した GROUP BY status という書き方は例外で, 一部 DBMS でのみ許されます).
select*from goats;
+----+--------+------------+-------+-------+
| id | weight | birthday | color | name |+----+--------+------------+-------+-------+
|1|63|2016-03-02| W | Alice ||2|79|2012-06-25| W | Bob ||3|76|2020-02-01| W | Carl ||4|75|2022-11-27| W | Dan ||5|70|2014-08-29| B | Elie ||6|69|2013-06-07| B | Fai ||7|67|2013-01-16| B | Gabi ||8|63|2014-05-14| B | Helen |+----+--------+------------+-------+-------+
SELECT id,
color,
weight,
AVG(weight) OVER(PARTITION BY color) as average
FROM goats;
+----+-------+--------+---------+
| id | color | weight | average |+----+-------+--------+---------+
|5| B |70|67.2500||6| B |69|67.2500||7| B |67|67.2500||8| B |63|67.2500||1| W |63|73.2500||2| W |79|73.2500||3| W |76|73.2500||4| W |75|73.2500|+----+-------+--------+---------+
SELECT id,
weight,
color,
CASEWHEN weight <AVG(weight) OVER(PARTITION BY color) THEN'Light'ELSE'Heavy'ENDas compare
FROM goats;
+----+--------+-------+---------+
| id | weight | color | compare |+----+--------+-------+---------+
|5|70| B | Heavy ||6|69| B | Heavy ||7|67| B | Light ||8|63| B | Light ||1|63| W | Light ||2|79| W | Heavy ||3|76| W | Heavy ||4|75| W | Heavy |+----+--------+-------+---------+
条件を「自分より体重が大きいヤギは存在しない」というふうに言い換えることで, 条件を NOT EXISTS で表現できます.
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT*FROM goats g1
WHERENOTEXISTS (
SELECT*FROM goats g2
WHERE g2.color = g1.color AND g2.weight > g1.weight);
+----+--------+------------+-------+------+
| id | weight | birthday | color | name |+----+--------+------------+-------+------+
|2|79|2012-06-25| W | Bob ||5|70|2014-08-29| B | Elie |+----+--------+------------+-------+------+
SELECT*FROM (SELECT*,
MAX(weight) OVER(PARTITION BY color) as max_weight
FROM goats) as TMP
WHERE weight = max_weight;
+----+--------+------------+-------+------+------------+
| id | weight | birthday | color | name | max_weight |+----+--------+------------+-------+------+------------+
|5|70|2014-08-29| B | Elie |70||2|79|2012-06-25| W | Bob |79|+----+--------+------------+-------+------+------------+
-- A { 1, 2, 3, 4 }, B { 3, 4, 5, 6 }
CREATE TABLE A (value int);
INSERT INTO A values(1);
INSERT INTO A values(2);
INSERT INTO A values(3);
INSERT INTO A values(4);
CREATE TABLE B (value int);
INSERT INTO B values(3);
INSERT INTO B values(4);
INSERT INTO B values(5);
INSERT INTO B values(6);
それぞれ演算が用意されているので, それを使うだけです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- A + B { 1, 2, 3, 4, 5, 6 }
SELECT*FROM A
UNIONSELECT*FROM B;
-- A - B { 1, 2 }
SELECT*FROM A
EXCEPTSELECT*FROM B;
-- A ∧ B { 3, 4 }
SELECT*FROM A
INTERSECTSELECT*FROM B;