SQL Insert

Drizzle ORM์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์— ํ–‰์„ ์‚ฝ์ž…ํ•˜๋Š” ๊ฐ€์žฅ SQL๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Insert one row

Drizzle๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  SQL๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ํ™•์ธํ•ด๋ณด์„ธ์š”:

await db.insert(users).values({ name: 'Andrew' });
insert into "users" ("name") values ("Andrew");

ํŠน์ • ํ…Œ์ด๋ธ”์˜ insert ํƒ€์ž…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ typeof usersTable.$inferInsert ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type NewUser = typeof users.$inferInsert;

const insertUser = async (user: NewUser) => {
  return db.insert(users).values(user);
}

const newUser: NewUser = { name: "Alef" };
await insertUser(newUser);

Insert returning

PostgreSQL
SQLite
MySQL
SingleStore

PostgreSQL๊ณผ SQLite์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ–‰์„ ์‚ฝ์ž…ํ•˜๊ณ  ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

await db.insert(users).values({ name: "Dan" }).returning();

// partial return
await db.insert(users).values({ name: "Partial Dan" }).returning({ insertedId: users.id });

Insert $returningId

PostgreSQL
SQLite
MySQL
SingleStore

MySQL ์ž์ฒด๋Š” INSERT ์‚ฌ์šฉ ํ›„ RETURNING์— ๋Œ€ํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ์ง€์›์ด ์—†์Šต๋‹ˆ๋‹ค. autoincrement(๋˜๋Š” serial) ํƒ€์ž…์„ ๊ฐ€์ง„ primary keys์˜ ๊ฒฝ์šฐ insertId์™€ affectedRows ํ•„๋“œ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. Drizzle๋กœ ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‚ฝ์ž…๋œ ๋ชจ๋“  ID๋ฅผ ๋ณ„๋„์˜ ๊ฐ์ฒด๋กœ ์ž๋™์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์ž๋™ ๋ฐฉ๋ฒ•์„ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค

import { boolean, int, text, mysqlTable } from 'drizzle-orm/mysql-core';

const usersTable = mysqlTable('users', {
  id: int('id').primaryKey(),
  name: text('name').notNull(),
  verified: boolean('verified').notNull().default(false),
});


const result = await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]).$returningId();
//    ^? { id: number }[]

๋˜ํ•œ Drizzle์„ ์‚ฌ์šฉํ•˜๋ฉด ๋Ÿฐํƒ€์ž„์— ์‚ฌ์šฉ์ž ์ •์˜ ๊ธฐ๋ณธ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” $default ํ•จ์ˆ˜๋กœ primary key๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. $returningId() ํ˜ธ์ถœ์—์„œ ์ƒ์„ฑ๋œ ํ‚ค๋„ ๋ฐ˜ํ™˜ํ•ด๋“œ๋ฆฝ๋‹ˆ๋‹ค

import { varchar, text, mysqlTable } from 'drizzle-orm/mysql-core';
import { createId } from '@paralleldrive/cuid2';

const usersTableDefFn = mysqlTable('users_default_fn', {
  customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(createId),
  name: text('name').notNull(),
});


const result = await db.insert(usersTableDefFn).values([{ name: 'John' }, { name: 'John1' }]).$returningId();
//  ^? { customId: string }[]

๊ธฐ๋ณธ ํ‚ค๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ -> ์ด๋Ÿฌํ•œ ์ฟผ๋ฆฌ์˜ ํƒ€์ž…์€ {}[]์ด ๋ฉ๋‹ˆ๋‹ค

Insert multiple rows

await db.insert(users).values([{ name: 'Andrew' }, { name: 'Dan' }]);

Upserts and conflicts

Drizzle ORM์€ upsert์™€ ์ถฉ๋Œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

On conflict do nothing

PostgreSQL
SQLite
MySQL
SingleStore

onConflictDoNothing์€ ์ถฉ๋Œ์ด ์žˆ์„ ๊ฒฝ์šฐ ์‚ฝ์ž…์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค:

await db.insert(users)
  .values({ id: 1, name: 'John' })
  .onConflictDoNothing();

// explicitly specify conflict target
await db.insert(users)
  .values({ id: 1, name: 'John' })
  .onConflictDoNothing({ target: users.id });

On conflict do update

PostgreSQL
SQLite
MySQL

onConflictDoUpdate๋Š” ์ถฉ๋Œ์ด ์žˆ์„ ๊ฒฝ์šฐ ํ–‰์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค:

