Tuple hell with many Slick joins [closed]

2019-03-14 06:06发布

问题:

Many Slick joins:

for {
  ((((a, b), c), d), e) <-
    qa.filter(_.m == x) join
    qb on (_.m === _.id) join
    qc on (_._1.u === _.uid) join
    qd.filter(_.rid == uid) on (_._1._1.u === _.aid) joinLeft
    qe on (_._1._1._1.u === _.uid)
} yield (c, d, e)

a couple of problems:

  1. the _._._._1 that keeps growing the more joins there are;
  2. the ((((a, b), c), d), e) making it difficult to work out which one corresponds to which join

Question:

How to make it better? Does the code smell to you? Really great if yes - what patterns should I use instead?

回答1:

You could write for example something like this:

val query1 = (for {
  a <- qa filter(_.m === x)
  b <- qb if a.m === b.id
  c <- qc if a.u === c.uid
  d <- qd filter(_.rid === uid) if (a.u === d.aid)
} yield (a, c, d))

val query2 = (for {
  ((a, c, d), e) <- query1 joinLeft qe on (_._1.u === _.uid)
} yield (c, d, e))

val results: Future[Seq[(C, D, Option[E])]] = db.run(query2.result)

In query1 we use implicit inner joins to join over the four tables a, b, c and d. Then we can use this query and combine it with another one. In your case a left outer join on table e, which leads to query2. Finally you execute the resulting query2 on your database. This way you can combine as many queries as you want.

PS: I probably mixed up the join conditions in query1. The principle keeps the same.

PPS: In query1 we use monadic joins. The same result can be achieved using applicative joins. IMHO monadic joins are preferable, since the readability is better. Also it saves you a few characters. See http://slick.typesafe.com/doc/3.0.0/queries.html for more information about joining and querying.

To catch up the question from the comments:

As I mentioned before, you can combine as many queries you want. The scenario you described could look like this:

val query1 = (for {
  a <- qa filter(_.m === x)
  b <- qb if a.m === b.id
  c <- qc if a.u === c.uid
  d <- qd filter(_.rid === uid) if (a.u === d.aid)
} yield (a, b, c, d))

val query2 = (for {
  ((a, b, c, d), e) <- query1 joinLeft qe on (_._1.u === _.uid)
} yield (a, b, c, d, e))

val query3 = (for {
  (a, b, c, d, e) <- query2
  f <- qf if //... condition
  g <- qg if //... condition
} yield (a, b, c, d, e, f, g))

val query4 = (for {
  ((a, b, c, d, e, f, g), i) <- query3 joinLeft qe on (//... condition)
} yield (a, b, c, d, e, f, g, i))

AFAIK this is the only method combining inner and outer joins, as there is no such a thing as a monadic outer join. Applicative joins require to have a left and a right side. E.g queryLeft joinLeft queryRight on .... If queryLeft depends on other joins, you must write that query in the first place.