9月7日、VictoriaMetricsが「PostgreSQL 19 interactive tour」と題した記事を公開した。2026年秋にGA(一般提供)予定のPostgreSQL 19 beta 3で動作確認済みの新機能を、実際に実行可能なコードサンプルとともに解説したものだ。VictoriaMetricsはモニタリング・可観測性分野のSaaS企業だが、技術コミュニティ向けにこうした詳細な検証記事を定期的に公開している。
PostgreSQL 19の最大のトピックは、SQL:2023標準で定義されたプロパティグラフクエリ(SQL/PGQ)の実装だ。Neo4jやAmazon Neptuneといったグラフ専用DBを使わなくても、既存のリレーショナルテーブルに対してグラフ的な問い合わせが書ける。upsert時の「取りこぼし」を解消する ON CONFLICT DO SELECT、時系列データの部分更新を1文にまとめる FOR PORTION OF など、実務で長らく待望されていた機能が一気に揃った。公式リリースノートも合わせて参照されたい。
SQL/PGQによるグラフクエリ
既存テーブルに対して CREATE PROPERTY GRAPH でグラフ構造を宣言する。データのコピーは行われず、ビューに近いオブジェクトが作られるだけだ。
CREATE PROPERTY GRAPH social
VERTEX TABLES (
person KEY (id) LABEL person PROPERTIES (id, name)
)
EDGE TABLES (
follows KEY (follower, followee)
SOURCE KEY (follower) REFERENCES person (id)
DESTINATION KEY (followee) REFERENCES person (id)
LABEL follows
);
GRAPH_TABLE 構文で -[...]->による有向エッジのパターンマッチが可能になる。
SELECT * FROM GRAPH_TABLE (social
MATCH (a IS person)-[IS follows]->(b IS person)
COLUMNS (a.name AS follower, b.name AS followee)
) ORDER BY follower, followee;
エッジを連鎖させることで「友人の友人」のような多段ホップも、自己JOINなしに表現できる。
SELECT * FROM GRAPH_TABLE (social
MATCH (a IS person WHERE a.name = 'Ada')-[IS follows]->()-[IS follows]->(c IS person)
COLUMNS (a.name AS start, c.name AS friend_of_friend)
);
EXPLAIN で確認するとHash Joinに変換されており、専用のグラフ実行エンジンは存在しない。クエリはプランナによって通常のリレーショナル処理に変換されるため、既存のインデックスや統計情報をそのまま活用できる点が実用上の強みだ。
制約として、可変長パス(-[IS follows]->{1,3} のような量化子)は現時点で未対応で、パースは通るが element pattern quantifier is not supported として拒否される。任意深さの探索が必要なユースケースでは引き続きグラフ専用DBの検討が必要だ。
ON CONFLICT DO SELECT でupsertの「取りこぼし」を解消
これまで INSERT ... ON CONFLICT DO NOTHING ... RETURNING には問題があった。コンフリクトした行は結果セットから消えてしまい、「すでに存在していた」のか「何も起きなかった」のかを区別できなかった。
PostgreSQL 19では ON CONFLICT DO SELECT が追加され、既存の行に一切書き込まずにその行をそのまま返すことができる。
INSERT INTO inventory VALUES ('widget', 1), ('gadget', 3)
ON CONFLICT (sku) DO SELECT
RETURNING sku, qty;
┌────────┬─────┐
│ sku │ qty │
├────────┼─────┤
│ widget │ 7 │ ← 既存値(挿入しようとした1ではなく7)
│ gadget │ 3 │
└────────┴─────┘
FOR UPDATE などのロック句と組み合わせると、システム列 xmax(行の更新・削除トランザクションIDを示す内部フィールド)を使って「本当に挿入された行」と「コンフリクトした行」を区別できる。ロック句ありの場合、コンフリクトした行には排他ロックが取得されたことを示すトランザクションIDが xmax に設定されるが、新規挿入された行の xmax は 0 のままとなるため、二者の区別が可能になる。
INSERT INTO inventory VALUES ('widget', 1), ('gadget', 3)
ON CONFLICT (sku) DO SELECT FOR UPDATE
RETURNING sku, qty, xmax = 0 AS was_inserted;
┌────────┬─────┬──────────────┐
│ sku │ qty │ was_inserted │
├────────┼─────┬──────────────┤
│ widget │ 7 │ f │
│ gadget │ 3 │ t │
└────────┴─────┴──────────────┘
ロック句なしでは xmax がどちらも 0 のまま(ロック取得が行われないため)で判別できない点に注意が必要だ。
FOR PORTION OF による時系列データの部分更新・削除
UPDATE と DELETE に FOR PORTION OF 句が追加された。有効期間を持つ範囲型カラム(テンポラルテーブル)に対して、特定期間のスライスだけを操作できる。
2026年全期間有効な価格レコード1行に対して7月だけ価格を変更すると:
UPDATE price
FOR PORTION OF valid_at FROM '2026-07-01' TO '2026-08-01'
SET amount = 7.99;
1行が自動的に3行に分割される(1月〜7月・7月〜8月・8月〜12月)。これまで手動でスライスしてINSERTし直す必要があった処理が1文で済む。PostgreSQL 19ではテンポラルテーブルに関する新しいドキュメントチャプターも追加されている。
ウィンドウ関数の IGNORE NULLS 対応
lead()、lag()、first_value()、last_value()、nth_value() がSQL標準の IGNORE NULLS 句をサポートした。センサーデータのようなスパースな時系列において、前回の実測値をそのまま引き継ぐLOCF(Last Observation Carried Forward:欠損値を直前の観測値で補完する手法)がサブクエリなしの一行で書ける。
SELECT ts, val,
last_value(val) IGNORE NULLS OVER (ORDER BY ts) AS filled
FROM sensor_readings;
これまでは FILTER + サブクエリや CASE 式を組み合わせる必要があり、コードが煩雑になりがちだった。
REPACK コマンド:VACUUM FULL と CLUSTER を統合
REPACK コマンドが追加され、これまで別々だった VACUUM FULL(テーブルの物理的な再構築)と CLUSTER(インデックス順への物理並び替え)を1つのコマンドで実行できる。REPACK (CONCURRENTLY) オプションでは ACCESS EXCLUSIVE ロックなしにテーブルを再構築でき、稼働中のサービスへの影響を抑えられる(ただし wal_level = replica 以上が必要)。既存の VACUUM FULL と CLUSTER コマンド自体は引き続き使用可能だ。
MERGE PARTITIONS / SPLIT PARTITION はPostgreSQL 20以降へ持ち越し
パーティションのマージ・分割を1文で実行できる ALTER TABLE ... MERGE PARTITIONS と ALTER TABLE ... SPLIT PARTITION も開発されていたが、2026年8月27日(beta 3の約2週間後)に「設計上の複数の問題」を理由にリバートされた。PostgreSQL 20以降での実装を目指すとされており、この機能を期待していた場合は引き続き手動でのパーティション操作か、拡張機能での対応が必要になる。
詳細はPostgreSQL 19 interactive tourを参照していただきたい。