SQL Insert
SQL Insert
Drizzle ORM은 데이터베이스 테이블에 행을 삽입하는 가장 SQL과 유사한 방법을 제공합니다.
Drizzle로 데이터를 삽입하는 것은 매우 간단하고 SQL과 유사합니다. 직접 확인해보세요:
await db.insert(users).values({ name: 'Andrew' });insert into "users" ("name") values ("Andrew");특정 테이블에 대한 삽입 타입이 필요한 경우 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);returning
PostgreSQL과 SQLite에서는 다음과 같이 행을 삽입하고 결과를 반환받을 수 있습니다:
await db.insert(users).values({ name: "Dan" }).returning();
// partial return
await db.insert(users).values({ name: "Partial Dan" }).returning({ insertedId: users.id });$returningId
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 }[]기본 키가 없는 경우 -> 해당 쿼리의 타입은
{}[]가 됩니다
output
MSSQL에서는 다음과 같이 행을 삽입하고 결과를 반환받을 수 있습니다:
await db.insert(users).values({ name: "Dan" }).output();
// partial return
await db.insert(users).values({ name: "Partial Dan" }).output({ insertedId: users.id });여러 행 삽입
await db.insert(users).values([{ name: 'Andrew' }, { name: 'Dan' }]);Upsert와 충돌 처리
Drizzle ORM은 upsert와 충돌을 처리하기 위한 간단한 인터페이스를 제공합니다.
충돌 시 아무 작업도 하지 않기
onConflictDoNothing은 충돌이 발생하면 삽입을 취소합니다:
await db.insert(users)
.values({ id: 1, name: 'John' })
.onConflictDoNothing();
// 명시적으로 충돌 대상 지정
await db.insert(users)
.values({ id: 1, name: 'John' })
.onConflictDoNothing({ target: users.id });충돌 시 업데이트
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' }
});중복 키 업데이트
MySQL은 ON CONFLICT 절 대신 ON DUPLICATE KEY UPDATE를 지원합니다. MySQL은 기본 키와 고유 인덱스를 기반으로 충돌 대상을 자동으로 결정하며, 어떤 고유 인덱스와 충돌하는 경우 행을 업데이트합니다.
Drizzle은 onDuplicateKeyUpdate 메서드를 통해 이를 지원합니다:
// MySQL은 기본 키와 고유 인덱스를 기반으로 자동으로 대상을 결정합니다
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 절
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 문을 실행하여 반환된 각 데이터 행에 대해 새로운 항목이 테이블에 삽입됩니다. 컬럼 목록이 지정된 경우, SELECT 결과의 컬럼 수는 컬럼 목록의 항목 수와 동일해야 합니다. 그렇지 않으면, 컬럼 목록이 지정되지 않은 경우 SELECT 결과의 컬럼 수는 테이블의 컬럼 수와 동일해야 합니다. ORDER BY 및/또는 LIMIT 절이 있는 복합 SELECT 및 SELECT 문을 포함한 모든 SELECT 문을 이 형태의 INSERT 문에 사용할 수 있습니다.
파싱 모호성을 피하기 위해, upsert 절이 있는 경우 SELECT 문은 단순히 “WHERE true”라도 항상 WHERE 절을 포함해야 합니다. WHERE 절이 없으면 파서는 “ON” 토큰이 SELECT의 조인 제약조건의 일부인지 upsert 절의 시작인지 알 수 없습니다.
PostgreSQL 문서에서 언급한 바와 같이:
삽입할 행을 제공하는 쿼리(SELECT 문)
그리고 MySQL 문서에서 언급한 바와 같이:
INSERT … SELECT를 사용하면 하나 또는 여러 테이블에서 선택할 수 있는 SELECT 문의 결과에서 테이블에 많은 행을 빠르게 삽입할 수 있습니다.
Drizzle은 모든 dialect에 대해 동일한 문법을 지원하며, 모두 동일한 구문을 공유합니다. 일반적인 시나리오와 API 사용법을 살펴보겠습니다. insert 문 내에서 select를 사용하는 여러 가지 방법이 있어 선호하는 방법을 선택할 수 있습니다:
- select 함수 내에 쿼리 빌더를 전달할 수 있습니다.
- 콜백 내에서 쿼리 빌더를 사용할 수 있습니다.
- 원하는 커스텀 select 쿼리와 함께 SQL 템플릿 태그를 전달할 수 있습니다.
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'))
);