将嵌套连接和groupby查询转换为Slick 3.0

时间:2015-07-08 23:32:30

标签: sql scala slick-3.0

我正在实施待办事项列表。用户可以有多个列表,列表可以有多个用户。我希望能够检索用户的所有列表,其中每个列表都包含它共享的用户列表(包括所有者)。没有成功实现此查询。

表格定义:

case class DBList(id: Int, uuid: String, name: String)
class Lists(tag: Tag) extends Table[DBList](tag, "list") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column
  def uuid = column[String]("uuid")
  def name = column[String]("name")
  // Every table needs a * projection with the same type as the table's type parameter
  def * = (id, uuid, name) <> (DBList.tupled, DBList.unapply)
}
val lists = TableQuery[Lists]

case class DBUser(id: Int, uuid: String, email: String, password: String, firstName: String, lastName: String)
// Shared user projection, this is the data of other users which a user who shared an item can see
case class DBSharedUser(id: Int, uuid: String, email: String, firstName: String, lastName: String, provider: String)
class Users(tag: Tag) extends Table[DBUser](tag, "user") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column
  def uuid = column[String]("uuid")    
  def email = column[String]("email")    
  def password = column[String]("password")    
  def firstName = column[String]("first_name")    
  def lastName = column[String]("last_name")    
  def * = (id, uuid, email, password, firstName, lastName) <> (DBUser.tupled, DBUser.unapply)
  def sharedUser = (id, uuid, email, firstName, lastName) <> (DBSharedUser.tupled, DBSharedUser.unapply)
}
val users = TableQuery[Users]


// relation n:n user-list
case class DBListToUser(listUuid: String, userUuid: String)
class ListToUsers(tag: Tag) extends Table[DBListToUser](tag, "list_user") {
  def listUuid = column[String]("list_uuid")
  def userUuid = column[String]("user_uuid")    
  def * = (listUuid, userUuid) <> (DBListToUser.tupled, DBListToUser.unapply)
  def pk = primaryKey("list_user_unique", (listUuid, userUuid)) 
}
val listToUsers = TableQuery[ListToUsers]

我创建了一个额外的类来保存数据库列表对象+用户,我的目标是将查询结果以某种方式映射到此类的实例。

case class DBListWithSharedUsers(list: DBList, sharedUsers: Seq[DBSharedUser])

这是大多数的SQL查询,它首先得到用户的所有列表(内部查询)然后它与list_user和用户进行列表连接,以便获取其余数据和用户每个列表,然后它使用内部查询进行过滤。它没有按部分包含该组

select * from list inner join list_user on list.uuid=list_user.list_uuid inner join user on user.uuid=list_user.user_uuid where list.uuid in (
  select (list_uuid) from list_user where user_uuid=<myUserUuuid>
);

我测试了它并且它有效。我试图在Slick中实现它,但我收到编译器错误。我也不知道那部分的结构是否正确,但是还没有能够找到更好的结构。

def findLists(user: User) = {
  val listsUsersJoin = listToUsers join lists join users on {
    case ((listToUser, list), user) =>
      listToUser.listUuid === list.uuid &&
      listToUser.userUuid === user.uuid
  }

  // get all the lists for the user (corresponds to inner query in above SQL)
  val queryToGetListsForUser = listToUsers.filter(_.userUuid===user.uuid)

  // map to uuids
  val queryToGetListsUuidsForUser: Query[Rep[String], String, Seq] = queryToGetListsForUser.map { ltu => ltu.listUuid }

  // create query that mirrors SQL above (problems):
  val queryToGetListsWithSharedUsers = (for {
    listsUuids <- queryToGetListsUuidsForUser
    ((listToUsers, lists), users) <- listsUsersJoin
    if lists.uuid.inSet(listsUuids) // error because inSet requires a traversable and passing a ListToUsers
  } yield (lists))

  // group - doesn't compile because above doesn't compile:
  queryToGetListsWithSharedUsers.groupBy {case (list, user) =>
    list.uuid
  }
...
}

我该如何解决这个问题?

提前致谢

修改:

我把这个紧急解决方案(至少它编译)放在一起,我使用原始SQL执行查询,然后以编程方式进行分组,它看起来像这样:

case class ResultTmp(listId: Int, listUuid: String, listName: String, userId:Int, userUuid: String, userEmail: String, userFirstName: String, userLastName: String, provider: String)

