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:
- the
_._._._1
that keeps growing the more joins there are;
- 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?
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.