Indexes & Constraints

Constraints

SQL ์ œ์•ฝ ์กฐ๊ฑด์€ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์— ์ ์šฉ๋˜๋Š” ๊ทœ์น™์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž…๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด ๋ฐ์ดํ„ฐ์˜ ์ •ํ™•์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

Default

DEFAULT ์ ˆ์€ INSERT ์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ’์„ ์ œ๊ณตํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ปฌ๋Ÿผ์— ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ปฌ๋Ÿผ ์ •์˜์— ๋ช…์‹œ์ ์ธ DEFAULT ์ ˆ์ด ์—†์œผ๋ฉด, ์ปฌ๋Ÿผ์˜ ๊ธฐ๋ณธ๊ฐ’์€ NULL์ž…๋‹ˆ๋‹ค.

๋ช…์‹œ์ ์ธ DEFAULT ์ ˆ์€ ๊ธฐ๋ณธ๊ฐ’์ด NULL, ๋ฌธ์ž์—ด ์ƒ์ˆ˜, blob ์ƒ์ˆ˜, ๋ถ€ํ˜ธ ์žˆ๋Š” ์ˆซ์ž ๋˜๋Š” ๊ด„ํ˜ธ๋กœ ๋ฌถ์ธ ์ƒ์ˆ˜ ํ‘œํ˜„์‹์ž„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { sql } from "drizzle-orm";
import { integer, uuid, pgTable } from "drizzle-orm/pg-core";

const table = pgTable('table', {
  integer1: integer('integer1').default(42),
  integer2: integer('integer2').default(sql`'42'::integer`),
  uuid1: uuid('uuid1').defaultRandom(),
  uuid2: uuid('uuid2').default(sql`gen_random_uuid()`),
});
CREATE TABLE IF NOT EXISTS "table" (
  "integer1" integer DEFAULT 42,
  "integer2" integer DEFAULT '42'::integer,
  "uuid1" uuid DEFAULT gen_random_uuid(),
  "uuid2" uuid DEFAULT gen_random_uuid()
);

Not null

๊ธฐ๋ณธ์ ์œผ๋กœ ์ปฌ๋Ÿผ์€ NULL ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. NOT NULL ์ œ์•ฝ ์กฐ๊ฑด์€ ์ปฌ๋Ÿผ์ด NULL ๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” ํ•„๋“œ๊ฐ€ ํ•ญ์ƒ ๊ฐ’์„ ํฌํ•จํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋ฏ€๋กœ, ์ด ํ•„๋“œ์— ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ ๋Š” ์ƒˆ ๋ ˆ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜๊ฑฐ๋‚˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { integer, pgTable } from "drizzle-orm/pg-core";

const table = pgTable('table', {
  integer: integer('integer').notNull(),
});
CREATE TABLE IF NOT EXISTS "table" (
  "integer" integer NOT NULL
);

Unique

UNIQUE ์ œ์•ฝ ์กฐ๊ฑด์€ ์ปฌ๋Ÿผ์˜ ๋ชจ๋“  ๊ฐ’์ด ์„œ๋กœ ๋‹ค๋ฅด๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

UNIQUE์™€ PRIMARY KEY ์ œ์•ฝ ์กฐ๊ฑด ๋ชจ๋‘ ์ปฌ๋Ÿผ ๋˜๋Š” ์ปฌ๋Ÿผ ์ง‘ํ•ฉ์— ๋Œ€ํ•œ ๊ณ ์œ ์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

PRIMARY KEY ์ œ์•ฝ ์กฐ๊ฑด์€ ์ž๋™์œผ๋กœ UNIQUE ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ฐ–์Šต๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ”๋‹น ์—ฌ๋Ÿฌ ๊ฐœ์˜ UNIQUE ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, PRIMARY KEY ์ œ์•ฝ ์กฐ๊ฑด์€ ํ…Œ์ด๋ธ”๋‹น ํ•˜๋‚˜๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { integer, text, unique, pgTable } from "drizzle-orm/pg-core";

export const user = pgTable('user', {
  id: integer('id').unique(),
});

export const table = pgTable('table', {
  id: integer('id').unique('custom_name'),
});

export const composite = pgTable('composite_example', {
  id: integer('id'),
  name: text('name'),
}, (t) => [
  unique().on(t.id, t.name),
  unique('custom_name').on(t.id, t.name)
]);

