【问题标题】:Why does Slick generate a subquery when take() method is called为什么调用take()方法时Slick会生成子查询
【发布时间】:2013-01-20 15:06:02
【问题描述】:

我使用Slick 1.0.0-RC1。我对表对象有这个定义:

object ProductTable extends Table[(Int, String, String, String, Double, java.sql.Date, Int, Option[Int], Int, Boolean)]("products") {
  def id = column[Int]("productId", O.PrimaryKey, O.AutoInc)
  def title = column[String]("title")
  def description = column[String]("description")
  def shortDescription = column[String]("shortDescription")
  def price = column[Double]("price")
  def addedDate = column[java.sql.Date]("addedDate")
  def brandId = column[Int]("brandId")
  def defaultImageId = column[Option[Int]]("defaultImageId")
  def visitCounter = column[Int]("visitCounter")
  def archived = column[Boolean]("archived")
  def * = id ~ title ~ description ~ shortDescription ~ price ~ addedDate ~ brandId ~ defaultImageId ~ visitCounter ~ archived
}

我需要一个从数据库中选择 8 行的简单查询:

ProductTable.filter(_.title === "something")
  .sortBy(_.visitCounter)
  .map(_.title)
  .take(8)
  .selectStatement

输出是:

select x2.x3 from 
   (select x4.`title` as x3 from `products` x4 
     where x4.`title` = 'something' 
     order by x4.`visitCounter` limit 8) x2

如果我摆脱take() 方法:

ProductTable.filter(_.title === "something")
 .sortBy(_.visitCounter)
 .map(_.title)
 .selectStatement

那么输出是:

select x2.`title` from `products` x2 
where x2.`title` = 'something' 
order by x2.`visitCounter`

所以我的问题是:为什么 Slick 的查询对象用take() 方法构造时会生成子查询?

附:如果可以相关,我将所有这些都使用 MySql 驱动程序

【问题讨论】:

  • 翻转地图并拍摄
  • 你试过了吗?它不会改变输出 SQL
  • 发布到 Slick 用户组,Zeiger 比任何人都更清楚为什么会生成子选择......

标签: sql scala slick


【解决方案1】:

有一个简短的答案和一个长的答案。简短的是:子查询在那里,因为到目前为止没有人愿意删除它。

更长的答案与交换“map”和“take”没有任何区别这一事实有关,这是因为查询的编译几乎是简单直接的。

在查询编译器的相对早期,有一个“forceOuterBinds”阶段,该阶段(可能)引入了许多在语义上等效且冗余的额外 Bind(又名 flatMap)操作。我们的想法是获取一些具有集合类型的 x 并将其转换为 Bind(s, x, Pure(s)) 其中 s是一个新鲜的符号。如果 x 已经是 Pure(y) 的形状,我们把它变成 Bind(s, Pure(ProductNode()), Pure(y)) 代替。在 Scala 代码中,将其视为将 x: List[T] 转换为 x.flatMap(s => List(s))List( y) 转换成 List(()).flatMap(s => List(y))。这种转换的目的是通过为我们提供一个地方来修改我们可能想要这样做的所有地方的投影(它是作为恒等映射创建的),从而使后面的编译器阶段中的树重写更容易。

稍后,在“convertToComprehensions”中,所有来自单子形式的节点(Bind、Pure、Filter、Take、Drop 等)单独转换为 Comprehension 节点(代表一个 SQL select 语句)。结果仍然不是合法的 SQL:SQL 推导不是 monad 推导。它们具有非常有限的范围规则,不允许 from 子句引用由先前的 from 子句引入的变量(在相同的理解或封闭的理解中)。

这就是我们需要下一个阶段“fuseComprehensions”的原因,它乍一看可能纯粹是一种优化,但实际上是生成正确代码所必需的。这个阶段试图尽可能地融合个人理解,以避免这些非法引用。我们在可以融合的方面取得了一些进展,但还没有 100% 解决范围问题(事实上,我很确定这是不可能解决的)。

重申这一点,这一阶段的进步主要是出于对正确性的需求,而不仅仅是生成更好的代码。那么我们可以删除那个额外的子查询吗?是的,当然,但还没有人实现它。

如果您想尝试实现这样的优化,请考虑以下注意事项:

  • 您是否满足于移除纯粹的锯齿投影(即,select 槽的形式为 Some(ProductNode(ch)),其中 ch 的每个元素都是路径)?
  • 或者也许你认为select x+1 from (... limit ...))也应该被融合。你可以允许什么样的表达方式?例如。 RowNum 可以吗?
  • 子查询需要有什么样的形状?例如,它可能包含 groupByorderBy 子句吗?

(我要考虑的事情是:与解释为什么还不存在相比,实施该优化需要多长时间?)

【讨论】:

  • 您的答案在 Slick 3.0 中仍然有效吗?或者发生了一些我找不到的变化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-31
  • 1970-01-01
  • 1970-01-01
  • 2021-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多