๋ทฐ

PostgreSQL
SQLite
MySQL
SingleStore

Drizzle ORM์œผ๋กœ ๋ทฐ๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ทฐ๋ฅผ ์„ ์–ธํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ทฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธ๋ผ์ธ query builder ๊ตฌ๋ฌธ, ๋…๋ฆฝํ˜• query builder ๋ฐ ์›์‹œ sql ์—ฐ์‚ฐ์ž๋กœ ๋ทฐ ๋ฌธ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋ทฐ ์„ ์–ธํ•˜๊ธฐ

PostgreSQL
MySQL
SQLite
schema.ts
import { pgTable, pgView, serial, text, timestamp } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: serial(),
  name: text(),
  email: text(),
  password: text(),
  role: text().$type<"admin" | "customer">(),
  createdAt: timestamp("created_at"),
  updatedAt: timestamp("updated_at"),
});

export const userView = pgView("user_view").as((qb) => qb.select().from(user));
export const customersView = pgView("customers_view").as((qb) => qb.select().from(user).where(eq(user.role, "customer")));
CREATE VIEW "user_view" AS SELECT * FROM "user";
CREATE VIEW "customers_view" AS SELECT * FROM "user" WHERE "role" = 'customer';

์ปฌ๋Ÿผ์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฟผ๋ฆฌ ๋นŒ๋”์—์„œ .select({ ... }) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

export const customersView = pgView("customers_view").as((qb) => {
  return qb
    .select({
      id: user.id,
      name: user.name,
      email: user.email,
    })
    .from(user);
});
CREATE VIEW "customers_view" AS SELECT "id", "name", "email" FROM "user" WHERE "role" = 'customer';

๋…๋ฆฝํ˜• query builder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
schema.ts
import { pgTable, pgView, serial, text, timestamp, QueryBuilder} from "drizzle-orm/pg-core";

const qb = new QueryBuilder();

export const user = pgTable("user", {
  id: serial(),
  name: text(),
  email: text(),
  password: text(),
  role: text().$type<"admin" | "customer">(),
  createdAt: timestamp("created_at"),
  updatedAt: timestamp("updated_at"),
});

export const userView = pgView("user_view").as(qb.select().from(user));
export const customersView = pgView("customers_view").as(qb.select().from(user).where(eq(user.role, "customer")));
CREATE VIEW "user_view" AS SELECT * FROM "user";
CREATE VIEW "customers_view" AS SELECT * FROM "user" WHERE "role" = 'customer';

์›์‹œ SQL๋กœ ๋ทฐ ์„ ์–ธํ•˜๊ธฐ

์ฟผ๋ฆฌ ๋นŒ๋”์—์„œ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ๋ฅผ ์„ ์–ธํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, sql ์—ฐ์‚ฐ์ž๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ณ  ๋ทฐ ์ปฌ๋Ÿผ ์Šคํ‚ค๋งˆ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// ์ผ๋ฐ˜ ๋ทฐ
const newYorkers = pgView('new_yorkers', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  cityId: integer('city_id').notNull(),
}).as(sql`select * from ${users} where ${eq(users.cityId, 1)}`);

// ๊ตฌ์ฒดํ™”๋œ ๋ทฐ
const newYorkers = pgMaterializedView('new_yorkers', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  cityId: integer('city_id').notNull(),
}).as(sql`select * from ${users} where ${eq(users.cityId, 1)}`);

๊ธฐ์กด ๋ทฐ ์„ ์–ธํ•˜๊ธฐ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ธฐ์กด ๋ทฐ์— ๋Œ€ํ•œ ์ฝ๊ธฐ ์ „์šฉ ์•ก์„ธ์Šค๊ฐ€ ์ œ๊ณต๋˜๋Š” ๊ฒฝ์šฐ .existing() ๋ทฐ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. drizzle-kit์€ ์ƒ์„ฑ๋œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—์„œ create view ๋ฌธ์„ ๋ฌด์‹œํ•˜๊ณ  ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

export const user = pgTable("user", {
  id: serial(),
  name: text(),
  email: text(),
  password: text(),
  role: text().$type<"admin" | "customer">(),
  createdAt: timestamp("created_at"),
  updatedAt: timestamp("updated_at"),
});

// ์ผ๋ฐ˜ ๋ทฐ
export const trimmedUser = pgView("trimmed_user", {
  id: serial("id"),
  name: text("name"),
  email: text("email"),
}).existing();

// ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋Š” ์ฐจ์ด๊ฐ€ ์—†์ง€๋งŒ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
export const trimmedUser = pgMaterializedView("trimmed_user", {
  id: serial("id"),
  name: text("name"),
  email: text("email"),
}).existing();

๊ตฌ์ฒดํ™”๋œ ๋ทฐ

PostgreSQL
MySQL
SQLite

๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด PostgreSQL์€ ์ผ๋ฐ˜ ๋ฐ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ๋ชจ๋‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

PostgreSQL์˜ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋Š” ์ผ๋ฐ˜ ๋ทฐ์™€ ๊ฐ™์ด ๊ทœ์น™ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ด๋ธ”๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Drizzle ORM์€ PostgreSQL ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:

schema.ts
const newYorkers = pgMaterializedView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1)));
CREATE MATERIALIZED VIEW "new_yorkers" AS SELECT * FROM "users";

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋Ÿฐํƒ€์ž„์—์„œ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ์ƒˆ๋กœ ๊ณ ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

await db.refreshMaterializedView(newYorkers);

await db.refreshMaterializedView(newYorkers).concurrently();

await db.refreshMaterializedView(newYorkers).withNoData();

ํ™•์žฅ๋œ ์˜ˆ์ œ

์ฟผ๋ฆฌ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” $1, $2 ๋“ฑ์œผ๋กœ ๋Œ€์ฒด๋˜์ง€ ์•Š๊ณ  ์ธ๋ผ์ธ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

// ์ผ๋ฐ˜ ๋ทฐ
const newYorkers = pgView('new_yorkers')
  .with({
    checkOption: 'cascaded',
    securityBarrier: true,
    securityInvoker: true,
  })
  .as((qb) => {
    const sq = qb
      .$with('sq')
      .as(
        qb.select({ userId: users.id, cityId: cities.id })
          .from(users)
          .leftJoin(cities, eq(cities.id, users.homeCity))
          .where(sql`${users.age1} > 18`),
      );
    return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`);
  });

// ๊ตฌ์ฒดํ™”๋œ ๋ทฐ
const newYorkers2 = pgMaterializedView('new_yorkers')
  .using('btree')
  .with({
    fillfactor: 90,
    toast_tuple_target: 0.5,
    autovacuum_enabled: true,
    ...
  })
  .tablespace('custom_tablespace')
  .withNoData()
  .as((qb) => {
    const sq = qb
      .$with('sq')
      .as(
        qb.select({ userId: users.id, cityId: cities.id })
          .from(users)
          .leftJoin(cities, eq(cities.id, users.homeCity))
          .where(sql`${users.age1} > 18`),
      );
    return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`);
  });