// In Postgres 15.0+ NULLS NOT DISTINCT is available
// This example demonstrates both available usages
export const userNulls = pgTable('user_nulls_example', {
  id: integer('id').unique("custom_name", { nulls: 'not distinct' }),
}, (t) => [
  unique().on(t.id).nullsNotDistinct()
]);
CREATE TABLE IF NOT EXISTS "composite_example" (
    "id" integer,
  "name" text,
  CONSTRAINT "composite_example_id_name_unique" UNIQUE("id","name"),
  CONSTRAINT "custom_name" UNIQUE("id","name")
);

CREATE TABLE IF NOT EXISTS "table" (
	"id" integer,
	CONSTRAINT "custom_name" UNIQUE("id")
);

CREATE TABLE IF NOT EXISTS "user" (
	"id" integer,
	CONSTRAINT "user_id_unique" UNIQUE("id")
);

CREATE TABLE IF NOT EXISTS "user_nulls_example" (
  "id" integer,
  CONSTRAINT "custom_name" UNIQUE NULLS NOT DISTINCT("id"),
  CONSTRAINT "user_nulls_example_id_unique" UNIQUE NULLS NOT DISTINCT("id")
);

Check

CHECK ์ œ์•ฝ ์กฐ๊ฑด์€ ์ปฌ๋Ÿผ์— ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์˜ ๋ฒ”์œ„๋ฅผ ์ œํ•œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ปฌ๋Ÿผ์— CHECK ์ œ์•ฝ ์กฐ๊ฑด์„ ์ •์˜ํ•˜๋ฉด ํ•ด๋‹น ์ปฌ๋Ÿผ์— ํŠน์ • ๊ฐ’๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ”์— CHECK ์ œ์•ฝ ์กฐ๊ฑด์„ ์ •์˜ํ•˜๋ฉด ํ–‰์˜ ๋‹ค๋ฅธ ์ปฌ๋Ÿผ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํŠน์ • ์ปฌ๋Ÿผ์˜ ๊ฐ’์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { sql } from "drizzle-orm";
import { check, integer, pgTable, text, uuid } from "drizzle-orm/pg-core";

export const users = pgTable(
  "users",
  {
    id: uuid().defaultRandom().primaryKey(),
    username: text().notNull(),
    age: integer(),
  },
  (table) => [
    check("age_check1", sql`${table.age} > 21`),
  ]
);
CREATE TABLE IF NOT EXISTS "users" (
    "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
    "username" text NOT NULL,
    "age" integer,
    CONSTRAINT "age_check1" CHECK ("users"."age" > 21)
);

Primary Key

PRIMARY KEY ์ œ์•ฝ ์กฐ๊ฑด์€ ํ…Œ์ด๋ธ”์˜ ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํ‚ค๋Š” UNIQUE ๊ฐ’์„ ํฌํ•จํ•ด์•ผ ํ•˜๋ฉฐ, NULL ๊ฐ’์„ ํฌํ•จํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ”์€ ํ•˜๋‚˜์˜ ๊ธฐ๋ณธ ํ‚ค๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ธฐ๋ณธ ํ‚ค๋Š” ๋‹จ์ผ ๋˜๋Š” ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ(ํ•„๋“œ)์œผ๋กœ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, pgTable } from "drizzle-orm/pg-core";

const user = pgTable('user', {
  id: serial('id').primaryKey(),
});

const table = pgTable('table', {
  id: text('cuid').primaryKey(),
});
CREATE TABLE IF NOT EXISTS "user" (
  "id" serial PRIMARY KEY
);

CREATE TABLE IF NOT EXISTS "table" (
  "cuid" text PRIMARY KEY
);

Composite Primary Key

PRIMARY KEY์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค๋Š” ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ด๋ธ”์˜ ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค.

Drizzle ORM์€ ์ด๋ฅผ ์œ„ํ•œ ๋…๋ฆฝ์ ์ธ primaryKey ์—ฐ์‚ฐ์ž๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, integer, primaryKey, pgTable } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: serial("id").primaryKey(),
  name: text("name"),
});

export const book = pgTable("book", {
  id: serial("id").primaryKey(),
  name: text("name"),
});

export const booksToAuthors = pgTable("books_to_authors", {
  authorId: integer("author_id"),
  bookId: integer("book_id"),
}, (table) => [
  primaryKey({ columns: [table.bookId, table.authorId] }),
  // Or PK with custom name
  primaryKey({ name: 'custom_name', columns: [table.bookId, table.authorId] }),
]);
...

CREATE TABLE IF NOT EXISTS "books_to_authors" (
  "author_id" integer,
  "book_id" integer,
  PRIMARY KEY("book_id","author_id")
);

ALTER TABLE "books_to_authors" ADD CONSTRAINT "custom_name" PRIMARY KEY("book_id","author_id");

Foreign key