await db.insert(users)
  .values({ id: 1, name: 'Dan' })
  .onConflictDoUpdate({ target: users.id, set: { name: 'John' } });

where ์ ˆ

on conflict do update๋Š” ๋‘ ๊ฐ€์ง€ ๋‹ค๋ฅธ ์œ„์น˜์— where ์ ˆ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค - ์ถฉ๋Œ ๋Œ€์ƒ์˜ ์ผ๋ถ€(์˜ˆ: ๋ถ€๋ถ„ ์ธ๋ฑ์Šค์šฉ) ๋˜๋Š” update ์ ˆ์˜ ์ผ๋ถ€๋กœ:

insert into employees (employee_id, name)
values (123, 'John Doe')
on conflict (employee_id) where name <> 'John Doe'
do update set name = excluded.name

insert into employees (employee_id, name)
values (123, 'John Doe')
on conflict (employee_id) do update set name = excluded.name
where name <> 'John Doe';

Drizzle์—์„œ ์ด๋Ÿฌํ•œ ์กฐ๊ฑด์„ ์ง€์ •ํ•˜๋ ค๋ฉด setWhere์™€ targetWhere ์ ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

await db.insert(employees)
  .values({ employeeId: 123, name: 'John Doe' })
  .onConflictDoUpdate({
    target: employees.employeeId,
    targetWhere: sql`name <> 'John Doe'`,
    set: { name: sql`excluded.name` }
  });

await db.insert(employees)
  .values({ employeeId: 123, name: 'John Doe' })
  .onConflictDoUpdate({
    target: employees.employeeId,
    set: { name: 'John Doe' },
    setWhere: sql`name <> 'John Doe'`
  });

๋ณตํ•ฉ ์ธ๋ฑ์Šค ๋˜๋Š” onConflictDoUpdate๋ฅผ ์œ„ํ•œ ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•œ Upsert:

await db.insert(users)
  .values({ firstName: 'John', lastName: 'Doe' })
  .onConflictDoUpdate({
    target: [users.firstName, users.lastName],
    set: { firstName: 'John1' }
  });

On duplicate key update

PostgreSQL
SQLite
MySQL
SingleStore

MySQL์€ ON CONFLICT ์ ˆ ๋Œ€์‹  ON DUPLICATE KEY UPDATE๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. MySQL์€ ๊ธฐ๋ณธ ํ‚ค์™€ ๊ณ ์œ  ์ธ๋ฑ์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ถฉ๋Œ ๋Œ€์ƒ์„ ์ž๋™์œผ๋กœ ๊ฒฐ์ •ํ•˜๋ฉฐ, ๋ชจ๋“  ๊ณ ์œ  ์ธ๋ฑ์Šค ์ถฉ๋Œ ์‹œ ํ–‰์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

Drizzle์€ onDuplicateKeyUpdate ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:

// Note that MySQL automatically determines targets based on the primary key and unique indexes
await db.insert(users)
  .values({ id: 1, name: 'John' })
  .onDuplicateKeyUpdate({ set: { name: 'John' } });

MySQL์€ ์ถฉ๋Œ ์‹œ ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์ง์ ‘ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ, ์ž„์˜์˜ ์ปฌ๋Ÿผ ๊ฐ’์„ ์ž์‹ ์œผ๋กœ ์„ค์ •ํ•˜์—ฌ no-op์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { sql } from 'drizzle-orm';

await db.insert(users)
  .values({ id: 1, name: 'John' })
  .onDuplicateKeyUpdate({ set: { id: sql`id` } });

with insert ์ ˆ

select, update, delete์™€ ํ•จ๊ป˜ WITH ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์„ธ์š”

with ์ ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ๊ณตํ†ต ํ…Œ์ด๋ธ” ํ‘œํ˜„์‹(CTE)์ด๋ผ๊ณ  ํ•˜๋Š” ๋” ์ž‘์€ ํ•˜์œ„ ์ฟผ๋ฆฌ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const userCount = db.$with('user_count').as(
	db.select({ value: sql`count(*)`.as('value') }).from(users)
);

const result = await db.with(userCount)
	.insert(users)
	.values([
		{ username: 'user1', admin: sql`((select * from ${userCount}) = 0)` }
	])
	.returning({
		admin: users.admin
	});
