Kolom yang dihasilkan adalah kolom yang selalu dihitung dari kolom lain dalam baris. Kolom ini dapat membuat kueri lebih sederhana, menghemat biaya evaluasi ekspresi pada waktu kueri, dan dapat diindeks atau digunakan sebagai kunci asing. Dalam artikel ini, kami akan menjelaskan cara mengelola jenis kolom ini di database Anda.
Menambahkan kolom yang dihasilkan ke tabel baru
Dalam cuplikan CREATE TABLE
berikut, kita membuat tabel untuk menyimpan informasi tentang pengguna. Kita memiliki kolom untuk FirstName
dan LastName
serta menentukan
kolom yang dihasilkan untuk FullName
, yang merupakan penggabungan FirstName
dan
LastName
. SQL dalam tanda kurung disebut ekspresi pembuatan.
Kolom yang dihasilkan dapat ditandai sebagai STORED
untuk menghemat biaya evaluasi
ekspresi pada waktu kueri. Akibatnya, nilai FullName
hanya dihitung saat baris baru disisipkan atau saat FirstName
atau LastName
diperbarui untuk baris yang ada. Nilai yang dihitung disimpan bersama dengan kolom lain dalam
tabel.
GoogleSQL
CREATE TABLE Users (
Id STRING(20) NOT NULL,
FirstName STRING(50),
LastName STRING(50),
Age INT64 NOT NULL,
FullName STRING(100) AS (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)
);
Anda dapat membuat kolom yang dihasilkan dan tidak disimpan dengan menghapus atribut STORED
di DDL. Jenis kolom yang dihasilkan ini dievaluasi pada waktu kueri
dan dapat mempermudah kueri. Di PostgreSQL, Anda dapat membuat kolom yang dihasilkan
dan tidak disimpan menggunakan atribut VIRTUAL
.
GoogleSQL
FullName STRING(MAX) AS (CONCAT(FirstName, " ", LastName))
PostgreSQL
fullname text GENERATED ALWAYS AS (firstname || ' ' || lastname) VIRTUAL
expression
dapat berupa ekspresi SQL valid yang dapat ditetapkan ke jenis data kolom dengan batasan berikut.Ekspresi hanya dapat mereferensikan kolom dalam tabel yang sama.
Ekspresi tidak boleh berisi subkueri.
Ekspresi dengan fungsi non-deterministik seperti
PENDING_COMMIT_TIMESTAMP()
,CURRENT_DATE()
, danCURRENT_TIMESTAMP()
tidak dapat dibuat menjadi kolom yang dihasilkanSTORED
atau kolom yang dihasilkan yang diindeks.Anda tidak dapat mengubah ekspresi
STORED
atau kolom yang dihasilkan dan diindeks.
Atribut
STORED
yang mengikuti ekspresi menyimpan hasil ekspresi bersama dengan kolom lain dalam tabel. Pembaruan berikutnya pada kolom yang dirujuk akan menyebabkan Spanner mengevaluasi ulang dan menyimpan ekspresi.Kolom yang dihasilkan yang bukan
STORED
tidak dapat ditandai sebagaiNOT NULL
.Penulisan langsung ke kolom yang dihasilkan tidak diizinkan.
Opsi kolom
allow_commit_timestamp
tidak diizinkan pada kolom yang dihasilkan atau kolom apa pun yang mereferensikan kolom yang dihasilkan.Untuk
STORED
atau kolom yang dihasilkan yang diindeks, Anda tidak dapat mengubah jenis data kolom, atau kolom apa pun yang dirujuk oleh kolom yang dihasilkan.Anda tidak dapat menghapus kolom yang direferensikan oleh kolom yang dihasilkan.
Anda dapat menggunakan kolom yang dihasilkan sebagai kunci utama dengan batasan tambahan berikut:
Kunci utama yang dihasilkan tidak dapat mereferensikan kolom lain yang dihasilkan.
Kunci utama yang dihasilkan dapat mereferensikan, maksimal, satu kolom non-kunci.
Kunci utama yang dihasilkan tidak dapat bergantung pada kolom non-kunci dengan klausa
DEFAULT
.
Aturan berikut berlaku saat menggunakan kolom kunci yang dihasilkan:
- API Baca: Anda harus menentukan kolom kunci sepenuhnya, termasuk kolom kunci yang dihasilkan.
- Mutation API: Untuk
INSERT
,INSERT_OR_UPDATE
, danREPLACE
, Spanner tidak mengizinkan Anda menentukan kolom kunci yang dihasilkan. UntukUPDATE
, Anda dapat menentukan kolom kunci yang dihasilkan secara opsional. UntukDELETE
, Anda harus menentukan kolom kunci sepenuhnya, termasuk kunci yang dihasilkan. - DML: Anda tidak dapat menulis secara eksplisit ke kunci yang dihasilkan dalam pernyataan
INSERT
atauUPDATE
. - Kueri: Secara umum, sebaiknya gunakan kolom kunci yang dihasilkan sebagai filter dalam kueri Anda. Secara opsional, jika ekspresi untuk kolom kunci yang dihasilkan
hanya menggunakan satu kolom sebagai referensi, kueri dapat menerapkan kondisi
kesetaraan (
=
) atauIN
ke kolom yang dirujuk. Untuk mengetahui informasi selengkapnya dan contoh, lihat Membuat kunci unik yang berasal dari kolom nilai.
Kolom yang dihasilkan dapat dikueri seperti kolom lainnya, seperti yang ditunjukkan dalam contoh berikut.
GoogleSQL
SELECT Id, FullName
FROM Users;
PostgreSQL
SELECT id, fullname
FROM users;
Kueri yang menggunakan Fullname
setara dengan kueri dengan ekspresi
yang dihasilkan. Oleh karena itu, kolom yang dihasilkan dapat membuat kueri lebih sederhana.
GoogleSQL
SELECT Id, ARRAY_TO_STRING([FirstName, LastName], " ") as FullName
FROM Users;
PostgreSQL
SELECT id, firstname || ' ' || lastname as fullname
FROM users;
Membuat indeks pada kolom yang dibuat
Anda juga dapat mengindeks atau menggunakan kolom yang dihasilkan sebagai kunci asing.
Untuk membantu pencarian pada kolom yang dihasilkan FullName
, kita dapat membuat
indeks sekunder seperti yang ditunjukkan dalam cuplikan berikut.
GoogleSQL
CREATE INDEX UsersByFullName ON Users (FullName);
PostgreSQL
CREATE INDEX UserByFullName ON users (fullname);
Menambahkan kolom yang dihasilkan ke tabel yang ada
Dengan menggunakan pernyataan ALTER TABLE
berikut, kita dapat menambahkan kolom yang dihasilkan ke
tabel Users
untuk membuat dan menyimpan inisial pengguna.
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;
Jika Anda menambahkan kolom yang dihasilkan dan disimpan ke tabel yang ada, operasi yang berjalan lama untuk mengisi ulang nilai kolom akan dimulai. Selama pengisian ulang, kolom yang dihasilkan dan disimpan tidak dapat dibaca atau dikueri. Status pengisian ulang ditampilkan dalam tabel INFORMATION_SCHEMA.
Membuat indeks parsial menggunakan kolom yang dihasilkan
Bagaimana jika kita hanya ingin membuat kueri pengguna yang berusia di atas 18 tahun? Pemindaian penuh tabel tidak akan efisien, jadi kita menggunakan indeks parsial.
Gunakan pernyataan berikut untuk menambahkan kolom lain yang dihasilkan yang menampilkan usia pengguna jika mereka berusia di atas 18 tahun, dan menampilkan
NULL
jika tidak.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;
Buat indeks pada kolom baru ini, dan nonaktifkan pengindeksan nilai
NULL
dengan kata kunciNULL_FILTERED
di GoogleSQL atau predikatIS NOT NULL
di PostgreSQL. Indeks parsial ini lebih kecil dan lebih efisien daripada indeks normal karena mengecualikan semua orang yang berusia 18 tahun atau lebih muda.GoogleSQL
CREATE NULL_FILTERED INDEX UsersAbove18ByAge ON Users (AgeAbove18);
PostgreSQL
CREATE INDEX UsersAbove18ByAge ON users (AgeAbove18) WHERE AgeAbove18 IS NOT NULL;
Untuk mengambil
Id
danAge
dari semua pengguna yang berusia di atas 18 tahun, jalankan kueri berikut.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;
Untuk memfilter berdasarkan usia yang berbeda, misalnya, untuk mengambil semua pengguna yang berusia lebih dari 21 tahun, gunakan indeks dan filter yang sama pada kolom yang dihasilkan sebagai berikut:
GoogleSQL
SELECT Id, Age FROM Users@{FORCE_INDEX=UsersAbove18ByAge} WHERE AgeAbove18 > 21;
PostgreSQL
SELECT Id, Age FROM users /*@ FORCE_INDEX = UsersAbove18ByAge */ WHERE AgeAbove18 > 21;
Kolom yang dihasilkan dan diindeks dapat menghemat biaya evaluasi ekspresi pada waktu kueri dan menghindari penyimpanan nilai dua kali (di tabel dasar dan indeks) dibandingkan dengan kolom yang dihasilkan
STORED
.
Menghapus kolom yang dihasilkan
Pernyataan DDL berikut menghapus kolom yang dihasilkan dari tabel Users
:
GoogleSQL
ALTER TABLE Users DROP COLUMN Initials;
PostgreSQL
ALTER TABLE users DROP COLUMN initials;
Mengubah ekspresi kolom yang dihasilkan
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;
Memperbarui ekspresi kolom yang dihasilkan STORED
atau kolom yang dihasilkan yang tidak disimpan dan diindeks tidak diizinkan.
Membuat kunci utama pada kolom yang dihasilkan
Di Spanner, Anda dapat menggunakan kolom yang dihasilkan STORED
di
kunci utama.
Contoh berikut menunjukkan pernyataan DDL yang membuat tabel UserInfoLog
dengan kolom yang dihasilkan ShardId
. Nilai kolom ShardId
bergantung pada kolom lain. Kolom ini berasal dari penggunaan fungsi MOD
pada
kolom UserId
. ShardId
dideklarasikan sebagai bagian dari kunci utama.
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));
Biasanya, untuk mengakses baris tertentu secara efisien, Anda perlu menentukan semua kolom
kunci. Dalam contoh sebelumnya, ini berarti menyediakan ShardId
dan
UserId
. Namun, Spanner terkadang dapat menyimpulkan nilai
kolom kunci utama yang dihasilkan jika bergantung pada satu kolom lain dan jika
nilai kolom yang menjadi dependensinya telah ditentukan sepenuhnya. Hal ini benar jika kolom yang direferensikan oleh kolom kunci utama yang dihasilkan memenuhi salah satu kondisi berikut:
- Sama dengan nilai konstan atau parameter terikat dalam klausa
WHERE
, atau - Nilainya ditetapkan oleh operator
IN
dalam klausaWHERE
- Nilainya diperoleh dari kondisi equi-join
Misalnya, untuk kueri berikut:
GoogleSQL
SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;
PostgreSQL
SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;
Spanner dapat menyimpulkan nilai ShardId
dari UserId
yang disediakan.
Kueri sebelumnya setara dengan kueri berikut setelah pengoptimalan kueri:
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;
Contoh berikutnya menunjukkan cara membuat tabel Students
dan menggunakan
ekspresi yang mengambil kolom id
dari kolom JSON StudentInfo
dan
menggunakannya sebagai kunci utama:
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));
Melihat properti kolom yang dihasilkan
INFORMATION_SCHEMA
Spanner berisi informasi tentang kolom yang dihasilkan di database Anda. Berikut adalah beberapa contoh pertanyaan yang dapat Anda jawab saat membuat kueri skema informasi.
Kolom yang dihasilkan apa yang ditentukan dalam database saya?
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
adalah YES
untuk kolom yang dihasilkan dan disimpan, NO
untuk kolom yang dihasilkan dan tidak disimpan, atau NULL
untuk kolom yang tidak dihasilkan.
Apa status kolom yang dihasilkan saat ini di tabel Users
?
Jika telah menambahkan kolom yang dihasilkan ke tabel yang ada, Anda dapat
meneruskan SPANNER_STATE
dalam kueri untuk mengetahui status kolom saat ini.
SPANNER_STATE
menampilkan nilai berikut:
COMMITTED
: Kolom dapat digunakan sepenuhnya.WRITE_ONLY
: Kolom sedang diisi ulang. Tidak ada operasi baca yang diizinkan.
Gunakan kueri berikut untuk menemukan status kolom:
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;
Catatan: Kolom yang dihasilkan dan tidak disimpan hanya dapat diakses menggunakan kueri SQL. Namun, jika diindeks, Anda dapat menggunakan API baca untuk mengakses nilai dari indeks.
Performa
Kolom yang dihasilkan STORED
tidak memengaruhi performa operasi baca atau kueri. Namun, kolom yang dihasilkan yang tidak disimpan dan digunakan dalam kueri dapat memengaruhi
performanya karena overhead evaluasi ekspresi kolom yang dihasilkan.
Performa operasi tulis (pernyataan DML dan mutasi) terpengaruh saat menggunakan kolom yang dihasilkan STORED
atau kolom yang dihasilkan yang diindeks. Overhead ini disebabkan oleh evaluasi ekspresi kolom yang dihasilkan saat
operasi tulis menyisipkan atau mengubah kolom yang dirujuk dalam
ekspresi kolom yang dihasilkan. Karena overhead bervariasi bergantung pada beban kerja
tulis untuk aplikasi, desain skema, dan karakteristik set data, sebaiknya
Anda menjalankan benchmark aplikasi sebelum menggunakan kolom yang dihasilkan.
Langkah selanjutnya
Pelajari lebih lanjut Skema informasi untuk database dialek GoogleSQL dan Skema informasi untuk database dialek PostgreSQL Spanner.
Lihat detail selengkapnya tentang kolom yang dihasilkan dalam detail parameter CREATE TABLE.