Generated Columns

이 κΈ°λŠ₯을 μ‚¬μš©ν•˜λ €λ©΄ drizzle-orm@0.32.0 이상 및 drizzle-kit@0.23.0 이상이 ν•„μš”ν•©λ‹ˆλ‹€

SQL의 Generated columnsλŠ” 같은 ν…Œμ΄λΈ” λ‚΄μ˜ λ‹€λ₯Έ μ»¬λŸΌλ“€μ„ ν¬ν•¨ν•˜λŠ” ν‘œν˜„μ‹μ„ 기반으둜 값이 μžλ™μœΌλ‘œ κ³„μ‚°λ˜λŠ” μ»¬λŸΌμ„ 생성할 수 μžˆλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€. 이λ₯Ό 톡해 데이터 일관성을 보μž₯ν•˜κ³ , λ°μ΄ν„°λ² μ΄μŠ€ 섀계λ₯Ό λ‹¨μˆœν™”ν•˜λ©°, 쿼리 μ„±λŠ₯을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

Generated columnsμ—λŠ” 두 κ°€μ§€ μœ ν˜•μ΄ μžˆμŠ΅λ‹ˆλ‹€:

  1. Virtual (λ˜λŠ” non-persistent) Generated Columns: μ΄λŸ¬ν•œ μ»¬λŸΌμ€ 쿼리할 λ•Œλ§ˆλ‹€ λ™μ μœΌλ‘œ κ³„μ‚°λ©λ‹ˆλ‹€. λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ 곡간을 μ°¨μ§€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

  2. Stored (λ˜λŠ” persistent) Generated Columns: μ΄λŸ¬ν•œ μ»¬λŸΌμ€ 행이 μ‚½μž…λ˜κ±°λ‚˜ μ—…λ°μ΄νŠΈλ  λ•Œ κ³„μ‚°λ˜λ©° κ·Έ 값이 λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯λ©λ‹ˆλ‹€. 이λ₯Ό 톡해 인덱싱이 κ°€λŠ₯ν•˜κ³  각 μΏΌλ¦¬λ§ˆλ‹€ 값을 μž¬κ³„μ‚°ν•  ν•„μš”κ°€ μ—†κΈ° λ•Œλ¬Έμ— 쿼리 μ„±λŠ₯을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

Generated columnsλŠ” λ‹€μŒκ³Ό 같은 κ²½μš°μ— 특히 μœ μš©ν•©λ‹ˆλ‹€:

Generated columns의 κ΅¬ν˜„ 및 μ‚¬μš©λ²•μ€ μ„œλ‘œ λ‹€λ₯Έ SQL λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 크게 λ‹¬λΌμ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. PostgreSQL, MySQL, SQLiteλŠ” 각각 generated columns와 κ΄€λ ¨ν•˜μ—¬ κ³ μœ ν•œ κΈ°λŠ₯, μ—­λŸ‰ 및 μ œν•œ 사항을 κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 μ„Ήμ…˜μ—μ„œλŠ” 각 λ°μ΄ν„°λ² μ΄μŠ€ μ‹œμŠ€ν…œμ—μ„œ generated columnsλ₯Ό κ°€μž₯ 잘 ν™œμš©ν•˜λŠ” 방법을 μ΄ν•΄ν•˜λŠ” 데 도움이 λ˜λ„λ‘ μ΄λŸ¬ν•œ 차이점을 μžμ„Ένžˆ μ‚΄νŽ΄λ΄…λ‹ˆλ‹€.

PostgreSQL
MySQL
SQLite
SingleStore(WIP)

λ°μ΄ν„°λ² μ΄μŠ€ μΈ‘

νƒ€μž…: STORED만 지원

μž‘λ™ 방식

  • μ‚½μž… λ˜λŠ” μ—…λ°μ΄νŠΈ μ‹œ λ‹€λ₯Έ μ»¬λŸΌμ„ 기반으둜 값을 μžλ™μœΌλ‘œ κ³„μ‚°ν•©λ‹ˆλ‹€.

