5

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

表定义:

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:

我正在尝试单子连接,但也被困在这里。例如,这样的事情会得到给定用户的所有列表:

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:

我还不知道这是否正确,但一元连接可能是解决方案的途径。这编译:

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

1 回答 1

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)
}
于 2015-07-09T14:34:47.907 回答