PrismaでのInner Joinのやりかた

2020/09/17に公開
2

いま個人的に 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

Discussion

rithmetyrithmety

これは常用する形式だと思うのですが、どうやって解決するんでしょうね?

db.lesson.findMany({
	select: {
		title: true,
		themeId: true,
		theme: {
			select: {
				name: true,
			},
		},
	},
	where: { id: 4 },
})