PostgreSQLのSQLの特徴
第3章では、
そこでまずは、
そのあとの節では、
PostgreSQLは標準SQLを重視する
PostgreSQLは、
結果として、
標準SQLに準拠している機能
標準SQLに準拠している機能の一覧は、
標準SQLに準拠していない機能
残念ながら、
PostgreSQLは今後も標準SQLに準拠していくための機能追加を進めていく予定です。まだサポートされていない標準SQLの一覧を眺めると、
PostgreSQL独自のSQLと機能
PostgreSQLは標準SQLを重視していますが、
UPSERT──追加と更新を同時に行う
データの挿入時に該当のキーにデータがなければINSERT、
これは、INSERT ... ON DUPLICATE KEY UPDATE
の構文でサポートしています。PostgreSQLは、INSERT ... ON CONFLICT DO UPDATE
の構文をサポートしました。
PostgreSQLのUPSERTは、
待望の機能としてリリースされたUPSERTは、
独自の関数
RDBMSで独自に提供されている機能の筆頭と言えば関数で、
Window関数──集計の味方
標準SQLにはWindow関数が定義されており、
Window関数の役割
まず、
ここでは、
図1のSQLを使って売上表を作成します。図1では、\x
を利用して、
テーブルの用意 demo=# CREATE TABLE public."売上" (id serial NOT NULL, "売上日時" timestamp NOT NULL DEFAULT now(), "商品名" character varying(64) NOT NULL, "価格" numeric NOT NULL DEFAULT 0, "ジャンル" text NOT NULL); バルクINSERTでテストデータを作る demo=# INSERT INTO "売上" ("売上日時","商品名","価格 ","ジャンル") VALUES (now()-((random()*10000)::int%365)*interval '1 day', 'WEB+DB PRESS Vol.107',1480,'紙'), (now()-((random()*10000)::int%365)*interval '1 day', 'WEB+DB PRESS Vol.106',1480,'電子'), (now()-((random()*10000)::int%365)*interval '1 day', 'WEB+DB PRESS Vol.105',1480,'紙'), (now()-((random()*10000)::int%365)*interval '1 day', 'WEB+DB PRESS Vol.104',1480,'電子'); demo=# \x Expanded display is on. データの中身の確認 demo=# SELECT * FROM "売上" WHERE "商品名" = 'WEB+DB PRESS Vol.105' LIMIT 1; -[ RECORD 1 ]------------------------------- id | 22251 売上日時 | 2018-09-08 07:42:00.067831 商品名 | WEB+DB PRESS Vol.105 価格 | 1480 ジャンル | 紙
この売上表の8月から10月の各月における、
Window関数を利用しない場合
まずは、
8~10月の各月の日別の売上件数の上位3件を表示
demo=# SELECT "売上日", "件数" FROM
(SELECT "売上日時"::date AS "売上日",
COUNT(*) AS "件数" FROM "売上"
WHERE "売上日時" >= '2018/10/01 00:00:00'
AND "売上日時" <= '2018/10/31 23:59:59'
GROUP BY "売上日"
ORDER BY "件数" DESC,売上日 LIMIT 3) AS oct
UNION ALL SELECT * FROM
(SELECT "売上日時"::date AS "売上日",
COUNT(*) AS "件数" FROM "売上"
WHERE "売上日時" >= '2018/09/01 00:00:00'
AND "売上日時" <= '2018/09/30 23:59:59'
GROUP BY "売上日"
ORDER BY "件数" DESC,売上日 LIMIT 3) AS Sep
UNION ALL SELECT * FROM
(SELECT "売上日時"::date AS "売上日",
COUNT(*) AS "件数" FROM "売上"
WHERE "売上日時" >= '2018/08/01 00:00:00'
AND "売上日時" <= '2018/08/31 23:59:59'
GROUP BY "売上日"
ORDER BY "件数" DESC,売上日 LIMIT 3) AS Aug;
売上日 | 件数
------------+------
2018-10-13 | 101
2018-10-01 | 99
2018-10-05 | 97
2018-09-04 | 104
2018-09-10 | 100
2018-09-26 | 99
2018-08-25 | 108
2018-08-19 | 102
2018-08-24 | 100
(9 rows)
Window関数を利用した場合
Window関数を利用すると、
Window関数を利用した例が図3です。Window関数は、OVER()
で対象を指定します。PARTITION BY句は、
8~10月の各月の日別の売上件数の上位3件を表示
demo=# SELECT "売上日", "件数", "順位"
FROM (
SELECT *, dense_rank() OVER (PARTITION BY "売上月"
ORDER BY "件数" DESC, 売上日) AS "順位"
FROM (
SELECT
extract(month from "売上日時") AS "売上月",
"売上日時"::date AS "売上日",
COUNT(*) AS "件数"
FROM "売上"
WHERE "売上日時" >= '2018/08/01 00:00:00'
AND "売上日時" <= '2018/10/31 23:59:59'
GROUP BY "売上日", "売上月"
) AS "集計"
) AS "集計ランキング"
WHERE "順位" <= 3
ORDER BY "売上月" DESC, "順位";
売上日 | 件数 | 順位
------------+------+------
2018-10-13 | 101 | 1
2018-10-01 | 99 | 2
2018-10-05 | 97 | 3
2018-09-04 | 104 | 1
2018-09-10 | 100 | 2
2018-09-26 | 99 | 3
2018-08-25 | 108 | 1
2018-08-19 | 102 | 2
2018-08-24 | 100 | 3
(9 rows)
これが、
主なWindow関数を表1にまとめました。使いこなしてデータ分析や集計を効率化していきましょう。
関数名 | 説明 |
---|---|
row_ | 行番号を出力する |
rank() | 同率の番号を飛ばしてランキングを出力する |
dense_ | 同率の番号を飛ばさずにランキングを出力する |
percent_ | ランキングを%で出力する。計算方法は(ランキング - 1) / (全行数 - 1) |
cume_ | percent_ |
ntile(N) | ランキングを1.. |
lag(value, offset, default) | ウィンドウをソートした状態での前の行の値を出力する |
lead(value, offset, default) | ウィンドウをソートした状態でのあとの行の値を出力する |
first_ | 最初の値を出力する |
last_ | 最後の値を出力する |
nth_ | 1から数えたN番目の値を出力する |
ウィンドウフレーム──Window関数の対象を決める
Window関数を使うときにPARTITION BYなどで作ったウィンドウに対し、
ウィンドウフレームは、
ウィンドウフレームのモードを指定したら、
実際の利用例は図4とおりです。図4はWITH句を使って、
demo=# WITH t(id, value) AS (VALUES (1, 1), (2, 1), (3, 3), (4, 5), (5, 5), (6, 5), (7, 6)) SELECT id, value, array_agg(id) OVER ROWS as row_id, array_agg(value) OVER ROWS as row_value, array_agg(id) OVER RANGE as renge_id, array_agg(value) OVER RANGE as renge_value, array_agg(id) OVER GROUPS as groups_id, array_agg(value) OVER GROUPS as groups_value FROM t WINDOW obj AS (ORDER BY value), ROWS AS (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING), RANGE AS (obj RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), GROUPS AS (obj GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING); id | value | row_id | row_value | renge_id | renge_value | groups_id | groups_value ----+-------+---------+-----------+-----------+-------------+---------------+--------------- 1 | 1 | {1,2} | {1,1} | {1,2} | {1,1} | {1,2,3} | {1,1,3} 2 | 1 | {1,2,3} | {1,1,3} | {1,2} | {1,1} | {1,2,3} | {1,1,3} 3 | 3 | {2,3,4} | {1,3,5} | {3} | {3} | {1,2,3,4,5,6} | {1,1,3,5,5,5} 4 | 5 | {3,4,5} | {3,5,5} | {4,5,6,7} | {5,5,5,6} | {3,4,5,6,7} | {3,5,5,5,6} 5 | 5 | {4,5,6} | {5,5,5} | {4,5,6,7} | {5,5,5,6} | {3,4,5,6,7} | {3,5,5,5,6} 6 | 5 | {5,6,7} | {5,5,6} | {4,5,6,7} | {5,5,5,6} | {3,4,5,6,7} | {3,5,5,5,6} 7 | 6 | {6,7} | {5,6} | {4,5,6,7} | {5,5,5,6} | {4,5,6,7} | {5,5,5,6} (7 rows)
ROWSが指定されているROWS AS (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
は、
ROWSが上下の行を見ていたのに対し、RANGE AS (obj RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING)
は、
GROUPSが指定されているGROUPS AS (obj GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
は、
EXCLUDE句による除外指定
PostageSQL 11からはさらに、
EXCLUDE CURRENT ROW
- 自分の行を除外する
EXCLUDE GROUP
- 自分と同じ値の行を除外する
EXCLUDE TIES
- 重複した値の行を除外する
EXCLUDE NO OTHERS
- ウィンドウフレーム以外の値を省略する
EXCLUDE NO OTHERS
を指定した場合は、
実際の利用例は図5とおりです。
demo=# WITH t(value) AS (VALUES (1), (1), (3), (5), (5), (5), (6)) SELECT value, array_agg(value) OVER (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS current_row, array_agg(value) OVER (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS group, array_agg(value) OVER (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS ties, array_agg(value) OVER (obj ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) AS no_others FROM t WINDOW obj AS (ORDER BY value); value | current_row | group | ties | no_others -------+-------------+-------+---------+----------- 1 | {1} | | {1} | {1,1} 1 | {1,3} | {3} | {1,3} | {1,1,3} 3 | {1,5} | {1,5} | {1,3,5} | {1,3,5} 5 | {3,5} | {3} | {3,5} | {3,5,5} 5 | {5,5} | | {5} | {5,5,5} 5 | {5,6} | {6} | {5,6} | {5,5,6} 6 | {5} | {5} | {5,6} | {5,6} (7 rows)
ストアドファンクション/プロシージャ
PostgreSQLは従来から、
ストアドファンクション──SQLから呼べる独自関数
ストアドファンクションとはユーザー定義関数の一つで、
デメリットとしては、
PL/pgSQLで書く
ストアドファンクションを定義する場合、
demo=# CREATE OR REPLACE FUNCTION fizzbuzz()
demo-# RETURNS setof text as $$
demo$# BEGIN
demo$# FOR i IN 1 .. 100 LOOP
demo$# IF MOD(i, 15) = 0 THEN
demo$# return next 'FizzBuzz';
demo$# ELSIF MOD(i, 5) = 0 THEN
demo$# return next 'Buzz';
demo$# ELSIF MOD(i, 3) = 0 THEN
demo$# return next 'Fizz';
demo$# ELSE
demo$# return next i::text;
demo$# END IF;
demo$# END LOOP;
demo$# RETURN;
demo$# END;
demo$# $$ LANGUAGE plpgsql;
CREATE FUNCTION
demo=# SELECT array_agg(val) AS fizzbuzz FROM LATERAL
(SELECT fizzbuzz() AS val) AS list;
fizzbuzz
------------------------------------------------------
{1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,Fizz
Buzz,16,17,Fizz,19,Buzz,Fizz,22,23,Fizz,Buzz ...
(省略)
一般的な関数と同じように、
注意点は、
ほかの言語で書く
PostgreSQLのストアドファンクションは多くの言語で定義できます。PL/
今回はPL/
demo=# CREATE EXTENSION plpythonu;
demo=# CREATE OR REPLACE FUNCTION
demo-# fizzbuzz_py(input integer)
demo-# ;RETURNS TEXT
demo$# AS $$
demo$# if input % 15 == 0:
demo$# return "fizz buzz"
demo$# elif input % 3 == 0:
demo$# return "fizz"
demo$# elif input % 5 == 0:
demo$# return "buzz"
demo$# else:
demo$# return str(input)
demo$# $$ LANGUAGE plpythonu;
CREATE FUNCTION
demo=# SELECT array_agg(val) AS fizzbuzz FROM LATERAL
(SELECT fizzbuzz_py(GENERATE_SERIES(1, 13)) AS val)
AS list;
fizzbuzz_py
-------------
{1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13}
(13 rows)
今回は引数で値を受け取るようにしました。ロジックに大きな差はないのですが、
注意点として、
ストアドプロシージャ──SQLの処理をまとめる
PostgreSQL 11からは、
呼び出し方法は、CALLストアドプロシージャ名
のようにCALL文で呼び出します。たとえば引数を受け取り、
demo=# CREATE TABLE tbl_a (id int);
CREATE TABLE
demo=# CREATE PROCEDURE
demo-# insert_data(a integer, b integer)
demo-# LANGUAGE SQL
demo-# AS $$
demo$# INSERT INTO tbl_a VALUES (a);
demo$# INSERT INTO tbl_a VALUES (b);
demo$# $$;
CREATE PROCEDURE
demo=# CALL insert_data(1, 2);
CALL
demo=# select * from tbl_a;
id
----
1
2
(2 rows)
Oracle DBからの移行やバッチ処理などで、
JITコンパイラ──実行時に処理を先にコンパイルする
PostgreSQL 11では、
JITコンパイラの使い方
JITコンパイラの利用の有無は、postgresql.
で設定します。デフォルトでは無効です。
先述したとおり、
任意のタイミングで利用したい場合は、
- jit_
optimize_ above_ cost - JITコンパイル時に最適化するかどうかを決めるコストの閾値を指定する。デフォルト値は500000
- jit_
inline_ above_ cost - JITコンパイル時にインライン化を行うかどうかを決めるコストの閾値を指定する。デフォルト値は500000
があります。基本的に変更することは少ないでしょうが、
それでは、on
にするとともに、10
に下げて利用してみましょう。一時的な値の変更ですので、
通常の実行計画 demo=# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class; QUERY PLAN ------------------------------------------------------------------------------------------------- Aggregate (cost=16.27..16.29 rows=1 width=8) (actual time=0.236..0.236 rows=1 loops=1) -> Seq Scan on pg_class (cost=0.00..15.42 rows=342 width=4) (actual time=0.008..0.122 rows=342 loops=1) Planning Time: 3.317 ms Execution Time: 0.485 ms (4 rows) JITコンパイラが利用された場合の実行計画 demo=# SET jit TO on; demo=# SET jit_above_cost TO 10; demo=# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class; QUERY PLAN ------------------------------------------------------------------------------------------------ Aggregate (cost=16.27..16.29 rows=1 width=8) (actual time=9.850..9.850 rows=1 loops=1) -> Seq Scan on pg_class (cost=0.00..15.42 rows=342 width=4) (actual time=0.014..0.066 rows=342 loops=1) Planning Time: 0.132 ms JIT: Functions: 3 Options: Inlining false, Optimization false, Expressions true, Deforming true Timing: Generation 0.894 ms, Inlining 0.000 ms, Optimization 0.652 ms, Emission 8.867 ms, Total 10.414 ms Execution Time: 114.956 ms (8 rows)
JITコンパイラが使われた場合、
名前 | 説明 |
---|---|
Functions | JITコンパイラで処理された箇所の数 |
Options: Inlining | JITコンパイル時にインライン化をしたか |
Options: Optimization | JITコンパイル時に最適化をしたか |
Timing: Generation | JITコンパイルの所要時間 |
Timing: Inlining | JITコンパイルでのインライン化の所要時間 |
Timing: Optimization | JITコンパイルでの最適化の所要時間 |
Timing: Emission | JITコンパイラのコード出力の所要時間 |
単純にExecution Timeを見ると、
JITコンパイラの使いどころ
図6のExecution Timeからわかるとおり、
では、
複雑な分析クエリや大きなテーブルに対するキャストや文字列加工が必要な場合は、
パラレルクエリ──並列実行で高速化
PostgreSQLの大きな魅力と言えば、
パラレルクエリの役割
パラレルクエリは図7のとおり、

パラレルクエリの使い方
パラレルクエリはデフォルトで有効となっており、
実際の実行計画を見てみましょう。図8のとおり、
10,000,000行のテストデータに対する実行
demo=# EXPLAIN ANALYZE SELECT id FROM t1 WHERE id % 3 = 0;
QUERY PLAN
-------------------------------------------------------------------------------------------------
Gather (cost=1000.00..151833.03 rows=49999 width=4) (actual time=0.374..2555.601 rows=3333333 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on t1 (cost=0.00..145833.13 rows=20833 width=4) (actual time=0.048..785.725 rows=1111111 loops=3)
Filter: ((id % 3) = 0)
Rows Removed by Filter: 2222222
Planning Time: 0.168 ms
Execution Time: 2873.127 ms
JITコンパイラほどではありませんが、
- max_
worker_ processes - システム全体で起動できるバックグランドワーカの上限数。これにはパラレルワーカも含み、
たとえば設定値が8の場合に、 自分が定義したバックグランドワーカを4つ起動させると、 パラレルワーカは最大4つしか起動しない。デフォルト値は8 - max_
parallel_ workers_ per_ gather - パラレルクエリ処理中に起動できるパラレルワーカの上限数。設定値が0の場合、
パラレルクエリは実行されない。デフォルト値は2 - max_
parallel_ workers - システム全体で起動できるパラレルワーカの上限数。max_
worker_ processesの設定値を超えることはできない。デフォルト値は8 - parallel_
tuple_ cost - パラレル処理時にほかのプロセスにデータを受け渡しするのに必要なコストに対するプランナの推測値。デフォルト値は0.
1 - parallel_
setup_ cost - パラレル処理を行うプロセスを起動するのに必要なコストに対するプランナの推測値。デフォルト値は1000
- min_
parallel_ table_ scan_ size - テーブルの参照対象が設定値以上だとパラレルワーカを追加起動する。デフォルト値は8MB
- min_
parallel_ index_ scan_ size - インデックスの参照対象が設定値以上だとパラレルワーカを追加起動する。デフォルト値は512KB
テストや使うべきと事前にわかっているアドホックなクエリを実行する場合は、force_
にするとパラレルクエリを利用できます。force_
また、
パラレルクエリの対象
先述したようにパラレルクエリはPostgreSQL 9.
それがPostgreSQL 10、
- 10で追加されたパラレルクエリの対象
- Parallel Index Scan
(b-treeのみ) - Parallel Index Only Scan
(b-treeのみ) - サブクエリ
- Merge Join
- Parallel bitmap heap scan
- Parallel Index Scan
- 11で追加されたパラレルクエリの対象
- Parallel Hash Join
(9. 6からより強化) - CREATE TABLE AS SELECT
- CREATE MATERIALIZED VIEW
- UNION ALLによるAPPEND
- SELECT INTO
- CREATE INDEX
- Parallel Hash Join
一般的に利用する参照部分は、
また、
まとめ
本章では、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT