使用 Doobie 映射多对多关系
Mapping a Many-to-Many Releationship Using Doobie
我在 Postgres 中有两个表。第一个包含有关电影的一般信息,而后者包含演员。
CREATE TABLE "MOVIES" (
"ID" uuid NOT NULL,
"TITLE" character varying NOT NULL,
"YEAR" smallint NOT NULL,
"DIRECTOR" character varying NOT NULL
);
CREATE TABLE "ACTORS" (
"ID" serial NOT NULL,
PRIMARY KEY ("ID"),
"NAME" character varying NOT NULL
);
在两者之间,我定义了一个多对多关系:
CREATE TABLE "MOVIES_ACTORS" (
"ID_MOVIES" uuid NOT NULL,
"ID_ACTORS" integer NOT NULL
);
ALTER TABLE "MOVIES_ACTORS"
ADD CONSTRAINT "MOVIES_ACTORS_ID_MOVIES_ID_ACTORS" PRIMARY KEY ("ID_MOVIES", "ID_ACTORS");
ALTER TABLE "MOVIES_ACTORS"
ADD FOREIGN KEY ("ID_MOVIES") REFERENCES "MOVIES" ("ID");
ALTER TABLE "MOVIES_ACTORS"
ADD FOREIGN KEY ("ID_ACTORS") REFERENCES "ACTORS" ("ID");
在 Scala 中,我定义了以下域类型,代表电影:
case class Movie(id: String, title: String, year: Int, actors: List[String], director: String)
如何使用 Doobie 库在 Movie
class 的实例中映射上述三个表之间的连接?
Doobie“只是”一个围绕 JDBC 的包装器,它提供了针对 SQL 注入的安全性。那么,你将如何查询 raw SQL 来获取你想要的数据呢?也许有这样的东西(只是一个例子,我没有检查过):
SELECT m."ID",
m."TITLE",
m."YEAR",
array_agg(a."NAME") as "ACTORS",
m."DIRECTOR"
FROM "MOVIES" m
JOIN "MOVIES_ACTORS" ma ON m."ID" = ma."ID_MOVIES"
JOIN "ACTORS" a ON ma."ID_ACTORS" = a."ID"
GROUP BY (m."ID",
m."TITLE",
m."YEAR",
m."DIRECTOR")
这正是我在 Doobie 中获取它的方法:
// import doobie normal utils
// import postgresql extensions for PG arrays and uuids
sql"""
|SELECT m."ID",
| m."TITLE",
| m."YEAR",
| array_agg(a."NAME") as "ACTORS",
| m."DIRECTOR"
|FROM "MOVIES" m
|JOIN "MOVIES_ACTORS" ma ON m."ID" = ma."ID_MOVIES"
|JOIN "ACTORS" a ON ma."ID_ACTORS" = a."ID"
|GROUP BY (m."ID",
| m."TITLE",
| m."YEAR",
| m."DIRECTOR")
|""".stripMargin
.query[Movies] // requires values to be fetched in the same order as in case class
.to[List]
.transact(transactor)
或者,您可以使用 3 个查询:
(for {
// fetch movies
movies <- sql"""SELECT m."ID",
| m."TITLE",
| m."YEAR",
| m."DIRECTOR"
|FROM movies
|""".stripMargin
.query[UUID, String, String, String]
.to[List]
// fetch joins by movies IDs
pairs <- NonEmptyList.fromList(movies.map(_._1)) match {
// query if there is something to join
case Some(ids) =>
(sql"""SELECT "MOVIES_ID",
| "ACTORS_ID"
|FROM "MOVIES_ACTORS"
|WHERE""".stripMargin ++
Fragments.in(fr""" "MOVIES_ID" """, ids))
.query[(UUID, Int)].to[List]
// avoid query altogether since condition would be empty
case None =>
List.empty[(UUID, Int)].pure[ConnectionIO]
}
// fetch actors by IDs
actors <- NonEmptyList.fromList(pairs.map(_._2)) match {
// query if there is something to join
case Some(ids) =>
(sql"""SELECT "ID",
| "NAME"
|FROM "ACTORS"
|WHERE""".stripMargin ++
Fragments.in(fr""" "ID" """, ids))
.query[(Int, String)].to[List]
// avoid query altogether since condition would be empty
case None =>
List.empty[(Int, String)].pure[ConnectionIO]
}
} yield {
// combine 3 results into 1
movies.map { case (movieId, title, year, director) =>
val actorIds = pairs.collect {
// get actorId if first of the pair is == movieId
case (`movieId`, actorId) => actorId
}.toSet
val movieActors = actors.collect {
// get actor name if id among actors from movie
case (id, name) if actorsIds.contains(id) => name
}
Movie(movieId, title, year, movieActors, director)
}
})
.transact(transactor)
由于它在您的代码中执行 JOIN ON 和 GROUP BY 的逻辑,因此更加冗长(并且可能更需要内存),但它表明您可以将多个查询组合到一个事务中。
我在 Postgres 中有两个表。第一个包含有关电影的一般信息,而后者包含演员。
CREATE TABLE "MOVIES" (
"ID" uuid NOT NULL,
"TITLE" character varying NOT NULL,
"YEAR" smallint NOT NULL,
"DIRECTOR" character varying NOT NULL
);
CREATE TABLE "ACTORS" (
"ID" serial NOT NULL,
PRIMARY KEY ("ID"),
"NAME" character varying NOT NULL
);
在两者之间,我定义了一个多对多关系:
CREATE TABLE "MOVIES_ACTORS" (
"ID_MOVIES" uuid NOT NULL,
"ID_ACTORS" integer NOT NULL
);
ALTER TABLE "MOVIES_ACTORS"
ADD CONSTRAINT "MOVIES_ACTORS_ID_MOVIES_ID_ACTORS" PRIMARY KEY ("ID_MOVIES", "ID_ACTORS");
ALTER TABLE "MOVIES_ACTORS"
ADD FOREIGN KEY ("ID_MOVIES") REFERENCES "MOVIES" ("ID");
ALTER TABLE "MOVIES_ACTORS"
ADD FOREIGN KEY ("ID_ACTORS") REFERENCES "ACTORS" ("ID");
在 Scala 中,我定义了以下域类型,代表电影:
case class Movie(id: String, title: String, year: Int, actors: List[String], director: String)
如何使用 Doobie 库在 Movie
class 的实例中映射上述三个表之间的连接?
Doobie“只是”一个围绕 JDBC 的包装器,它提供了针对 SQL 注入的安全性。那么,你将如何查询 raw SQL 来获取你想要的数据呢?也许有这样的东西(只是一个例子,我没有检查过):
SELECT m."ID",
m."TITLE",
m."YEAR",
array_agg(a."NAME") as "ACTORS",
m."DIRECTOR"
FROM "MOVIES" m
JOIN "MOVIES_ACTORS" ma ON m."ID" = ma."ID_MOVIES"
JOIN "ACTORS" a ON ma."ID_ACTORS" = a."ID"
GROUP BY (m."ID",
m."TITLE",
m."YEAR",
m."DIRECTOR")
这正是我在 Doobie 中获取它的方法:
// import doobie normal utils
// import postgresql extensions for PG arrays and uuids
sql"""
|SELECT m."ID",
| m."TITLE",
| m."YEAR",
| array_agg(a."NAME") as "ACTORS",
| m."DIRECTOR"
|FROM "MOVIES" m
|JOIN "MOVIES_ACTORS" ma ON m."ID" = ma."ID_MOVIES"
|JOIN "ACTORS" a ON ma."ID_ACTORS" = a."ID"
|GROUP BY (m."ID",
| m."TITLE",
| m."YEAR",
| m."DIRECTOR")
|""".stripMargin
.query[Movies] // requires values to be fetched in the same order as in case class
.to[List]
.transact(transactor)
或者,您可以使用 3 个查询:
(for {
// fetch movies
movies <- sql"""SELECT m."ID",
| m."TITLE",
| m."YEAR",
| m."DIRECTOR"
|FROM movies
|""".stripMargin
.query[UUID, String, String, String]
.to[List]
// fetch joins by movies IDs
pairs <- NonEmptyList.fromList(movies.map(_._1)) match {
// query if there is something to join
case Some(ids) =>
(sql"""SELECT "MOVIES_ID",
| "ACTORS_ID"
|FROM "MOVIES_ACTORS"
|WHERE""".stripMargin ++
Fragments.in(fr""" "MOVIES_ID" """, ids))
.query[(UUID, Int)].to[List]
// avoid query altogether since condition would be empty
case None =>
List.empty[(UUID, Int)].pure[ConnectionIO]
}
// fetch actors by IDs
actors <- NonEmptyList.fromList(pairs.map(_._2)) match {
// query if there is something to join
case Some(ids) =>
(sql"""SELECT "ID",
| "NAME"
|FROM "ACTORS"
|WHERE""".stripMargin ++
Fragments.in(fr""" "ID" """, ids))
.query[(Int, String)].to[List]
// avoid query altogether since condition would be empty
case None =>
List.empty[(Int, String)].pure[ConnectionIO]
}
} yield {
// combine 3 results into 1
movies.map { case (movieId, title, year, director) =>
val actorIds = pairs.collect {
// get actorId if first of the pair is == movieId
case (`movieId`, actorId) => actorId
}.toSet
val movieActors = actors.collect {
// get actor name if id among actors from movie
case (id, name) if actorsIds.contains(id) => name
}
Movie(movieId, title, year, movieActors, director)
}
})
.transact(transactor)
由于它在您的代码中执行 JOIN ON 和 GROUP BY 的逻辑,因此更加冗长(并且可能更需要内存),但它表明您可以将多个查询组合到一个事务中。