Drizzle ORMのリレーションでテーブルを結合して取得した話
Drizzle ORMの relations を使うと、テーブル間の関係を定義してシンプルなコードで結合データを取得できます。posts と comments を例に実装しました。
スキーマにリレーションを定義する
// src/lib/db/schema.ts
import { int, text, sqliteTable } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
export const posts = sqliteTable('posts', {
id: int('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
body: text('body').notNull(),
});
export const comments = sqliteTable('comments', {
id: int('id').primaryKey({ autoIncrement: true }),
postId: int('post_id').notNull(),
body: text('body').notNull(),
createdAt: int('created_at').notNull(),
});
// postsは複数のcommentsを持つ
export const postsRelations = relations(posts, ({ many }) => ({
comments: many(comments),
}));
// commentsはひとつのpostに属する
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId], // comments側の外部キー
references: [posts.id], // posts側の参照先
}),
}));
many と one の使い分け
| 意味 | 今回の例 | |
|---|---|---|
many |
1対多の「多」側 | 1つの post は複数の comments を持つ |
one |
多対1の「1」側 | 1つの comment は1つの post に属する |
fields と references
one(posts, {
fields: [comments.postId], // comments側の外部キー
references: [posts.id], // posts側の参照先
})
SQLで書くと JOIN posts ON comments.post_id = posts.id と同じ意味です。
Query APIでリレーションを使う
全件取得(with)
const db = createDb(platform!.env.DB);
const posts = await db.query.posts.findMany({
with: { comments: true },
});
// → [{ id: 1, title: '...', comments: [{ id: 1, body: '...' }, ...] }, ...]
findMany と findFirst
| 意味 | |
|---|---|
findMany |
条件に合う全件取得(配列を返す) |
findFirst |
条件に合う最初の1件取得 |
条件付きクエリ
// 特定のpostとそのcomments
const post = await db.query.posts.findFirst({
where: (posts, { eq }) => eq(posts.id, 1),
with: { comments: true },
});
// commentsに条件・並び替えを指定
const postsWithComments = await db.query.posts.findMany({
with: {
comments: {
where: (comments, { eq }) => eq(comments.postId, 1),
orderBy: (comments, { desc }) => [desc(comments.createdAt)],
},
},
});
where で使えるヘルパー関数
| ヘルパー | SQLの対応 |
|---|---|
eq(col, val) |
col = val |
ne(col, val) |
col != val |
gt(col, val) |
col > val |
lt(col, val) |
col < val |
like(col, val) |
col LIKE val |
and(...条件) |
条件 AND 条件 |
or(...条件) |
条件 OR 条件 |
SvelteKitへの組み込み例
// src/routes/posts-with-comments/+page.server.ts
import type { PageServerLoad } from './$types';
import { createDb } from '$lib/db';
export const load: PageServerLoad = async ({ platform }) => {
const db = createDb(platform!.env.DB);
const posts = await db.query.posts.findMany({
with: { comments: true },
});
return { posts };
};
<!-- src/routes/posts-with-comments/+page.svelte -->
<script lang="ts">
let { data } = $props();
</script>
{#each data.posts as post}
<h2>{post.title}</h2>
<p>{post.body}</p>
<h3>コメント({post.comments.length}件)</h3>
<ul>
{#each post.comments as comment}
<li>{comment.body}</li>
{/each}
</ul>
{/each}
内部で発行されるSQL
Drizzleはリレーションを取得するとき、JOINではなく2クエリ方式を使います:
-- ① postsを全件取得
SELECT * FROM posts;
-- ② 取得したpost_idに対応するcommentsを取得
SELECT * FROM comments WHERE post_id IN (1, 2, 3, ...);
N+1問題を防ぎつつ、JOINより扱いやすい形でデータを返します。
Query API と select() の使い分け
// Query API(リレーションが使いやすい)
const posts = await db.query.posts.findMany({
with: { comments: true },
});
// select API(SQLに近い書き方)
const posts = await db.select().from(posts);
| Query API | select API | |
|---|---|---|
| リレーション | with で簡単に取得 |
手動でJOINが必要 |
| 書き方 | 宣言的・直感的 | SQLに近い |
| 向いている場面 | リレーションを含む複雑なデータ取得 | シンプルなクエリ |
まとめ
relations()でテーブル間の関係を定義する(many/one)fieldsとreferencesで外部キーと参照先を指定する- Query APIの
findMany/findFirst+withでリレーションを含むデータを取得できる - 内部では2クエリ方式でN+1問題を防いでいる