Как я могу подсчитать количество строк, возвращаемых объединением в Slick?

Когда я пытаюсь выполнить Query(query.length).first в запросе, который представляет собой соединение двух таблиц, которые имеют несколько столбцов с одинаковыми именами, я получаю искаженный sql. Рассмотрим пример:

// in Main.scala
import scala.slick.driver.MySQLDriver.simple._
object Main extends App {

  object Houses extends Table[Long]("Houses") {
    def id = column[Long]("id")
    def * = id
  }
  object Rooms extends Table[(Long, Long)]("Rooms") {
    def id = column[Long]("id")
    def houseId = column[Long]("houseId")
    def * = id ~ houseId
  }

  val query = for {
    h <- Houses
    r <- Rooms
    if h.id === r.houseId
  } yield (h, r)
  println("QUERY: " + Query(query.length).selectStatement)
}

// in build.sbt
scalaVersion := "2.10.2"

libraryDependencies += "com.typesafe.slick" %% "slick" % "1.0.1"

В этом примере создается следующий SQL:

select x2.x3 from
  (select count(1) as x3 from 
    (select x4.`id`, x5.`id`, x5.`houseId` 
     from `Houses` x4, `Rooms` x5 where x4.`id` = x5.`houseId`) x6) x2

Что явно неверно и отклоняется MySQL, потому что столбец id дублируется в части select x4.id, x5.id.

Я мог бы попробовать сделать следующее:

query.list.size

но это приведет к извлечению всех строк из запроса и отправке их по сети, что значительно снизит производительность.

Что я делаю неправильно? Есть ли способ исправить это?


person Rogach    schedule 03.07.2013    source источник


Ответы (1)


Это интересный вопрос. Обычно с SQL вы используете псевдоним другого столбца, который вызывает конфликт имен, но я не уверен, как это работает с Slick (или, если возможно). Но я считаю, что вы можете обойти это, выбрав только один столбец, если вы просто хотите посчитать:

val query = for {
  h <- Houses
  r <- Rooms
  if h.id === r.houseId
} yield h.id.count

Теперь вызов count для id устарел, но этот вызов произвел чистый оператор sql, который выглядит следующим образом:

select count(x2.`id`) from `Houses` x2, `Rooms` x3 where x2.`id` = x3.`houseId`

Все, что я пробовал использовать .length, произвело кучу неверных sql.

ИЗМЕНИТЬ

В ответ на ваш комментарий вы хотели оставить запрос таким, каким он был (и давайте забудем, что сам запрос не работает из-за столкновения полей / неоднозначности в соединении), а затем также получить из него запрос подсчета, это будет выглядеть так:

def main(args: Array[String]) {
  val query = for {
    h <- Houses
    r <- Rooms
    if h.id === r.houseId
  } yield (h,r)

  val lengthQuery = query.map(_._1.id.count)
}

Дело в том, что вы должны иметь возможность принять любой запрос и map его в запрос подсчета, выбрав один столбец (вместо полных объектов), а затем получив этот count для этого столбца. В этом случае, поскольку результатом является Tuple2, мне нужно перейти на дополнительный уровень, чтобы добраться до столбца id, но я думаю, вы уловили картину.

person cmbaxter    schedule 12.07.2013
comment
Это действительно работает. Я надеялся, что существует решение для общего случая Query[A,B], потому что это уменьшит беспорядок в коде (текущее решение потребует от меня вручную писать код подсчета для каждого запроса). Но поскольку я не понимаю, как может быть возможно общее решение, это единственный способ. - person Rogach; 12.07.2013
comment
@Rogach, я добавил подробностей в ответ в ответ на ваш комментарий. - person cmbaxter; 12.07.2013
comment
Спасибо! Но я имел в виду другое. Например, у меня есть вспомогательный метод runApiQuery[A,B](query: Query[A,B]), который получает данные из запроса, применяет ограничения, подсчитывает объекты запроса и т. Д. Если .length в запросе работал правильно, я мог бы просто query.length внутри метода, не добавляя нагрузки runApiQuery вызывающему (и поскольку команда Slick уже 6 дней не комментирует ошибку, это, вероятно, произойдет не скоро). В текущей ситуации я могу только добавить параметр в runApiQuery: runApiQuery[A,B,C](q: Query[A,B])(countCol: A => Column[C]) и использовать countCol позже в теле. - person Rogach; 13.07.2013
comment
Забавно, я не прочитал весь ваш комментарий и на самом деле придумал в основном ту же сигнатуру метода, что и вы, прежде чем снова взглянуть на комментарий def runApiQuery[A,B, C:BaseTypeMapper](query:Query[A,B])(f:(A) => Column[C]). Счетчику нужен столбец для ввода, поэтому я не уверен, что вы можете сделать лучше, чем это. Я не уверен, как Slick может решить эту проблему должным образом. Допустим, они выбирают столбец; как они могут быть уверены, что это лучшая колонка, на которую можно положиться? Вы всегда должны рассчитывать на индексированный столбец, так что, если их логика иногда приводит к подсчету неиндексированного столбца? - person cmbaxter; 14.07.2013
comment
Они делают count(1), поэтому база данных решает, какой столбец использовать - я надеюсь, что он решит правильно. - person Rogach; 14.07.2013