Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a good way to combine pagination and non query grouping in Slick 3.0?

For simplification let's say I have three tables:

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

One post can have multiple tags and postTagTable just contains the relation.

Now I could query the posts and tags like this:

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))
        }
}

Which would give me a Future[Seq[(Post, Seq(Tag))]].

So far so good.

But what if I want to add pagination for the posts? Since one Post can have multiple Tags with the above query, I don't know how many rows to take from the query, in order to get, let's say, 10 Posts.

Does anyone know a good way of getting the same result with a specific number of Posts in a single query?

I am actually not even sure how I would approach this in native SQL without nested queries, so if someone has a suggestion in that direction I would also be glad to hear it.

Thanks!

EDIT

Just so you know, what kind of query I am currently doing:

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))
        }
}

But this obviously results in a nested query. And this is what I would like to avoid.

EDIT 2

Another 2-query solution that would be possible:

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))
                     }
        }
 }

But this would take two trips to the database and uses the in operator, so it's probably not as good as the join query.

Any suggestions?

like image 441
thwiegan Avatar asked Nov 09 '22 07:11

thwiegan


1 Answers

You can do like this:

  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)
  }

Inside PostRepository

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

Inside 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

I thing you can't do it without subselect in a single query. Your current solution is the best one. Also you can optimize your query by removing unnecessary "join"

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

And you'll get approximately next 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`)

Maybe in your case, also it's more efficient to pre-compile this query http://slick.typesafe.com/doc/3.0.0/queries.html#compiled-queries

like image 97
Igor Mielientiev Avatar answered Nov 14 '22 23:11

Igor Mielientiev