implicit val getListResult = GetResult(r => ResultTmp(r.nextInt, r.nextString, r.nextString, r.nextInt, r.nextString, r.nextString, r.nextString, r.nextString, r.nextString))

val a = sql"""select (list.id, list.uuid, list.name, user.id, user.uuid, user.email, user.first_name, user.last_name, user.provider) from list inner join list_user on list.uuid=list_user.list_uuid inner join user on user.uuid=list_user.user_uuid where list.uuid in (
  select (list_uuid) from list_user where user_uuid=${user.uuid}
);""".as[ResultTmp]

val result: Future[Vector[ResultTmp]] = db.run(a)

val res: Future[Seq[DBListWithSharedUsers]] = result.map {resultsTmp =>
  val myMap: Map[String, Vector[ResultTmp]] = resultsTmp.groupBy { resultTmp => resultTmp.listUuid }

  val r: Iterable[DBListWithSharedUsers] = myMap.map {case (listUuid, resultsTmp) =>

    val first = resultsTmp(0)

    val list = DBList(first.listId, listUuid, first.listName)

    val users: Seq[DBSharedUser] = resultsTmp.map { resultTmp => 
      DBSharedUser(resultTmp.userId, resultTmp.userUuid, resultTmp.userEmail, resultTmp.userFirstName, resultTmp.userLastName, resultTmp.provider)
    }

    DBListWithSharedUsers(list, users)
  }

  r.toSeq
}

但这太可怕了,我如何让它以正常的方式运作?

编辑2:

我正在尝试使用monadic连接,但也被困在这里。例如,像这样的东西将获得给定用户的所有列表:

val listsUsersJoin = for {
    list <- lists
    listToUser <- listToUsers 
    user_ <- users if user_.uuid === user.uuid

} yield (list.uuid, list.name, user.uuid, user.firstName ...)

但这还不够,因为我还需要得到这些列表的所有用户,所以我需要2个查询。因此,我需要首先获得用户的列表,然后找到这些列表的所有用户,例如:

val queryToGetListsForUser = listToUsers.filter(_.userUuid===user.uuid)

val listsUsersJoin = for {
    list <- lists
    listToUser <- listToUsers 
    user_ <- users /* if list.uuid is in queryToGetListsForUser result  */
} yield (list.uuid, list.name, user.uuid, user.firstName ... )

但我不知道如何将其传递给联盟。我甚至不确定groupBy,至少在数据库级别是否正确,到目前为止我看到这只用于将结果聚合为单个值,如count或avg。我需要他们收藏。

编辑3:

我还不知道这是否正确但是monadic join可能是解决方案的途径。这编译:

val listsUsersJoin = for {
  listToUser <- listToUsers if listToUser.userUuid === user.uuid   // get the lists for the user
  list <- lists if list.uuid === listToUser.listUuid  // join with list
  listToUser2 <- listToUsers if list.uuid === listToUser.listUuid // get all the users for the lists  
  user_ <- users if user_.uuid === listToUser2.userUuid // join with user

} yield (list.uuid, list.name, user.uuid, user.email, user.firstName, user.lastName)

1 个答案:

答案 0 :(得分:1)

啊,看看那个,我想出了一个解决方案。我仍然需要测试是否有效,但至少编译器停止对它大喊大叫。如有必要,我稍后会对此进行编辑。

val listsUsersJoin = for {
  listToUser <- listToUsers if listToUser.userUuid === user.uuid  
  list <- lists if list.uuid === listToUser.listUuid 
  listToUser2 <- listToUsers if list.uuid === listToUser.listUuid
  user_ <- users if user_.uuid === listToUser2.userUuid

} yield (list.id, list.uuid, list.name, user_.id, user_.uuid, user_.email, user_.firstName, user_.lastName, user_.provider)

val grouped = listsUsersJoin.groupBy(_._2)

val resultFuture = db.run(grouped.result).flatMap {groupedResults =>

  val futures: Seq[Future[DBListWithSharedUsers]] = groupedResults.map {groupedResult =>

    val listUuid = groupedResult._1
    val valueQuery = groupedResult._2

    db.run(valueQuery.result).map {valueResult =>

        val first = valueResult(0) // if there's a grouped result this should never be empty 

        val list = DBList(first._1, listUuid, first._3)         
        val users = valueResult.map {value =>
            DBSharedUser(value._4, value._5, value._6, value._7, value._8, value._9)
        }

        DBListWithSharedUsers(list, users)
    }
  }
  Future.sequence(futures)
}