Scala Slick joinLeft和组合条件

时间:2019-03-08 01:29:10

标签: scala playframework slick

我希望能够使用Slick创建查询,以动态方式过滤左联接

case class Player(
  id: Long,
  createdAt: DateTime,
  lastModificationDate: DateTime,
  name: String
)

class PlayerTable(tag: Tag)  extends Table[Player](tag, "players") {
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def createdAt = column[DateTime]("createdAt")
  def lastModificationDate = column[DateTime]("lastModificationDate")
  def name = column[String]("name")
  override def * : ProvenShape[Player] = (
  id,
  createdAt,
  lastModificationDate,
  updatedAt,
  name
  ) <> (Player.tupled, Player.unapply)
}

case class PlayerGame(
  id: Long,
  createdAt: DateTime,
  lastModificationDate: DateTime,
  playerId: Long,
  level: Int,
  status: String
)

class PlayerGameTable(tag: Tag)  extends Table[PlayerGame](tag, "player_games") {
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def createdAt = column[DateTime]("createdAt")
  def lastModificationDate = column[DateTime]("lastModificationDate")
  def playerId = column[Long]("playerId")
  def level = column[Int]("level")
  def status = column[String]("status")
  override def * : ProvenShape[PlayerGame] = (
  id,
  createdAt,
  lastModificationDate,
  playerId,
  level,
  status
  ) <> (PlayerGame.tupled, PlayerGame.unapply)
}

我想用Slick编写这样的查询,其中WHERE CLAUSE是动态的。我写了两个例子

SELECT *
FROM players
LEFT JOIN player_games AS playerGamesOne ON players.id = playerGamesOne.playerId AND playerGamesOne.level = 1
LEFT JOIN player_games AS playerGamesTwo ON players.id = playerGamesTwo.playerId AND playerGamesTwo.level = 2
WHERE playerGamesOne.status LIKE 'gameOver'
OR playerGamesTWO.status LIKE 'gameOver'


SELECT *
FROM players
LEFT JOIN player_games AS playerGamesOne ON players.id = playerGamesOne.playerId AND playerGamesOne.level = 1
LEFT JOIN player_games AS playerGamesTwo ON players.id = playerGamesTwo.playerId AND playerGamesTwo.level = 2
WHERE playerGamesOne.status LIKE 'playing'
OR playerGamesTwo.status NOT LIKE 'gameOver'

我正在尝试类似的操作,但是我得到了Rep[Option[PlayerGameTable]]作为参数。也许有一种不同的方式来做这样的事情

val baseQuery = for {
  ((p, g1), g2) <- PlayerTable.playerQuery joinLeft 
    PlayerGameTable.playerGameQuery ON ((x, y) => x.id === y.playerId && y.level === 1) joinLeft
    PlayerGameTable.playerGameQuery ON ((x, y) => x._1.id === y.playerId && y.level === 2)
} yield (p, g1, g2)

private def filterPlayerGames(gameStatus: String, playerGamesOneOpt: Option[PlayerGameTable], playerGamesTwoOpt: Option[PlayerGameTable]) = {
  (gameStatus, playerGamesOneOpt, playerGamesOneOpt) match {
    case (gameStatus: String, Some(playerGamesOne: PlayerGameTable), Some(playerGamesOne: PlayerGameTable)) if gameStatus == "gameOver"  => playerGamesOne.status === "gameOver" || playerGamesTwo.status === "gameOver"
 }
}

这是一个复杂的问题,如果不清楚,请让我知道,我会尽力澄清

1 个答案:

答案 0 :(得分:2)

有两个问题:

  1. 在多种情况下,您的underscore子句中使用的ON占位符将无法按预期的方式运行
  2. _.level = something是分配而不是条件

假设PlayerTable.playerQueryTableQuery[PlayerTable],而PlayerGameTable.playerGameQueryTableQuery[PlayerGameTable],则您的baseQuery应该如下所示:

val baseQuery = for {
  ((p, g1), g2) <- PlayerTable.playerQuery joinLeft 
    PlayerGameTable.playerGameQuery on ((x, y) => x.id === y.playerId && y.level === 1) joinLeft
    PlayerGameTable.playerGameQuery on ((x, y) => x._1.id === y.playerId && y.level === 2)
} yield (p, g1, g2)

我尚不清楚您的filterPlayerGames方法将如何处理动态条件。我也不认为任何过滤包装器方法都具有足够的灵活性来使用任意and/or/negation运算符覆盖多个条件。我建议您将baseQuery用于必要的join,并在其之上构建过滤查询,类似于以下内容:

val query1 = baseQuery.filter{ case (_, g1, g2) =>
  g1.filter(_.status === "gameOver").isDefined || g2.filter(_.status === "gameOver").isDefined
}

val query2 = baseQuery.filter{ case (_, g1, g2) =>
  g1.filter(_.status === "playing").isDefined || g2.filter(_.status =!= "gameOver").isDefined
}

请注意,对于left joing1g2属于Option类型,因此isDefined适用于or操作。

另外,鉴于您的过滤条件仅在PlayerGameTable上执行,因此在filtering之前执行join可能会更有效。