Azure SQL Databaseをソースとして差分連携を実施する際における行バージョンが有効な開始時刻列の活用

## 概要

本記事では、Azure SQL database で更新日の監査列を行バージョンが有効な開始時刻（`GENERATED ALWAYS AS ROW START`）列により設定する方法を紹介します。Azure SQL database をソースとして差分連携する際、最終更新日に基づいてデータを抽出する必要があります。通常、更新日の監査列はプログラムにより設定されますが、ソース数が大量であるため、適切に設定されているかの確認が困難でした。そこで、最小限の変更で差分連携を行うために行バージョンが有効な開始時刻列を利用することにしました。この機能を利用することで、差分連携する際の抽出条件を行バージョンが有効な開始時刻列に共通化できるというメリットがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/e0adfadb-c6f8-2d87-67e8-84aede4c19c2.png)

引用元：[CREATE TABLE (Transact-SQL) - SQL Server | Microsoft Learn](https://learn.microsoft.com/ja-jp/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver16#generated-always-as--row--transaction_id--sequence_number---start--end---hidden---not-null-)


## 行バージョンが有効な開始時刻列の設定

行バージョンが有効な開始時刻（`GENERATED ALWAYS AS ROW START`）列は、テンポラルテーブル（システム バージョン管理されたテンポラル テーブル）機能を利用時に設定が必要なカラムであり、INSERT や UPDATE が実施された際にはその時点での UTC のタイムスタンプが設定されます。行バージョンが有効な開始時刻列を利用するためには、以下の SQL に示すように ３つのカラムを設定する必要があります。これらのカラムは、テンポラルテーブル機能を設定しない場合でも動作し、INSERT と UPDATE の処理時刻を打刻します。今回は差分連携することを目的としているため、テンポラルテーブル機能は利用しないこと（SYSTEM_VERSIONING = OFF）としました。

```sql
    ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
```

引用元：[システム バージョン管理されたテンポラル テーブルを作成する - SQL Server | Microsoft Learn](https://learn.microsoft.com/ja-jp/sql/relational-databases/tables/creating-a-system-versioned-temporal-table?view=sql-server-ver16#creating-a-temporal-table-with-a-user-defined-history-table)

行バージョンが有効な開始時刻（`GENERATED ALWAYS AS ROW START`）列を設定する際に既存のプログラムに影響を与えないために、`HIDDEN`オプションを設定する必要があります。`HIDDEN`オプションを設定しない場合には、INSERT 文において default を設定する必要があり、既存のプログラムの改修が必要となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/134882c2-0c91-26e2-7cef-45b7a19cecdd.png)

引用元：[システム バージョン管理のテンポラル テーブルのデータの変更 - SQL Server | Microsoft Learn](https://learn.microsoft.com/ja-jp/sql/relational-databases/tables/modifying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16#insert-data-into-a-table-with-hidden-period-columns)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/80f8b175-0f47-6742-b1b9-132cc386c7e2.png)

引用元：[システム バージョン管理のテンポラル テーブルのデータの変更 - SQL Server | Microsoft Learn](https://learn.microsoft.com/ja-jp/sql/relational-databases/tables/modifying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16#insert-new-data-with-visible-period-columns)


## 検証コードと実行結果

本記事では、次の２つのケースにおける検証コードと実行結果を記述します。

1. 新規テーブルにおける行バージョンが有効な開始時刻列の利用
2. 既存テーブルにおける行バージョンが有効な開始時刻列の利用

### 1. 新規テーブルにおける行バージョンが有効な開始時刻列の利用

#### テーブルの作成

```sql
DROP TABLE IF EXISTS UpadateTest01;
CREATE TABLE UpadateTest01 (
    Id INT NOT NULL,
    Name VARCHAR(50),
    ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
    ValidTo DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
    PERIOD FOR SYSTEM_TIME(ValidFrom, ValidTo),
    CONSTRAINT PkUpadateTest01 PRIMARY KEY CLUSTERED (Id)
    )
    WITH (SYSTEM_VERSIONING = OFF);

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/c51361ab-590a-cf57-7d0f-18273977559e.png)

#### INSERT の実行

```sql
-- Insert
INSERT INTO UpadateTest01 (
    Id,
    Name
    )
SELECT 1,
    'insert'
UNION ALL
SELECT 2,
    NULL
UNION ALL
SELECT 4,
    NULL;

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/65661c40-36c0-ce01-dd40-a76673a5171e.png)

#### UPDATE の実行

```sql
-- Update
WAITFOR DELAY '00:00:10';

UPDATE UpadateTest01
SET Name = 'update'
WHERE Id = 2;

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/292915e8-5585-d407-1753-c4aed5d1215c.png)

#### MERGE の実行

```sql
-- Merge
WAITFOR DELAY '00:00:10';
DROP TABLE IF EXISTS #UpadateTest01Staging;
CREATE TABLE #UpadateTest01Staging (Id INT, Name varchar(50));

GO
INSERT INTO #UpadateTest01Staging VALUES (3, 'merge_insert');;
INSERT INTO #UpadateTest01Staging VALUES (4, 'merge_update');

MERGE UpadateTest01 AS target
USING #UpadateTest01Staging AS source
    ON (target.Id = source.Id)
WHEN MATCHED
    THEN
        UPDATE
        SET Name = source.Name
WHEN NOT MATCHED
    THEN
        INSERT (
            Id,
            Name
            )
        VALUES (
            source.Id,
            source.Name
            );

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/0fe3328d-586d-e49b-1f89-4536d5ef6578.png)

#### 実行結果の確認

行バージョンが有効な開始時刻列（`ValidFrom`列）に実行時のタイムスタンプが設定されていることが確認できます。

```sql
SELECT *,
    ValidFrom,
    ValidTo
FROM UpadateTest01;

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/207ae44b-075c-4672-113c-66a0780cf619.png)

### 2. 既存テーブルにおける行バージョンが有効な開始時刻列の利用

#### 1. テーブルの作成と INSERT 処理の実行

```sql
DROP TABLE IF EXISTS UpadateTest02;
CREATE TABLE UpadateTest02 (
    Id INT NOT NULL,
    Name VARCHAR(50)
    CONSTRAINT PkUpadateTest02 PRIMARY KEY CLUSTERED (Id)
    );

-- Insert
INSERT INTO UpadateTest02 (
    Id,
    Name
    )
SELECT 1,
    'pre_insert';

SELECT *
FROM UpadateTest02;

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/fd98404e-db76-5db0-5c77-154ea1925233.png)


