2013-07-22 4 views
8

Per esempio, ho le seguenti definizioni di tabella:Come posso eseguire query aggregate in Slick?

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 
} 

e voglio selezionare il più grande spazio per ogni casa.

mi si avvicinò con il seguente trucco:

val query = { 
    (r1, r2) <- Rooms leftJoin Rooms on ((r1,r2) => 
    r1.houseId === r2.houseId && r1.size > r2.size 
) 
    if r2.id.isNull 
} yield r1 

Si fa quello che mi serve, ma è brutto, totalmente illeggibile, e sembra compromettere le prestazioni. Ho provato a usare groupBy su query, ma sembra che io stia fraintendendo un concetto fondamentale - non riesco a capire bene i tipi.

Esiste un modo migliore per eseguire tale query aggregata in Slick?

risposta

8

In primo luogo, questo tipo di query non è completamente semplice in SQL semplice. groupBy Slick si traduce in SQL GROUP BY, alla fine, in modo da usarlo abbiamo bisogno di una query SQL con GROUP BY

Una tale domanda potrebbe sembrare

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 

Questo può ora essere tradotto in chiazza di petrolio

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 

Tuttavia, ho riscontrato problemi con le query aggregate utilizzate in altre query nella versione corrente di slick.

Ma la query può essere scritta senza un gruppo utilizzando ESISTE

SELECT r.* FROM Rooms r 
    WHERE NOT EXISTS (
    SELECT r2.id FROM Rooms r2 WHERE r2.size > r.size and r2.houseId = r.houseId) 

Questo può ancora una volta essere tradotto in chiazza di petrolio

val query = for { 
    r <- Rooms 
    if !Query(Rooms).filter(_.houseId === r.houseId).filter(_.size > r.size).exists 
} yield r 

Un'altra opzione sarebbe probabilmente uso di window functions, ma posso' Ti posso davvero aiutare con quelli e non penso che lo slick possa lavorare con loro.

(Nota che non ho uno scalatore a portata di mano quindi potrebbero esserci errori nel codice)

+0

Grazie! La versione con 'exists' è infatti molto più veloce e percorre 30x in meno di righe. – Rogach