2015-08-14 10 views
5

Per semplificare diciamo che ho tre tabelle:Qual è un buon modo per combinare l'impaginazione e il raggruppamento senza query in Slick 3.0?

val postTable = TableQuery[Posts] 
val postTagTable = TableQuery[PostTags] 
val tagTable = TableQuery[Tags] 

Un post può avere più tag e postTagTable contiene solo la relazione.

Ora ho potuto interrogare i post e tag in questo modo:

val query = for { 
    post <- postTable 
    postTag <- postTagTable if post.id === postTag.postId 
    tag <- tagTable if postTag.tagId === tag.id 
} yield (post, tag) 

val postTags = db.run(query.result).map { 
    case result: Seq[(Post,Tag)] => 
     result.groupBy(_._1).map { 
      case (post, postTagSeq) => (post, postTagSeq.map(_._2)) 
     } 
} 

Il che mi un Future[Seq[(Post, Seq(Tag))]] darebbe.

Fin qui tutto bene.

Ma cosa succede se voglio aggiungere l'impaginazione per i post? Poiché uno Post può avere più Tags con la query precedente, non so quante righe a take dalla query, per ottenere, diciamo, 10 Posts.

Qualcuno conosce un buon metodo per ottenere lo stesso risultato con un numero specifico di post in una singola query?

In realtà non sono nemmeno sicuro di come mi avvicinerei a questo in SQL nativo senza query annidate, quindi se qualcuno ha un suggerimento in quella direzione sarei anche lieto di ascoltarlo.

Grazie!

EDIT

Solo così si sa, che tipo di interrogazione Attualmente sto facendo:

val pageQuery = postTable drop(page * pageSize) take(pageSize) 

val query = for { 
    pagePost <- pageQuery 
    post <- postTable if pagePost.id === post.id 
    postTag <- postTagTable if post.id === postTag.postId 
    tag <- tagTable if postTag.tagId === tag.id 
} yield (post, tag) 

val postTags = db.run(query.result).map { 
    case result: Seq[(Post,Tag)] => 
     result.groupBy(_._1).map { 
      case (post, postTagSeq) => (post, postTagSeq.map(_._2)) 
     } 
} 

Ma questo si traduce, ovviamente, in una query nidificate. E questo è quello che vorrei evitare.

EDIT 2

Un'altra soluzione 2-query che sarebbe possibile:

val pageQuery = postTable drop(page * pageSize) map(_.id) take(pageSize) 

db.run(pageQuery.result) flatMap { 
    case ids: Seq[Int] => 
     val query = for { 
      post <- postTable if post.id inSetBind ids 
      postTag <- postTagTable if post.id === postTag.postId 
      tag <- tagTable if postTag.tagId === tag.id 
     } yield (post, tag) 

     val postTags = db.run(query.result).map { 
       case result: Seq[(Post,Tag)] => 
        result.groupBy(_._1).map { 
         case (post, postTagSeq) => (post, postTagSeq.map(_._2)) 
        } 
     } 
} 

Ma questo avrebbe preso due viaggi al database e utilizza l'operatore in, quindi è probabilmente non è buono come la query di join.

Qualche suggerimento?

+0

L'aiuto 'groupBy' di Slick non è in questo caso? Se fai 'groupBy' per Post sulla query e poi 'prendi'? – Ixx

+0

Se eseguo il groupBy sulla query, devo usare la mappa per aggregare tutto ciò su cui non ho raggruppato. Quindi se dovessi raggruppare su Post (sulla query), non potrei ottenere i Tag come Elenco ma solo ad es. contali. – thwiegan

risposta

1

Si può fare in questo modo:

def findPagination(from: Int, to: Int): Future[Seq[(Post, Seq[Tag])]] = { 
    val query:DBIO[Seq[(Album,Seq[Genre])]] = postRepository.findAll(from, to).flatMap{posts=> 
     DBIO.sequence(
     posts.map{ post=> 
      tagRepository.findByPostId(post.id).map(tags=>(post,tags)) 
     } 
    ) 
    } 
    db.run(query) 
    } 

All'interno PostRepository

def findAll(from: Int, limit: Int): DBIO[Seq[Post]] = postTable.drop(from).take(limit).result 

All'interno TagRepository

def findByPostId(id: Int): DBIO[Seq[Tag]] = { 
    val query = for { 
     tag <- tagTable 
     pstTag <- postTagTable if pstTag.postId === id && tag.id === pstTag.tagId 
    } yield tag 
    query.result 
    } 

EDIT

Io non posso farlo senza sottoselezionare in una singola query. La tua soluzione attuale è la migliore.Inoltre è possibile ottimizzare la query rimuovendo inutili "join"

val query = for { 
    pagePost <- pageQuery 
    postTag <- postTagTable if pagePost.id === postTag.postId 
    tag <- tagTable if postTag.tagId === tag.id 
} yield (pagePost, tag) 

E avrai circa prossima SQL (Slick 3.0.1):

SELECT x2.`postname`, 
     x2.`id`, 
     x3.`tagname`, 
     x3.`id` 
FROM 
    (SELECT x4.`postname` AS `postname`, x4.`id` AS `id` 
    FROM `POST` x4 LIMIT 10, 1) x2, 
    `POST_TAG` x5, 
    `TAG` x3 
WHERE (x2.`id` = x5.`postId`) 
    AND (x5.`tagId` = x3.`id`) 

Forse nel tuo caso, anche che è più efficiente per pre-compilare questa query http://slick.typesafe.com/doc/3.0.0/queries.html#compiled-queries

+0

Ma anche se colpisce una sola volta il database, ciò risulterebbe in istruzioni n + 1 (dove n è pageSize), no? – thwiegan

+0

Sì, ottieni n + 1 risultati nel database. 1 hit per ottenere i post "impaginati" e n per ottenere i tag per ogni post –

+0

Okay in modo da evitare la query nidificata, ma fare un paio di roundtrip al database è ancora peggio imo – thwiegan

0

Ho lo stesso problema. Può essere interessante ancora:

 val query = for { 
     ((p, pt), t) <- posts.filter({x => x.userid === userId}).sortBy({x=>x.createdate.desc}).take(count). 
         joinLeft (postsTags).on((x, y)=>x.postid === y.postid). 
         joinLeft (tags).on(_._2.map(_.tagid) === _.tagid) 
    } yield (p.?, t.map(_.?)) 
    //val query = posts filter({x => x.userid === userId}) sortBy({x=>x.createdate.desc}) take(count) 
    try db.run(query result) 
    catch{ 
     case ex:Exception => { 
     log error("ex:", ex) 
     Future[Seq[(Option[PostsRow], Option[Option[TagsRow]])]] { 
      Seq((None, None)) 
     } 
     } 
    } 

poi risultato di questa query:

result onComplete { 
case Success(x) => { 
    x groupBy { x => x._1 } map {x=>(x._1, x._2.map(_._2))} 

} 
case Failure(err) => { 
    log error s"$err" 
} 

}

restituisce sequenza in questo modo: Seq [(Post, Seq [Tag]), (Post , Seq [Tag]) ........]

Problemi correlati