drizzle import 경로는 사용하는 **데이터베이스 드라이버**에 따라 달라집니다.
Drizzle 쿼리
Drizzle ORM은 SQL 위에 얇은 타입 레이어로 설계되었습니다. 우리는 TypeScript에서 SQL 데이터베이스를 운영하는 가장 좋은 방법을 설계했다고 확신하며, 이제 더 나아질 때입니다.
관계형 쿼리는 SQL 데이터베이스에서 중첩된 관계형 데이터를 쿼리할 때 여러 조인과 복잡한 데이터 매핑을 피하면서 훌륭한 개발자 경험을 제공하기 위한 것입니다.
이것은 기존 스키마 정의와 쿼리 빌더의 확장입니다. 필요에 따라 선택적으로 사용할 수 있습니다. 우리는 최고 수준의 개발자 경험과 성능을 모두 제공할 수 있도록 했습니다.
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ schema });
const result = await db._query.users.findMany({
with: {
posts: true
},
});[{
id: 10,
name: "Dan",
posts: [
{
id: 1,
content: "SQL is awesome",
authorId: 10,
},
{
id: 2,
content: "But check relational queries",
authorId: 10,
}
]
}]⚠️ SQL 스키마가 여러 파일에 선언되어 있다면 다음과 같이 할 수 있습니다
import * as schema1 from './schema1';
import * as schema2 from './schema2';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ schema: { ...schema1, ...schema2 } });
const result = await db._query.users.findMany({
with: {
posts: true
},
});모드
Drizzle 관계형 쿼리는 데이터베이스에서 실행할 정확히 하나의 SQL 문을 항상 생성하며, 특정한 주의사항이 있습니다.
모든 데이터베이스에 대한 최고 수준의 지원을 제공하기 위해 **모드**를 도입했습니다.
Drizzle 관계형 쿼리는 내부적으로 서브쿼리의 lateral 조인을 사용하며, 현재 PlanetScale은 이를 지원하지 않습니다.
일반 MySQL 데이터베이스에서 mysql2 드라이버를 사용할 때 — mode: "default"를 지정해야 합니다
PlanetScale에서 mysql2 드라이버를 사용할 때 — mode: "planetscale"을 지정해야 합니다
import * as schema from './schema';
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";
const connection = await mysql.createConnection({
uri: process.env.PLANETSCALE_DATABASE_URL,
});
const db = drizzle({ client: connection, schema, mode: 'planetscale' });쿼리
관계형 쿼리는 Drizzle의 기존 **쿼리 빌더**의 확장입니다.
drizzle() 초기화 시 스키마 파일에서 모든 tables와 relations를 제공해야 하며,
그런 다음 db._query API를 사용하면 됩니다.
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ schema });
await db._query.users.findMany(...);// 여러 파일에 스키마가 있는 경우
import * as schema1 from './schema1';
import * as schema2 from './schema2';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ schema: { ...schema1, ...schema2 } });
await db._query.users.findMany(...);Drizzle은 .findMany()와 .findFirst() API를 제공합니다.
여러 개 찾기
const users = await db._query.users.findMany();// 결과 타입
const result: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
}[];첫 번째 찾기
.findFirst()는 쿼리에 limit 1을 추가합니다.
const user = await db._query.users.findFirst();// 결과 타입
const result: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
};관계 포함
With 연산자를 사용하면 여러 관련 테이블의 데이터를 결합하고 결과를 적절히 집계할 수 있습니다.
모든 게시글과 댓글 가져오기:
const posts = await db._query.posts.findMany({
with: {
comments: true,
},
});첫 번째 게시글과 댓글 가져오기:
const post = await db._query.posts.findFirst({
with: {
comments: true,
},
});필요한 만큼 중첩된 with 문을 체이닝할 수 있습니다.
중첩된 with 쿼리의 경우 Drizzle은 Core Type API를 사용하여 타입을 추론합니다.
모든 사용자와 게시글 가져오기. 각 게시글은 댓글 목록을 포함해야 합니다:
const users = await db._query.users.findMany({
with: {
posts: {
with: {
comments: true,
},
},
},
});부분 필드 선택
columns 매개변수를 사용하면 데이터베이스에서 가져올 컬럼을 포함하거나 제외할 수 있습니다.
Drizzle은 쿼리 수준에서 부분 선택을 수행하므로 데이터베이스에서 추가 데이터가 전송되지 않습니다.
Drizzle은 단일 SQL 문을 출력한다는 점을 명심하세요.
id, content만 포함하고 comments를 포함하여 모든 게시글 가져오기:
const posts = await db._query.posts.findMany({
columns: {
id: true,
content: true,
},
with: {
comments: true,
}
});content 없이 모든 게시글 가져오기:
const posts = await db._query.posts.findMany({
columns: {
content: false,
},
});true와 false 선택 옵션이 모두 있으면 모든 false 옵션은 무시됩니다.
name 필드를 포함하고 id 필드를 제외하면, id 제외는 중복됩니다.
name을 제외한 모든 필드는 어차피 제외됩니다.
동일한 쿼리에서 필드 제외 및 포함:
const users = await db._query.users.findMany({
columns: {
name: true,
id: false //ignored
},
});// 결과 타입
const users: {
name: string;
};중첩된 관계의 컬럼만 포함:
const res = await db._query.users.findMany({
columns: {},
with: {
posts: true
}
});// 결과 타입
const res: {
posts: {
id: number,
text: string
}
}[];중첩 부분 필드 선택
**부분 선택**과 마찬가지로, 중첩된 관계의 컬럼을 포함하거나 제외할 수 있습니다:
const posts = await db._query.posts.findMany({
columns: {
id: true,
content: true,
},
with: {
comments: {
columns: {
authorId: false
}
}
}
});필터 선택
SQL과 유사한 쿼리 빌더와 마찬가지로,
관계형 쿼리 API를 사용하면 연산자 목록으로 필터와 조건을 정의할 수 있습니다.
drizzle-orm에서 import하거나 콜백 구문에서 사용할 수 있습니다:
import { eq } from 'drizzle-orm';
const users = await db._query.users.findMany({
where: eq(users.id, 1)
})const users = await db._query.users.findMany({
where: (users, { eq }) => eq(users.id, 1),
})id=1인 게시글과 특정 날짜 이전에 생성된 댓글 찾기:
await db._query.posts.findMany({
where: (posts, { eq }) => (eq(posts.id, 1)),
with: {
comments: {
where: (comments, { lt }) => lt(comments.createdAt, new Date()),
},
},
});Limit & Offset
Drizzle ORM은 쿼리와 중첩된 엔티티에 대한 limit와 offset API를 제공합니다.
5개의 게시글 찾기:
await db._query.posts.findMany({
limit: 5,
});게시글 찾기 및 댓글 최대 3개 가져오기:
await db._query.posts.findMany({
with: {
comments: {
limit: 3,
},
},
});offset은 최상위 쿼리에서만 사용할 수 있습니다.
await db._query.posts.findMany({
limit: 5,
offset: 2, // correct ✅
with: {
comments: {
offset: 3, // incorrect ❌
limit: 3,
},
},
});5번째부터 10번째 게시글까지 댓글과 함께 찾기:
await db._query.posts.findMany({
limit: 5,
offset: 5,
with: {
comments: true,
},
});Order By
Drizzle은 관계형 쿼리 빌더에서 정렬을 위한 API를 제공합니다.
동일한 정렬 **core API**를 사용하거나
import 없이 콜백에서 order by 연산자를 사용할 수 있습니다.
import { desc, asc } from 'drizzle-orm';
await db._query.posts.findMany({
orderBy: [asc(posts.id)],
});await db._query.posts.findMany({
orderBy: (posts, { asc }) => [asc(posts.id)],
});asc + desc로 정렬:
await db._query.posts.findMany({
orderBy: (posts, { asc }) => [asc(posts.id)],
with: {
comments: {
orderBy: (comments, { desc }) => [desc(comments.id)],
},
},
});사용자 정의 필드 포함
관계형 쿼리 API를 사용하면 사용자 정의 추가 필드를 추가할 수 있습니다. 데이터를 조회하고 추가 함수를 적용해야 할 때 유용합니다.
현재 extras에서 집계는 지원되지 않습니다. 집계가 필요한 경우 **core 쿼리**를 사용하세요.
import { sql } from 'drizzle-orm';
await db._query.users.findMany({
extras: {
loweredName: sql`lower(${users.name})`.as('lowered_name'),
},
})await db._query.users.findMany({
extras: {
loweredName: (users, { sql }) => sql`lower(${users.name})`.as('lowered_name'),
},
})lowerName이 키로 반환된 객체의 모든 필드에 포함됩니다.
.as("<name_for_column>")을 명시적으로 지정해야 합니다.
모든 사용자를 그룹과 함께 조회하되, fullName 필드를 포함하려면 (firstName과 lastName의 연결), Drizzle 관계형 쿼리 빌더로 다음 쿼리를 사용할 수 있습니다.
const res = await db._query.users.findMany({
extras: {
fullName: sql<string>`concat(${users.name}, " ", ${users.name})`.as('full_name'),
},
with: {
usersToGroups: {
with: {
group: true,
},
},
},
});// 결과 타입
const res: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
fullName: string;
usersToGroups: {
group: {
id: number;
name: string;
description: string | null;
};
}[];
}[];
모든 게시글과 댓글을 조회하고 게시글 내용의 크기와 각 댓글 내용의 크기를 계산하는 추가 필드를 추가하려면:
const res = await db._query.posts.findMany({
extras: (table, { sql }) => ({
contentLength: (sql<number>`length(${table.content})`).as('content_length'),
}),
with: {
comments: {
extras: {
commentSize: sql<number>`length(${comments.content})`.as('comment_size'),
},
},
},
});// 결과 타입
const res: {
id: number;
createdAt: Date;
content: string;
authorId: number | null;
contentLength: number;
comments: {
id: number;
createdAt: Date;
content: string;
creator: number | null;
postId: number | null;
commentSize: number;
}[];
};Prepared 문
Prepared 문은 쿼리 성능을 대폭 향상시키도록 설계되었습니다 — 여기를 참조하세요.
이 섹션에서는 Drizzle 관계형 쿼리 빌더를 사용하여 플레이스홀더를 정의하고 prepared 문을 실행하는 방법을 배울 수 있습니다.
where에서 플레이스홀더
const prepared = db._query.users.findMany({
where: ((users, { eq }) => eq(users.id, placeholder('id'))),
with: {
posts: {
where: ((users, { eq }) => eq(users.id, placeholder('pid'))),
},
},
}).prepare('query_name');
const usersWithPosts = await prepared.execute({ id: 1 });limit에서 플레이스홀더
const prepared = db._query.users.findMany({
with: {
posts: {
limit: placeholder('limit'),
},
},
}).prepare('query_name');
const usersWithPosts = await prepared.execute({ limit: 1 });offset에서 플레이스홀더
const prepared = db._query.users.findMany({
offset: placeholder('offset'),
with: {
posts: true,
},
}).prepare('query_name');
const usersWithPosts = await prepared.execute({ offset: 1 });여러 플레이스홀더
const prepared = db._query.users.findMany({
limit: placeholder('uLimit'),
offset: placeholder('uOffset'),
where: ((users, { eq, or }) => or(eq(users.id, placeholder('id')), eq(users.id, 3))),
with: {
posts: {
where: ((users, { eq }) => eq(users.id, placeholder('pid'))),
limit: placeholder('pLimit'),
},
},
}).prepare('query_name');
const usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 });