【Drizzle ORM】JOINした結果をマッピングして集計する

const rows = await this.db
.select({
user: usersTable,
post: postsTable,
})
.from(usersTable)
.leftJoin(postsTable, eq(usersTable.id, postsTable.userId));

Drizzle ORMで複数テーブルをJOINしSELECTする時。

Expected
[
{
user: {
id: 1,
name: 'user1',
},
post: [
{
id: 1,
title: 'post1',
},
{
id: 2,
title: 'post2',
},
],
},
];
Actual
[
{
user: {
id: 1,
name: 'user1',
},
post: {
id: 1,
title: 'post1',
},
},
{
user: {
id: 1,
name: 'user1',
},
post: {
id: 2,
title: 'post2',
},
},
];

他のORMに慣れているとuserに紐づくpost一覧がマッピングされたレコードが返ってくるのを期待してしまうが、Drizzle ORMはクエリ実行結果のまま返す1ので、これをマッピングしたい。

解決法

type User = InferModel<typeof usersTable>;
type Post = InferModel<typeof postsTable>;
const rows = await this.db
.select({
user: usersTable,
post: postsTable,
})
.from(usersTable)
.leftJoin(postsTable, eq(usersTable.id, postsTable.userId));
const map = rows.reduce<Map<number, { user: User; posts: Post[] }>>(
(acc, { user, post }) => {
if (!acc.has(user.id)) {
acc.set(user.id, { user, posts: [] });
}
if (post) acc.get(user.id)?.posts.push(post);
return acc;
},
new Map(),
);
const result = [...map.values()];

Array.reduce()Mapを用いて自前でマッピングする2のが最もシンプルそう。

参考
  1. Drizzle Queriesを利用した場合を除く

  2. 公式ドキュメントでもほぼ同じ集計方法が紹介されている