7

Slick1.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`

take()だから私の質問は:クエリオブジェクトがメソッドで構築されているときにSlickがサブクエリを生成するのはなぜですか?

PSそれが関連している可能性がある場合、私はこれらすべてでMySqlドライバーを使用します

4

1 に答える 1

12

短い答えと長い答えがあります。短いものは次のとおりです。これまで誰もそれを削除することを気にしなかったので、サブクエリはそこにあります。

より長い答えは、「マップ」と「テイク」を交換しても違いがないという事実に関連しています。これは、クエリのコンパイルがほとんど単純で単純であるためです。

クエリコンパイラの比較的早い段階で「forceOuterBinds」フェーズがあり、意味的に同等で冗長な多くの追加のバインド(別名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 )に変換すると考えてください。 ))。この変換の目的は、必要になる可能性のあるすべての場所でプロジェクション(IDマッピングとして作成された)を変更する場所を提供することにより、後のコンパイラフェーズでのツリーの書き換えを容易にすることです。

後で、「convertToComprehensions」で、モナディック形式のすべてのノード(Bind、Pure、Filter、Take、Dropなど)が個別にComprehensionノード(SQL selectステートメントを表す)に変換されます。ただし、結果はまだ正当なSQLではありません。SQL内包表記はモナド内包表記ではありません。それらには、 from句が前のfrom句によって導入された変数を参照することを許可しない非常に限定的なスコープルールがあります(同じ理解またはそれを囲む理解内)。

そのため、次のフェーズである「fuseComprehensions」が必要です。これは、一見すると純粋に最適化のように見えますが、実際には正しいコードを生成するために必要です。このフェーズでは、これらの違法な参照を回避するために、個々の理解を可能な限り融合しようとします。融合できるものについてはある程度の進歩がありましたが、スコープの問題に対する100%の解決策は見えていません(実際、解決することは不可能だと確信しています)。

繰り返しになりますが、このフェーズの進捗は、より優れたコードを生成するだけでなく、正確さの必要性によって主に推進されました。では、その余分なサブクエリを削除できますか?はい、確かに、しかし誰もまだそれを実装していません。

このような最適化を実装しようとする場合は、次の点について考慮してください。

  • 純粋にエイリアシングするプロジェクション(つまり、選択スロットの形式がSome(ProductNode(ch))で、chの各要素がパスであるComprehension)を削除することに満足していますか?
  • または、(...制限...)からx+1を選択することも融合する必要があると思うかもしれません。どんな表現ができますか?たとえば、RowNumは大丈夫でしょうか?
  • サブクエリにはどのような形が必要ですか?たとえば、groupBy句またはorderBy句を含めることができます

(そして私が考える何か:それがまだ存在しない理由を説明するのと比較して、その最適化を実装するのにどれくらい時間がかかりますか?)

于 2013-01-23T15:36:04.807 に答える