#### 行バージョンが有効な開始時刻列等を追加

```sql
ALTER TABLE UpadateTest02
    ADD
        ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN
            CONSTRAINT DF_InsurancePolicy_ValidFrom DEFAULT SYSUTCDATETIME(),
        ValidTo DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN
            CONSTRAINT DF_InsurancePolicy_ValidTo DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
    PERIOD FOR SYSTEM_TIME(ValidFrom, ValidTo);
GO

ALTER TABLE UpadateTest02
    SET (SYSTEM_VERSIONING = OFF);
GO

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/170f21b4-6f46-851a-2c7b-ac71e51517da.png)

#### INSERT 処理

```sql
-- Insert
WAITFOR DELAY '00:00:10';

INSERT INTO UpadateTest02 (
    Id,
    Name
    )
SELECT 2,
    'insert';

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/99244f84-0cc0-7e00-2c99-29c01a7d9f26.png)

#### 実行結果の確認

行バージョンが有効な開始時刻列（`ValidFrom`列）に実行時のタイムスタンプが設定されていることが確認できます。

```sql
SELECT *,
    ValidFrom,
    ValidTo
FROM UpadateTest02;

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6f872299-2621-ed52-8de3-5110965e3d6c.png)

### テーブルの削除


```sql
DROP TABLE IF EXISTS UpadateTest01;
DROP TABLE IF EXISTS UpadateTest02;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/bb5921fe-4c2e-8617-88c8-9daf65736ca3.png)