For example, I have the following table definitions:
object Houses extends Table[Long]("Houses") {
def id = column[Long]("id")
def * = id
}
object Rooms extends Table[(Long, Long)]("Rooms") {
def id = column[Long]("id")
def houseId = column[Long]("houseId")
def size = column[Int]("size")
def * = id ~ houseId ~ size
}
And I want to select the biggest room for each house.
I came up with the following trick:
val query = {
(r1, r2) <- Rooms leftJoin Rooms on ((r1,r2) =>
r1.houseId === r2.houseId && r1.size > r2.size
)
if r2.id.isNull
} yield r1
It does what I need, but is ugly, totally unreadable, and seems to hurt performance. I tried to use groupBy
on query, but seems I am misunderstanding some core concept - I can't get the types right.
Is there a better way to do such aggregate query in Slick?
An aggregate query is a method of deriving group and subgroup data by analysis of a set of individual data entries. The term is frequently used by database developers and database administrators.
You can sum a column of numbers in a query by using a type of function called an aggregate function. Aggregate functions perform a calculation on a column of data and return a single value.
Slick is a Functional Relational Mapping library for Scala that allows us to query and access a database like other Scala collections. We can write database queries in Scala instead of SQL, thus providing typesafe queries.
First, this kind of query is not totally simple in plain SQL. Slick groupBy translates to SQL GROUP BY in the end, so to use it we need a SQL query with GROUP BY
One such query could look like
SELECT r2.* FROM
(SELECT r.houseId, MAX(size) as size FROM Rooms r GROUP BY r.houseId) mx
INNER JOIN
Rooms r2 on r2.size = mx.size and r2.houseId = mx.houseId
This can now be translated to slick
val innerQuery = Query(Rooms).groupBy(_.houseId).map {
case (houseId, rows) => (houseId, rows.map(_.size).max)
}
val query = for {
(hid, max) <- innerQuery
r <- Rooms if r.houseId === hid && r.size === max
} yield r
However I had problems with aggregated queries used in other queries in current version of slick.
But the query can be written without a GROUP BY using EXISTS
SELECT r.* FROM Rooms r
WHERE NOT EXISTS (
SELECT r2.id FROM Rooms r2 WHERE r2.size > r.size and r2.houseId = r.houseId)
This can again be translated to slick
val query = for {
r <- Rooms
if !Query(Rooms).filter(_.houseId === r.houseId).filter(_.size > r.size).exists
} yield r
Another option would probably be use of window functions, but I can't really help you with those and I don't think slick can work with them.
(Note that I don't have a scala compiler at hand so there may be errors in the code)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With