05. CRUD 조작 (CRUD Operations)

2026년 2월 16일 수정됨

📋 개요

기본적인 조작을 넘어, 실무에서 마주치는 복잡한 데이터 처리 패턴을 다룹니다.

📋 목차

기본적인 조작을 넘어, 실무에서 마주치는 복잡한 데이터 처리 패턴을 다룹니다.


🔍 1. 필터 & 연산자 총정리 (Cheat Sheet)

where 절에서 사용하는 연산자들입니다. import { eq, gt, ... } from 'drizzle-orm' 해서 씁니다.

연산자SQL 대응사용 예시비고
eq=eq(table.id, 1)Equal
ne<>ne(table.status, 'deleted')Not Equal
gt / gte> / >=gt(table.age, 18)Greater Than
lt / lte< / <=lt(table.price, 1000)Less Than
likeLIKElike(table.name, '%Kim%')부분 일치 (대소문자 구분 O)
ilikeILIKEilike(table.name, '%kim%')[PG전용] 대소문자 무시 매칭
inArrayINinArray(table.id, [1, 2, 3])배열에 포함 여부
notInArrayNOT INnotInArray(table.role, ['admin'])
isNullIS NULLisNull(table.deletedAt)
isNotNullIS NOT NULLisNotNull(table.email)
betweenBETWEENbetween(table.age, 20, 30)

논리 연산자 결합

import { and, or, not } from 'drizzle-orm';
 
// (age >= 20 AND status = 'active') OR role = 'admin'
await db.select().from(users).where(
  or(
    and(gte(users.age, 20), eq(users.status, 'active')),
    eq(users.role, 'admin')
  )
);

➕ 2. Insert 심화 (Upsert & Returning)

Upsert (있으면 수정, 없으면 입력)

PostgreSQL의 ON CONFLICT 구문을 활용합니다.

// ID가 겹치면 이름을 업데이트하고, 아니면 새로 만듦
await db.insert(users).values({ id: 1, name: 'New Name' })
  .onConflictDoUpdate({
    target: users.id, // 충돌 감지할 컬럼 (Unique/PK)
    set: { name: 'New Name', updatedAt: new Date() }, // 수정할 내용
  });
 
// ID가 겹치면 그냥 무시 (아무것도 안 함)
await db.insert(users).values({ id: 1, ... })
  .onConflictDoNothing();

Returning (결과 바로 받기)

Insert 뿐만 아니라 Update, Delete에서도 됩니다!

// 삭제된 유저 정보를 반환받음 (로그 남길 때 유용)
const [deletedUser] = await db.delete(users)
  .where(eq(users.id, 1))
  .returning({ 
    deletedId: users.id, 
    deletedEmail: users.email 
  });

✏️ 3. Update 심화 (Atomic Increments)

"조회수 +1" 같은 기능 만들 때, 값을 읽어와서 +1 하고 다시 저장하면 동시성 문제가 생깁니다. DB에서 직접 연산하세요.

import { sql } from 'drizzle-orm';
 
// 조회수 = 현재 조회수 + 1 (Atomic)
await db.update(posts)
  .set({ 
    views: sql`${posts.views} + 1`,
    updatedAt: new Date(),
  })
  .where(eq(posts.id, 5));

🚛 4. 대량 조작 (Batch Operations)

수만 건의 데이터를 처리할 때는 네트워크 왕복(Round Trip)을 줄여야 합니다.

Bulk Insert

const manyUsers = Array(1000).fill({ ... });
await db.insert(users).values(manyUsers); 
// Drizzle이 하나의 SQL로 묶어서 보냅니다. (Limit 있음, 보통 65535 파라미터)

Transaction 활용

여러 작업을 한 번에 묶습니다.

await db.transaction(async (tx) => {
  // 1. 유저 생성
  const [user] = await tx.insert(users).values(...).returning();
  
  // 2. 프로필 생성 (유저 ID 필요)
  await tx.insert(profiles).values({ userId: user.id, ... });
  
  // 3. 환영 이메일 로그
  await tx.insert(logs).values(...);
});

🔗 레퍼런스

다음 장: 06. 고급 쿼리 (Advanced Queries)