κΈ°λŠ₯

  • λ³΅μž‘ν•œ ν‘œν˜„μ‹μ„ 미리 κ³„μ‚°ν•˜μ—¬ 데이터 μ•‘μ„ΈμŠ€λ₯Ό λ‹¨μˆœν™”ν•©λ‹ˆλ‹€.
  • Generated columns에 λŒ€ν•œ 인덱슀 μ§€μ›μœΌλ‘œ 쿼리 μ„±λŠ₯을 ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€.

μ œν•œ 사항

  • 기본값을 μ§€μ •ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
  • ν‘œν˜„μ‹μ€ λ‹€λ₯Έ generated columnsλ₯Ό μ°Έμ‘°ν•˜κ±°λ‚˜ μ„œλΈŒμΏΌλ¦¬λ₯Ό 포함할 수 μ—†μŠ΅λ‹ˆλ‹€.
  • Generated column ν‘œν˜„μ‹μ„ μˆ˜μ •ν•˜λ €λ©΄ μŠ€ν‚€λ§ˆ 변경이 ν•„μš”ν•©λ‹ˆλ‹€.
  • Primary keys, foreign keys λ˜λŠ” unique constraintsμ—μ„œ 직접 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

μžμ„Έν•œ μ •λ³΄λŠ” PostgreSQL λ¬Έμ„œλ₯Ό ν™•μΈν•˜μ„Έμš”.

Drizzle μΈ‘

Drizzleμ—μ„œλŠ” λͺ¨λ“  컬럼 νƒ€μž…μ— .generatedAlwaysAs() ν•¨μˆ˜λ₯Ό μ§€μ •ν•˜κ³  μ§€μ›λ˜λŠ” sql 쿼리λ₯Ό μΆ”κ°€ν•˜μ—¬ 이 컬럼 데이터λ₯Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

κΈ°λŠ₯

이 ν•¨μˆ˜λŠ” 3κ°€μ§€ λ°©λ²•μœΌλ‘œ generated ν‘œν˜„μ‹μ„ 받을 수 μžˆμŠ΅λ‹ˆλ‹€:

string

export const test = pgTable("test", {
    generatedName: text("gen_name").generatedAlwaysAs(`hello world!`),
});
CREATE TABLE IF NOT EXISTS "test" (
    "gen_name" text GENERATED ALWAYS AS (hello world!) STORED
);

sql νƒœκ·Έ - Drizzle이 일뢀 값을 μ΄μŠ€μΌ€μ΄ν”„ν•˜λ„λ‘ ν•˜λ €λŠ” 경우

export const test = pgTable("test", {
    generatedName: text("gen_name").generatedAlwaysAs(sql`hello "world"!`),
});
CREATE TABLE IF NOT EXISTS "test" (
    "gen_name" text GENERATED ALWAYS AS (hello "world"!) STORED
);

callback - ν…Œμ΄λΈ”μ˜ μ»¬λŸΌμ„ μ°Έμ‘°ν•΄μ•Ό ν•˜λŠ” 경우

export const test = pgTable("test", {
    name: text("first_name"),
    generatedName: text("gen_name").generatedAlwaysAs(
      (): SQL => sql`hi, ${test.name}!`
    ),
});
CREATE TABLE IF NOT EXISTS "test" (
    "first_name" text,
    "gen_name" text GENERATED ALWAYS AS (hi, "test"."first_name"!) STORED
);

예제 전체 ν…μŠ€νŠΈ 검색을 μ‚¬μš©ν•œ generated columns

schema.ts
import { SQL, sql } from "drizzle-orm";
import { customType, index, integer, pgTable, text } from "drizzle-orm/pg-core";

const tsVector = customType<{ data: string }>({
  dataType() {
    return "tsvector";
  },
});

export const test = pgTable(
  "test",
  {
    id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
    content: text("content"),
    contentSearch: tsVector("content_search", {
      dimensions: 3,
    }).generatedAlwaysAs(
      (): SQL => sql`to_tsvector('english', ${test.content})`
    ),
  },
  (t) => [
    index("idx_content_search").using("gin", t.contentSearch)
  ]
);
CREATE TABLE IF NOT EXISTS "test" (
	"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "test_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
	"content" text,
	"content_search" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "test"."content")) STORED
);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "idx_content_search" ON "test" USING gin ("content_search");