生成された列は、行の他の列から常に計算される列です。これらの列では、クエリを簡素化し、クエリの実行時に式を評価する費用を削減できるほか、インデックスを作成したり、外部キーとして使用できます。この記事では、データベースでこの列タイプを管理する方法について説明します。
新しいテーブルに生成された列を追加する
次の CREATE TABLE
スニペットでは、ユーザーに関する情報を格納するテーブルを作成します。FirstName
と LastName
の列があり、FirstName
と LastName
を連結した FullName
用に生成される列を定義します。かっこ内の SQL は世代式と呼ばれます。
生成された列を STORED
とマークすることで、クエリ時に式を評価する費用を削減できます。そのため、FullName
の値は、新しい行が挿入されたとき、または既存の行に対して FirstName
か LastName
が更新されたときにのみ計算されます。計算された値は、テーブルの他の列と一緒に保存されます。
GoogleSQL
CREATE TABLE Users (
Id STRING(20) NOT NULL,
FirstName STRING(50),
LastName STRING(50),
Age INT64 NOT NULL,
FullName STRING(100) AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED,
) PRIMARY KEY (Id);
PostgreSQL
CREATE TABLE users (
id VARCHAR(20) NOT NULL,
firstname VARCHAR(50),
lastname VARCHAR(50),
age BIGINT NOT NULL,
fullname VARCHAR(100) GENERATED ALWAYS AS (firstname || ' ' || lastname) STORED,
PRIMARY KEY(id)
);
保存されていない生成列を作成するには、DDL で STORED
属性を省略します。このタイプの生成された列はクエリ時に評価され、クエリを簡略化できます。PostgreSQL では、VIRTUAL
属性を使用して、保存されない生成列を作成できます。
GoogleSQL
FullName STRING(MAX) AS (CONCAT(FirstName, " ", LastName))
PostgreSQL
fullname text GENERATED ALWAYS AS (firstname || ' ' || lastname) VIRTUAL
expression
は、列のデータ型に割り当てる有効な SQL 式です。ただし、次の制限があります。式は、同じテーブルの列のみを参照できます。
式にサブクエリを含めることはできません。
PENDING_COMMIT_TIMESTAMP()
、CURRENT_DATE()
、CURRENT_TIMESTAMP()
などの非決定性関数を含む式は、STORED
生成列またはインデックスが設定された生成列にすることはできません。STORED
またはインデックス付きで生成された列の式は変更できません。
式の後に続く
STORED
属性には、式の結果がテーブルの他の列とともに格納されます。その後、参照されるいずれかの列が更新されると、Spanner は式を再評価して保存します。STORED
以外の生成列はNOT NULL
としてマークできません。生成された列に直接書き込むことはできません。
生成された列、または生成された列が参照する列では、列オプション
allow_commit_timestamp
を使用できません。インデックス付きの
STORED
列または生成された列の場合、列のデータ型や、生成された列が参照する列のデータ型は変更できません。生成された列から参照する列は削除できません。
生成された列は、次の追加の制限事項で主キーとして使用できます。
生成された主キーは、生成された他の列を参照できません。
生成された主キーは、キー以外の列を 1 つまで参照できます。
生成された主キーは、
DEFAULT
句を含むキー以外の列には使用できません。
生成されたキー列を使用する場合は、次のルールが適用されます。
- 読み取り API: 生成されたキー列を含むキー列を完全に指定する必要があります。
- Mutation API:
INSERT
、INSERT_OR_UPDATE
、REPLACE
の場合、Spanner では生成されたキー列を指定できません。UPDATE
には、必要に応じて生成されたキー列を指定できます。DELETE
の場合、生成されたキーを含むキー列を完全に指定する必要があります。 - DML:
INSERT
またはUPDATE
ステートメントで、生成されたキーに明示的に書き込むことはできません。 - クエリ: 一般に、生成されたキー列をクエリ内のフィルタとして使用することをおすすめします。生成されたキー列の式で参照として 1 つの列のみが使用されている場合は、クエリで参照列に等式(
=
)またはIN
条件を適用できます。詳細と例については、値列から派生した一意のキーを作成するをご覧ください。
生成された列は、他の列と同様にクエリできます。次に例を示します。
GoogleSQL
SELECT Id, FullName
FROM Users;
PostgreSQL
SELECT id, fullname
FROM users;
Fullname
を使用するクエリは、生成された式を含むクエリと同じです。そのため、生成された列を使用するとクエリがシンプルになります。
GoogleSQL
SELECT Id, ARRAY_TO_STRING([FirstName, LastName], " ") as FullName
FROM Users;
PostgreSQL
SELECT id, firstname || ' ' || lastname as fullname
FROM users;
生成された列にインデックスを作成する
生成された列を外部キーとしてインデックスに登録したり、使用したりすることもできます。
生成された FullName
列の検索に役立つように、セカンダリ インデックスを作成できます。次のスニペットに例を示します。
GoogleSQL
CREATE INDEX UsersByFullName ON Users (FullName);
PostgreSQL
CREATE INDEX UserByFullName ON users (fullname);
既存のテーブルに生成された列を追加する
次の ALTER TABLE
ステートメントを使用して Users
テーブルに生成された列を追加し、ユーザーのイニシャルを生成、保存します。
GoogleSQL
ALTER TABLE Users ADD COLUMN Initials STRING(2)
AS (ARRAY_TO_STRING([SUBSTR(FirstName, 0, 1), SUBSTR(LastName, 0, 1)], "")) STORED;
PostgreSQL
ALTER TABLE users ADD COLUMN initials VARCHAR(2)
GENERATED ALWAYS AS (SUBSTR(firstname, 0, 1) || SUBSTR(lastname, 0, 1)) STORED;
既存のテーブルに格納された生成列を追加すると、列の値をバックフィルする長時間実行オペレーションが開始されます。バックフィル中、格納された生成された列の読み取りやクエリはできません。バックフィルの状態は INFORMATION_SCHEMA テーブルに反映されます。
生成された列を使用した部分的なインデックスの作成
18 歳以上のユーザーに対してのみクエリを実行する場合はどうすればよいでしょうか。テーブルのフルスキャンは非効率的であるため、部分インデックスを使用します。
次のステートメントを使用して、生成された別の列を追加します。この列は、18 歳以上であればユーザーの年齢を返し、そうでなければ
NULL
を返します。GoogleSQL
ALTER TABLE Users ADD COLUMN AgeAbove18 INT64 AS (IF(Age > 18, Age, NULL));
PostgreSQL
ALTER TABLE Users ADD COLUMN AgeAbove18 BIGINT GENERATED ALWAYS AS (nullif( Age , least( 18, Age) )) VIRTUAL;
この新しい列にインデックスを作成し、GoogleSQL 内の
NULL_FILTERED
キーワード、または PostgreSQL 内のIS NOT NULL
述語を持つNULL
値のインデックスを無効にします。この部分インデックスは、18 歳以下のすべてのユーザーが除外されるため、通常のインデックスよりも小さく、効率的です。GoogleSQL
CREATE NULL_FILTERED INDEX UsersAbove18ByAge ON Users (AgeAbove18);
PostgreSQL
CREATE INDEX UsersAbove18ByAge ON users (AgeAbove18) WHERE AgeAbove18 IS NOT NULL;
18 歳以上のすべてのユーザーの
Id
とAge
を取得するには、次のクエリを実行します。GoogleSQL
SELECT Id, Age FROM Users@{FORCE_INDEX=UsersAbove18ByAge} WHERE AgeAbove18 IS NOT NULL;
PostgreSQL
SELECT Id, Age FROM users /*@ FORCE_INDEX = UsersAbove18ByAge */ WHERE AgeAbove18 IS NOT NULL;
たとえば 21 歳以上のすべてのユーザーを取得するなど、違う年齢でフィルタリングするには、生成された列で同じインデックスとフィルタを使用します。次に例を示します。
GoogleSQL
SELECT Id, Age FROM Users@{FORCE_INDEX=UsersAbove18ByAge} WHERE AgeAbove18 > 21;
PostgreSQL
SELECT Id, Age FROM users /*@ FORCE_INDEX = UsersAbove18ByAge */ WHERE AgeAbove18 > 21;
インデックス付きの生成列を使用すると、クエリの実行時に式を評価する費用を削減でき、
STORED
で生成された列と比較して、ベーステーブルとインデックスに値を 2 回保存する必要がなくなります。
生成された列を削除する
次の DDL ステートメントは、生成された列を Users
テーブルから削除します:
GoogleSQL
ALTER TABLE Users DROP COLUMN Initials;
PostgreSQL
ALTER TABLE users DROP COLUMN initials;
生成された列の式を変更する
GoogleSQL
ALTER TABLE Users ALTER COLUMN FullName STRING(100)
AS (ARRAY_TO_STRING(ARRAY_TO_STRING([LastName, FirstName ], " ")));
PostgreSQL
ALTER TABLE users ADD COLUMN Initials VARCHAR(2)
GENERATED ALWAYS AS (lastname || ' ' || firstname) VIRTUAL;
STORED
で生成された列、またはインデックス付きの保存されていない生成された列の式を更新することはできません。
生成された列に主キーを作成する
Spanner では、主キーで STORED
生成列を使用できます。
次の例は、ShardId
生成列を持つ UserInfoLog
テーブルを作成する DDL ステートメントを示しています。ShardId
列の値は別の列に依存します。これは、UserId
列に対して MOD
関数を使用して導出されます。ShardId
は、主キーの一部として宣言されます。
GoogleSQL
CREATE TABLE UserInfoLog (
ShardId INT64 NOT NULL
AS (MOD(UserId, 2048)) STORED,
UserId INT64 NOT NULL,
FullName STRING(1024) NOT NULL,
) PRIMARY KEY (ShardId, UserId);
PostgreSQL
CREATE TABLE UserInfoLog (
ShardId BIGINT GENERATED ALWAYS
AS (MOD(UserId, '2048'::BIGINT)) STORED NOT NULL,
UserId BIGINT NOT NULL,
FullName VARCHAR(1024) NOT NULL,
PRIMARY KEY(ShardId, UserId));
通常、特定の行に効率的にアクセスするには、すべてのキー列を指定する必要があります。前の例では、ShardId
と UserId
の両方を指定することになります。ただし、Spanner は、生成された主キー列が 1 つの他の列に依存し、その列の値が完全に決定されている場合、生成された主キー列の値を推測できます。これは、生成された主キー列によって参照される列が次のいずれかの条件を満たす場合に該当します。
WHERE
句の定数値またはバインドされたパラメータと同じであるWHERE
句のIN
演算子によって設定された値を取得している- 等価結合条件から値を取得している
たとえば、次のクエリの場合:
GoogleSQL
SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;
PostgreSQL
SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;
Spanner は、指定された UserId
から ShardId
の値を推測できます。上記のクエリは、クエリ最適化後の次のクエリと同等です。
GoogleSQL
SELECT * FROM UserInfoLog
AS T WHERE T.ShardId = MOD(1, 2048)
AND T.UserId=1;
PostgreSQL
SELECT * FROM UserInfoLog
AS T WHERE T.ShardId = MOD(1, 2048)
AND T.UserId=1;
次の例は、Students
テーブルを作成し、StudentInfo
JSON 列の id
フィールドを取得して主キーとして使用する式を使用する方法を示しています。
GoogleSQL
CREATE TABLE Students (
StudentId INT64 NOT NULL
AS (CAST(JSON_VALUE(StudentInfo, "$.id") AS INT64)) STORED,
StudentInfo JSON NOT NULL,
) PRIMARY KEY (StudentId);
PostgreSQL
CREATE TABLE Students (
StudentId BIGINT GENERATED ALWAYS
AS (((StudentInfo ->> 'id'::TEXT))::BIGINT) STORED NOT NULL,
StudentInfo JSONB NOT NULL,
PRIMARY KEY(StudentId));
生成された列のプロパティを表示する
Spanner の INFORMATION_SCHEMA
には、データベース上に生成された列に関する情報が含まれています。以下に、情報スキーマをクエリすることによって回答できる質問の例を示します。
データベースではどのような列が生成されますか。
GoogleSQL
SELECT c.TABLE_NAME, c.COLUMN_NAME, C.IS_STORED
FROM INFORMATION_SCHEMA.COLUMNS as c
WHERE c.GENERATION_EXPRESSION IS NOT NULL;
PostgreSQL
SELECT c.TABLE_NAME, c.COLUMN_NAME, C.IS_STORED
FROM INFORMATION_SCHEMA.COLUMNS as c
WHERE c.GENERATION_EXPRESSION IS NOT NULL;
IS_STORED
は、保存済みの生成された列の場合は YES
、保存されていない生成された列の場合は NO
、生成されていない列の場合は NULL
です。
テーブル Users
で生成された列の現在の状態は?
既存のテーブルに生成された列を追加した場合は、クエリで SPANNER_STATE
を渡して、列の現在の状態を確認できます。SPANNER_STATE
は次の値を返します。
COMMITTED
: この列はすべて使用できます。WRITE_ONLY
: 列はバックフィルされます。読み取りはできません。
次のクエリを使用して、列の状態を確認します。
GoogleSQL
SELECT c.TABLE_NAME, c.COLUMN_NAME, c.SPANNER_STATE
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_NAME="Users" AND c.GENERATION_EXPRESSION IS NOT NULL;
PostgreSQL
SELECT c.TABLE_NAME, c.COLUMN_NAME, c.SPANNER_STATE
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_NAME='users' AND c.GENERATION_EXPRESSION IS NOT NULL;
注: 保存されていない生成された列には、SQL クエリを使用してのみアクセスできます。ただし、インデックス付けされている場合は、読み取り API を使用してインデックスの値にアクセスできます。
パフォーマンス
STORED
で生成された列は、読み取りオペレーションやクエリ オペレーションのパフォーマンスに影響しません。ただし、クエリで使用される保存されていない生成列は、生成された列の式を評価するオーバーヘッドのため、パフォーマンスに影響する可能性があります。
STORED
生成列またはインデックスが設定された生成列を使用すると、書き込みオペレーション(DML ステートメントとミューテーション)のパフォーマンスに影響します。このオーバーヘッドは、書き込みオペレーションによって生成された列式で参照される列の挿入または変更が行われたときに、生成された列式が評価されることで発生します。アプリケーションの書き込みワークロード、スキーマ設計、データセットの特性によってオーバーヘッドが異なるため、生成された列を使用する前にアプリケーションをベンチマークすることをおすすめします。
次のステップ
Spanner の GoogleSQL 方言データベースの情報スキーマと PostgreSQL 方言データベースの情報スキーマの詳細を確認する。
生成された列の詳細については、テーブルの作成パラメータの詳細をご覧ください。