PrismaでのInner Joinのやりかた

公開:2020/09/17
更新:2020/09/17
7 min読了の目安(約4300字TECH技術記事

いま個人的にBlitzjsが推しでして、そのなかで使用しているORMのPrismaについての情報が少なかったのでまとめました。

What's Prisma?

  • Blitzで使用されているNodeのORM
  • scheme.prismaという独自のファイルにモデル定義を行い、この定義情報を元にmigrateを実行する

Prisma - Database tools for modern application development

Q. scheme.prismaのさわり心地はどうですか?

A. prismaプラグインをIDEに入れてからは快適です。
(ファイルとスニペット識別がされます。されないと書きにくかった)

table間のrelationshipも割と直感的にかけます。

model Theme {
	id                 Int       @default(autoincrement()) @id
	createdAt          DateTime  @default(now())
	updatedAt          DateTime  @updatedAt
	name               String?
	lesson             Lesson[]
}

model Lesson {
	id                 Int       @default(autoincrement()) @id
	createdAt          DateTime  @default(now())
	updatedAt          DateTime  @updatedAt
	title              String?
	description        String?
	theme              Theme?    @relation(fields: [themeId], references: [id])
	themeId            Int?
	words              Int      @default(0)
	read_time          Int?
}

Schema定義(1:N)

例えば ThemeLesson1:Nの関係性なので、
Theme.lessonLesson[]として1:Nを表現します。
Lesson側はThemeモデルを扱うプロパティ(ここではtheme)を設定します。
これによりlesson.themeThemeのモデル情報を参照できます。
実際のRDSに保存されるカラムは@relationfieldsに定義できます。ここではthemeIdとなります。

model Theme {
	id                 Int       @default(autoincrement()) @id
	lesson             Lesson[]
}

model Lesson {
	id                 Int       @default(autoincrement()) @id
	theme              Theme?    @relation(fields: [themeId], references: [id])
	themeId            Int?
}

このように、

  1. メインモデルに次を設定(model Lesson
    1. サブモデルを扱うプロパティ情報(theme
    2. 外部キーを扱うプロパティ(themeId
  2. サブモデルに次を定義(model Theme
    1. メインモデルを扱うプロパティ情報(lesson

の作業を行えば定義完了です。

Select Inner Join

さて、Schemaを使用してinner joinしましょう。

せっかくなので blitz consoleで試してみます。

単純にwhereだけ指定してfindManyするとselect *されますが、@relationを設定したモデルは取得しません。

⚡️ > db.lesson.findMany({ where: { id: 4 } }).then(console.log)
Promise { <pending> }
⚡️ > [
  {
    id: 4,
    createdAt: 2020-09-17T15:28:26.000Z,
    updatedAt: 1970-01-01T00:00:00.000Z,
    title: '最高のレッスン!',
    description: 'どうだ!',
    themeId: 1,
    words: 61,
    read_time: 30
  }
]

joinして取得するにはincludeを使用します。
joinしたテーブルカラムのselect *をしたいだけなら、include: { theme: true }を指定すればよいです。

⚡️ > db.lesson.findMany({ where: { id: 4 }, include: { theme: true } }).then(console.log)
Promise { <pending> }
⚡️ > [
  {
    id: 4,
    createdAt: 2020-09-17T15:28:26.000Z,
    updatedAt: 1970-01-01T00:00:00.000Z,
    title: '最高のレッスン!',
    description: 'どうだ!',
    themeId: 1,
    words: 61,
    read_time: 30,
    theme: {
      id: 1,
      createdAt: 2020-09-16T08:16:33.566Z,
      updatedAt: 1970-01-01T00:00:00.000Z,
      name: 'GENERAL'
    }
  }
]

とはいえテーブル情報をすべて返却してしまうと、とあるサービスで騒ぎになったようなセキュリティインシデントも発生しますので、できるだけカラム指定しましょう。
カラム指定にはselectで指定します。
カラムごとにtrueを指定する形式でselectできます。select: { name: true }
(割とSQLの感覚と近いですね。)

⚡️ > db.lesson.findMany({ where: { id: 4 }, include: { theme: {select:{name:true}}} }).then(console.log)
Promise { <pending> }
⚡️ > [
  {
    id: 4,
    createdAt: 2020-09-17T15:28:26.000Z,
    updatedAt: 1970-01-01T00:00:00.000Z,
    title: '最高のレッスン!',
    description: 'どうだ!',
    themeId: 1,
    words: 61,
    read_time: 30,
    theme: { name: 'GENERAL' }
  }
]

ちなみに、メインモデルに対するselectとサブモデルへのincludeを同時に宣言するとエラーになります。

⚡️ > db.lesson.findMany({ select: { title: true, themeId: true } ,where: { id: 4 }, include: { theme: {select:{name:true}}} }).then(console.log)
Uncaught PrismaClientValidationError:
Invalid `prisma.lesson.findMany()` invocation in
repl:1:11

{
  select: {
  ~~~~~~
    title: true,
    themeId: true
  },
  where: {
    id: 4
  },
  include: {
  ~~~~~~~
    theme: {
      select: {
        name: true
      }
    }
  }
}

Please either use `include` or `select`, but not both at the same time.

これは常用する形式だと思うのですが、どうやって解決するんでしょうね?
ここのソリューションが見つかりましたらまた記事を投稿させていただきます。

以上、Prismaでinner joinの方法でした!

Refs