時間的行とバージョン管理された行
有効時点クエリと時点指定クエリを学びます
「時間的行とバージョン管理された行」はCoddyKit上の無料SQL Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSQL Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 SQL Academyコースには全4レッスンが含まれています。
テンポラルテーブルとは
テンポラルテーブルを使うと、データが時間とともにどのように変化するかを追跡できます。何かが変更されたときに行を上書きするのではなく、テンポラルテーブルでは行のすべてのバージョンを保持し、それぞれに有効だった期間を付与します。
重要な概念は2つあります。有効時間は現実世界で事実が正しかった期間を表し、トランザクション時間はデータベースがその事実を記録した時点を表します。両方を組み合わせると、完全なバイテンポラルテーブルになります。
有効時間とトランザクション時間の違い
有効時間は、現実世界で事実が正しい期間を表します。たとえば、従業員の給与が2020-01-01から2022-06-30まで適用されていた期間です。トランザクション時間は、データベースの行が挿入された時点、または期限切れになった時点です。これらを組み合わせると、「何が正しかったのか」と「それをいつ把握したのか」という2つの問いに答えられます。
実際の多くの用途では、まず有効時間の追跡から始めます。これはvalid_from列とvalid_to列を使って手動で実装できます。
有効時間テーブルの作成
バージョン管理された行を保存する最も簡単な方法は、valid_fromとvalid_toのタイムスタンプ列を追加することです。valid_toがNULL(または9999-12-31のような遠い将来のセンチネル値)である場合、その行は現在も有効であることを示します。
CREATE TABLE employee_salary (
id SERIAL PRIMARY KEY,
employee_id INT NOT NULL,
salary NUMERIC(12, 2) NOT NULL,
valid_from DATE NOT NULL,
valid_to DATE
);
INSERT INTO employee_salary (employee_id, salary, valid_from, valid_to)
VALUES
(1, 50000, '2020-01-01', '2022-06-30'),
(1, 60000, '2022-07-01', NULL);現在のバージョンの照会
各従業員について現在有効な行を見つけるには、valid_to IS NULL(終了時点が未定義)の行、または今日の日付が有効範囲内にある行に絞り込みます。'9999-12-31'のようなセンチネル値を使うと、範囲の比較が簡単になります。
SELECT employee_id, salary
FROM employee_salary
WHERE valid_to IS NULL
ORDER BY employee_id;As-Ofクエリ
As-Ofクエリは、「特定の時点でデータはどうなっていたか」を尋ねるクエリです。指定したタイムスタンプが有効期間内に入る行に絞り込みます。これはテンポラルテーブルの最も強力な機能の1つです。
-- What was employee 1's salary on 2021-03-15?
SELECT employee_id, salary, valid_from, valid_to
FROM employee_salary
WHERE employee_id = 1
AND valid_from <= '2021-03-15'
AND (valid_to IS NULL OR valid_to > '2021-03-15');バージョン管理された行の更新
事実が変わったとき、既存の行をその場でUPDATEしてはいけません。代わりに、現在の行にvalid_toを設定して終了させ、新しい値を持つ新しい行をINSERTします。これにより、完全な履歴が保持されます。
-- Employee 1 gets a raise effective 2023-01-01
BEGIN;
-- Close the current open row
UPDATE employee_salary
SET valid_to = '2022-12-31'
WHERE employee_id = 1
AND valid_to IS NULL;
-- Insert the new version
INSERT INTO employee_salary (employee_id, salary, valid_from, valid_to)
VALUES (1, 72000, '2023-01-01', NULL);
COMMIT;daterangeによる有効期間の管理
PostgreSQLのdaterange型を使うと、有効期間を1つの列で簡潔に表現できます。@>(包含)演算子を使って、日付が範囲内に入るかどうかを確認できます。また、排他制約を追加して、同じエンティティの期間が重複しないようにできます。
CREATE TABLE employee_salary_v2 (
id SERIAL PRIMARY KEY,
employee_id INT NOT NULL,
salary NUMERIC(12, 2) NOT NULL,
valid_period DATERANGE NOT NULL,
EXCLUDE USING GIST (employee_id WITH =, valid_period WITH &&)
);
INSERT INTO employee_salary_v2 (employee_id, salary, valid_period)
VALUES
(1, 50000, '[2020-01-01, 2022-07-01)'),
(1, 60000, '[2022-07-01, infinity)');daterangeを使ったAs-Ofクエリ
daterangeを使う方法では、As-Ofクエリを非常に読みやすく記述できます。@>演算子が指定した日付が範囲に含まれていることを確認し、下限と上限を自動的に処理します。
-- What was employee 1's salary on 2021-03-15?
SELECT employee_id, salary, valid_period
FROM employee_salary_v2
WHERE employee_id = 1
AND valid_period @> '2021-03-15'::date;システムバージョン管理テーブル(SQL標準)
SQL:2011標準では、システムバージョン管理テンポラルテーブルが導入されました。データベースがrow_start列とrow_end列をトランザクション時間の列として自動的に管理します。PostgreSQLではこれを模倣して実装しますが、SQL ServerとMariaDBではSYSTEM VERSIONINGとして組み込まれています。
次の例では、この概念の参考としてSQL Server / MariaDBの構文を示します。
-- SQL Server / MariaDB syntax (reference)
CREATE TABLE dbo.Product (
ProductID INT PRIMARY KEY,
Name VARCHAR(100),
Price DECIMAL(10,2),
SysStart DATETIME2 GENERATED ALWAYS AS ROW START,
SysEnd DATETIME2 GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (SysStart, SysEnd)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Product_History));テンポラル結合:2つのテーブルの時間軸をそろえる
よくある課題の1つは、時間の期間が一致する2つのテンポラルテーブルを結合することです。たとえば、両方に有効時間の期間がある従業員の給与と所属部署を結合する場合です。エンティティキーに加えて期間の重複条件でも結合し、範囲に対する&&または明示的な日付比較を使用します。
CREATE TABLE dept_assignment (
employee_id INT,
department VARCHAR(50),
valid_period DATERANGE
);
INSERT INTO dept_assignment VALUES
(1, 'Engineering', '[2020-01-01, infinity)'),
(1, 'Marketing', '[2019-01-01, 2020-01-01)');
-- Periods where employee 1 was in Engineering AND had salary > 55000
SELECT s.salary, d.department,
s.valid_period * d.valid_period AS overlap_period
FROM employee_salary_v2 s
JOIN dept_assignment d
ON s.employee_id = d.employee_id
AND s.valid_period && d.valid_period
WHERE s.employee_id = 1
AND s.salary > 55000;期間の空白と重複の防止
テンポラルテーブルでよくあるデータ品質の問題は、空白(レコードが存在しない期間)と重複(2つの行が同時に有効になること)です。&&を使った排他制約により、データベースレベルで重複を防止できます。空白の検出には、クエリで欠落している範囲を確認する必要があります。
-- Find gaps in salary history for employee 1
-- (periods where upper(prev) < lower(next))
SELECT
upper(a.valid_period) AS gap_start,
lower(b.valid_period) AS gap_end
FROM employee_salary_v2 a
JOIN employee_salary_v2 b
ON a.employee_id = b.employee_id
AND upper(a.valid_period) < lower(b.valid_period)
WHERE a.employee_id = 1
AND NOT EXISTS (
SELECT 1 FROM employee_salary_v2 c
WHERE c.employee_id = 1
AND lower(c.valid_period) > upper(a.valid_period)
AND lower(c.valid_period) < lower(b.valid_period)
)
ORDER BY gap_start;理解度チェック
テンポラルテーブルとAs-Ofクエリについて、理解度を確認しましょう。
復習:テンポラル行とバージョン管理された行
このレッスンでは、有効時間の列とPostgreSQLのdaterange型を使って、時間とともに変化するデータをモデル化する方法を学びました。重要なポイント:
- 履歴行を決して上書きしないでください。古い行を終了させ、新しいバージョンを挿入します。
- As-Ofクエリ(
valid_from <= target AND valid_to > target)を使うと、過去の任意の時点のデータを取得できます。 @>演算子と組み合わせたdaterange型により、テンポラルクエリを簡潔で読みやすく記述できます。&&(範囲の重複)に対する排他制約によって、データベースレベルでデータの整合性を保てます。- テンポラル結合では、有効期間の共通部分を取ることで2つの履歴の時間軸をそろえます。
これらのパターンは、イベントソーシング、監査ログ、そして履歴の正確性が重要なあらゆるシステムの基盤になります。
よくある質問
「時間的行とバージョン管理された行」レッスンは無料ですか?
はい。「時間的行とバージョン管理された行」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、SQL Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Academyコースには全4レッスンが含まれています。
「時間的行とバージョン管理された行」で何を学びますか?
有効時点クエリと時点指定クエリを学びます ブラウザで直接実行するハンズオンコードでSQL Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
SQL Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのSQL Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「時間的行とバージョン管理された行」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このSQL Academyレッスンでコードを書いて実行できますか?
はい。すべてのSQL Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 履歴を保持する理由
- 追記専用イベントテーブル
- 時間的行とバージョン管理された行
- イベントから状態を再構築する