FOREIGN KEY ์ œ์•ฝ ์กฐ๊ฑด์€ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๋งํฌ๋ฅผ ํŒŒ๊ดดํ•˜๋Š” ์ž‘์—…์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. FOREIGN KEY๋Š” ํ•œ ํ…Œ์ด๋ธ”์˜ ํ•„๋“œ(๋˜๋Š” ํ•„๋“œ ์ง‘ํ•ฉ)๋กœ, ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ PRIMARY KEY๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ์™ธ๋ž˜ ํ‚ค๊ฐ€ ์žˆ๋Š” ํ…Œ์ด๋ธ”์„ ์ž์‹ ํ…Œ์ด๋ธ”์ด๋ผ๊ณ  ํ•˜๊ณ , ๊ธฐ๋ณธ ํ‚ค๊ฐ€ ์žˆ๋Š” ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ๋˜๋Š” ๋ถ€๋ชจ ํ…Œ์ด๋ธ”์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

Drizzle ORM์€ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์„ ์–ธํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ปฌ๋Ÿผ ์„ ์–ธ๋ฌธ์—์„œ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, integer, pgTable } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: serial("id"),
  name: text("name"),
});

export const book = pgTable("book", {
  id: serial("id"),
  name: text("name"),
  authorId: integer("author_id").references(() => user.id)
});

์ž๊ธฐ ์ฐธ์กฐ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, TypeScript ์ œํ•œ์œผ๋กœ ์ธํ•ด ์ฐธ์กฐ ์ฝœ๋ฐฑ์— ๋Œ€ํ•œ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ๋…๋ฆฝ์ ์ธ foreignKey ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, integer, foreignKey, pgTable, AnyPgColumn } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: serial("id"),
  name: text("name"),
  parentId: integer("parent_id").references((): AnyPgColumn => user.id)
});

// or
export const user = pgTable("user", {
  id: serial("id"),
  name: text("name"),
  parentId: integer("parent_id"),
}, (table) => [
  foreignKey({
    columns: [table.parentId],
    foreignColumns: [table.id],
    name: "custom_fk"
  })
]);

๋‹ค์ค‘ ์ปฌ๋Ÿผ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด ์ „์šฉ foreignKey ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, foreignKey, pgTable, AnyPgColumn } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  firstName: text("firstName"),
  lastName: text("lastName"),
}, (table) => [
  primaryKey({ columns: [table.firstName, table.lastName]})
]);

export const profile = pgTable("profile", {
  id: serial("id").primaryKey(),
  userFirstName: text("user_first_name"),
  userLastName: text("user_last_name"),
}, (table) => [
  foreignKey({
    columns: [table.userFirstName, table.userLastName],
    foreignColumns: [user.firstName, user.lastName],
    name: "custom_fk"
  })
])

Indexes

Drizzle ORM์€ index์™€ unique index ์„ ์–ธ์„ ์œ„ํ•œ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
SingleStore
import { serial, text, index, uniqueIndex, pgTable } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: serial("id").primaryKey(),
  name: text("name"),
  email: text("email"),
}, (table) => [
  index("name_idx").on(table.name),
  uniqueIndex("email_idx").on(table.email)
]);
CREATE TABLE "user" (
  ...
);

CREATE INDEX "name_idx" ON "user" ("name");
CREATE UNIQUE INDEX "email_idx" ON "user" ("email");
IMPORTANT

drizzle-kit@0.22.0 ๋ฐ drizzle-orm@0.31.0 ์ด์ „ ๋ฒ„์ „์˜ ๊ฒฝ์šฐ drizzle-kit์€ ์ธ๋ฑ์Šค name๊ณผ on() ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

drizzle-kit@0.22.0 ๋ฐ drizzle-orm@0.31.0 ๋ฒ„์ „ ์ดํ›„๋ถ€ํ„ฐ๋Š” drizzle-kit์—์„œ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์ง€์›๋ฉ๋‹ˆ๋‹ค!

0.31.0๋ถ€ํ„ฐ Drizzle ORM์˜ ์ƒˆ๋กœ์šด ์ธ๋ฑ์Šค API๋Š” ์ธ๋ฑ์Šค ์ƒ์„ฑ์„ ์œ„ํ•œ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ธํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

// First example, with `.on()`
index('name')
  .on(table.column1.asc(), table.column2.nullsFirst(), ...) or .onOnly(table.column1.desc().nullsLast(), table.column2, ...)
  .concurrently()
  .where(sql``)
  .with({ fillfactor: '70' })

// Second Example, with `.using()`
index('name')
  .using('btree', table.column1.asc(), sql`lower(${table.column2})`, table.column1.op('text_ops'))
  .where(sql``) // sql expression
  .with({ fillfactor: '70' })