with "user_count" as (select count(*) as "value" from "users") 
insert into "users" ("username", "admin") 
values ($1, ((select * from "user_count") = 0)) 
returning "admin"

Insert into โ€ฆ select

SQLite ๋ฌธ์„œ์—์„œ ์–ธ๊ธ‰ํ•˜๋“ฏ์ด:

INSERT ๋ฌธ์˜ ๋‘ ๋ฒˆ์งธ ํ˜•์‹์€ VALUES ์ ˆ ๋Œ€์‹  SELECT ๋ฌธ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. SELECT ๋ฌธ ์‹คํ–‰์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ ๋ฐ์ดํ„ฐ ํ–‰์— ๋Œ€ํ•ด ์ƒˆ ํ•ญ๋ชฉ์ด ํ…Œ์ด๋ธ”์— ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค. column-list๊ฐ€ ์ง€์ •๋œ ๊ฒฝ์šฐ, SELECT ๊ฒฐ๊ณผ์˜ ์ปฌ๋Ÿผ ์ˆ˜๋Š” column-list์˜ ํ•ญ๋ชฉ ์ˆ˜์™€ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด, column-list๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ SELECT ๊ฒฐ๊ณผ์˜ ์ปฌ๋Ÿผ ์ˆ˜๋Š” ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ์ˆ˜์™€ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณตํ•ฉ SELECT ๋ฐ ORDER BY ๋ฐ/๋˜๋Š” LIMIT ์ ˆ์ด ์žˆ๋Š” SELECT ๋ฌธ์„ ํฌํ•จํ•œ ๋ชจ๋“  SELECT ๋ฌธ์„ ์ด ํ˜•์‹์˜ INSERT ๋ฌธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

IMPORTANT

ํŒŒ์‹ฑ ๋ชจํ˜ธ์„ฑ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด, upsert ์ ˆ์ด ์žˆ๋Š” ๊ฒฝ์šฐ SELECT ๋ฌธ์€ ํ•ญ์ƒ WHERE ์ ˆ์„ ํฌํ•จํ•ด์•ผ ํ•˜๋ฉฐ, ๋‹จ์ˆœํžˆ โ€œWHERE trueโ€์ธ ๊ฒฝ์šฐ์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. WHERE ์ ˆ์ด ์—†์œผ๋ฉด ํŒŒ์„œ๋Š” ํ† ํฐ โ€œONโ€์ด SELECT์˜ ์กฐ์ธ ์ œ์•ฝ ์กฐ๊ฑด์˜ ์ผ๋ถ€์ธ์ง€ upsert ์ ˆ์˜ ์‹œ์ž‘์ธ์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

PostgreSQL ๋ฌธ์„œ์—์„œ ์–ธ๊ธ‰ํ•˜๋“ฏ์ด:

์‚ฝ์ž…ํ•  ํ–‰์„ ์ œ๊ณตํ•˜๋Š” ์ฟผ๋ฆฌ(SELECT ๋ฌธ)

๊ทธ๋ฆฌ๊ณ  MySQL ๋ฌธ์„œ์—์„œ ์–ธ๊ธ‰ํ•˜๋“ฏ์ด:

INSERT โ€ฆ SELECT๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•˜๋‚˜ ๋˜๋Š” ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์—์„œ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” SELECT ๋ฌธ์˜ ๊ฒฐ๊ณผ๋กœ๋ถ€ํ„ฐ ํ…Œ์ด๋ธ”์— ๋งŽ์€ ํ–‰์„ ๋น ๋ฅด๊ฒŒ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

Drizzle์€ ๋ชจ๋“  dialect์— ๋Œ€ํ•œ ํ˜„์žฌ ๊ตฌ๋ฌธ์„ ์ง€์›ํ•˜๋ฉฐ, ๋ชจ๋‘ ๋™์ผํ•œ ๊ตฌ๋ฌธ์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ๋ช‡ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์‹œ๋‚˜๋ฆฌ์˜ค์™€ API ์‚ฌ์šฉ๋ฒ•์„ ๊ฒ€ํ† ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. insert ๋ฌธ ๋‚ด์—์„œ select๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์–ด ์„ ํ˜ธํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Query Builder
Callback
SQL template tag
const insertedEmployees = await db
  .insert(employees)
  .select(
    db.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
  )
  .returning({
    id: employees.id,
    name: employees.name
  });
const qb = new QueryBuilder();
await db.insert(employees).select(
    qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);