RDB で多対多のリレーションを記述する
一つ前の章 で扱ったアプリケーションに、「いいね」機能を実装してみましょう。
仕様はこんな感じです。
- ユーザーは、ポストに「いいね」をつけれる。
- ユーザーは、1 人あたり複数のポストにいいねをつけれる。
- ポストは、 1 個あたり複数のユーザーがいいねをつけれる。
スキーマを定義する
SQL のデータベースに直接多対多 (n to m) のリレーションを記述することはできないため、ひと工夫しましょう。
liked
(Liked
) という「中間テーブル」を作成します。
model User { id Int @id @default(autoincrement()) name String createdPosts Post[] liked Liked[]}
model Post { id Int @id @default(autoincrement()) creatorId Int creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade) content String likedBy Liked[]}
model Liked { likedById Int likedBy User @relation(fields: [likedById], references: [id], onDelete: Cascade) postId Int post Post @relation(fields: [likedById], references: [id], onDelete: Cascade)
@@unique([likedById, postId])}
export const usersTable = sqliteTable("users", { id: integer().primaryKey({ autoIncrement: true }), name: text().notNull(),});
export const postsTable = sqliteTable("posts", { id: integer().primaryKey({ autoIncrement: true }), creatorId: integer("creator_id") .notNull() .references(() => usersTable.id, { onDelete: "cascade" }), content: text().notNull(),});
export const likedTable = sqliteTable("liked", { userId: integer("user_id") .notNull() .references(() => usersTable.id, { onDelete: "cascade" }), postId: integer("post_id") .notNull() .references(() => postsTable.id, { onDelete: "cascade" }),});
export const usersRelations = relations(usersTable, ({ many }) => ({ posts: many(postsTable), liked: many(likedTable),}));
export const postsRelations = relations(postsTable, ({ one, many }) => ({ creator: one(usersTable, { fields: [postsTable.creatorId], references: [usersTable.id], }), likedBy: many(likedTable),}));
export const likedRelations = relations(likedTable, ({ one }) => ({ user: one(usersTable, { fields: [likedTable.userId], references: [usersTable.id], }), post: one(postsTable, { fields: [likedTable.postId], references: [postsTable.id], }),}));
export const usersTable = sqliteTable("users", { id: integer().primaryKey({ autoIncrement: true }), name: text().notNull(),});
export const postsTable = sqliteTable("posts", { id: integer().primaryKey({ autoIncrement: true }), creatorId: integer("creator_id") .notNull() .references(() => usersTable.id, { onDelete: "cascade" }), content: text().notNull(),});
export const likedTable = sqliteTable("liked", { userId: integer("user_id") .notNull() .references(() => usersTable.id, { onDelete: "cascade" }), postId: integer("post_id") .notNull() .references(() => postsTable.id, { onDelete: "cascade" }),});
CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);
CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, creator_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, content TEXT NOT NULL);
CREATE TABLE liked ( user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE, PRIMARY KEY (user_id, post_id));
取得する
では、ユーザーの id
から、「ユーザーがいいねをつけたポスト」を取得してみましょう。
const user = await prisma.user.findUnique({ where: { id: 1 }, include: { liked: { select: { post: true, } }, },});
const user = await db.query.usersTable.findFirst({ where: eq(usersTable.id, 1), with: { liked: { columns: {}, // liked テーブル自体の情報はいらない with: { post: true, }, }, },});
await db .select() .from(postsTable) .where( exists( db .select() .from(likedTable) .where( and( eq(likedTable.userId, 1), eq(likedTable.postId, postsTable.id), ), ), ), );
SELECT * FROM posts WHERE EXISTS ( SELECT * FROM liked WHERE liked.user_id = 1 AND liked.post_id = posts.id);