Как писать вложенные запросы в предложении Select

Я пытаюсь создать этот SQL с SLICK 1.0.0:

    select
    cat.categoryId,
    cat.title,
    (
      select
        count(product.productId)
      from
        products product
        right join products_categories productCategory on productCategory.productId = product.productId
        right join categories c on c.categoryId = productCategory.categoryId
      where
        c.leftValue >= cat.leftValue and
        c.rightValue <= cat.rightValue
    ) as productCount
from
    categories cat
where
    cat.parentCategoryId = 2;

моя самая успешная попытка (я отбросил часть "joins", поэтому она более читаема):

def subQuery(c: CategoriesTable.type) = (for {
        p <- ProductsTable

      } yield(p.id.count))
      for {
        c <- CategoriesTable
        if (c.parentId === 2)
      } yield(c.id, c.title, (subQuery(c).asColumn))

где производят недостающие скобки SQL в подзапрос:

   select 
    x2.categoryId, 
    x2.title, 
    select count(x3.productId) from products x3 
   from 
    categories x2 
   where x2.parentCategoryId = 2

что, очевидно, недопустимый SQL Любые мысли о том, как Слик поставил эти скобки в нужное место? Или, может быть, есть другой способ достичь этого?

1 ответов


Я никогда не использовал Slick или ScalaQuery, так что это было довольно приключение, чтобы узнать, как достичь этого. Slick очень расширяемый, но документация по расширению немного сложнее. Возможно, она уже существует, но это то, что я придумал. Если я сделал что-то неправильно, пожалуйста, поправьте меня.

Сначала нам нужно создать пользовательский драйвер. Я протянул H2Driver для того чтобы мочь испытать легко.

trait CustomDriver extends H2Driver {

  // make sure we create our query builder
  override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = 
    new QueryBuilder(input)

  // extend the H2 query builder
  class QueryBuilder(input: QueryBuilderInput) extends super.QueryBuilder(input) {

    // we override the expr method in order to support the 'As' function
    override def expr(n: Node, skipParens: Boolean = false) = n match {

      // if we match our function we simply build the appropriate query
      case CustomDriver.As(column, LiteralNode(name: String)) =>
        b"("
        super.expr(column, skipParens)
        b") as ${name}"

      // we don't know how to handle this, so let super hanle it
      case _ => super.expr(n, skipParens)
    }
  }
}

object CustomDriver extends CustomDriver {
  // simply define 'As' as a function symbol
  val As = new FunctionSymbol("As")

  // we override SimpleSql to add an extra implicit
  trait SimpleQL extends super.SimpleQL {

    // This is the part that makes it easy to use on queries. It's an enrichment class.
    implicit class RichQuery[T: TypeMapper](q: Query[Column[T], T]) {

      // here we redirect our as call to the As method we defined in our custom driver
      def as(name: String) = 
        CustomDriver.As.column[T](Node(q.unpackable.value), name)
    }
  }

  // we need to override simple to use our version
  override val simple: SimpleQL = new SimpleQL {}
}

для того, чтобы использовать его, нам нужно конкретно импорт вещи:

import CustomDriver.simple._
import Database.threadLocalSession

затем, чтобы использовать его, вы можете сделать следующее (Я использовал таблицы из официальной документации Slick в моем примере).

// first create a function to create a count query
def countCoffees(supID: Column[Int]) =
  for {
    c <- Coffees
    if (c.supID === supID)
  } yield (c.length)

// create the query to combine name and count
val coffeesPerSupplier = 
  for {
    s <- Suppliers
  } yield (s.name, countCoffees(s.id) as "test")

// print out the name and count
coffeesPerSupplier foreach { case (name, count) =>
  println(s"$name has $count type(s) of coffee")
}

результат такой:

Acme, Inc. has 2 type(s) of coffee
Superior Coffee has 2 type(s) of coffee
The High Ground has 1 type(s) of coffee