Как использовать `ConstColumn` для необязательных значений в слике

#scala #slick

#scala #слик

Вопрос:

Есть некоторая таблица:

 case class Thing(name: String, color: Option[String], height: Option[String])

class ThingSchema(t: Tag) extends Table[Thing](t, "things") {
  def name = column[String]("name")
  def color = column[Option[String]]("color")
  def height = column[Option[String]]("height")
  def * = (name, color, height) <> (Thing.tupled, Thing.unapply)
}
val things = TableQuery[ThingSchema]
  

Например, в things таблице есть следующие данные:

 |  name   |   color   | height |
 --------- ----------- -------- 
|   n1    |  green    | <null> |
|   n1    |  green    | <null> |
|   n1    |  <null>   | normal |
|   n1    |  <null>   | normal |
|   n1    |  red      | <null> |
|   n2    |  red      | <null> |
  

Мне нужно получить следующий результат из приведенных выше данных:

 |  name   |   color   | height | size |
 --------- ----------- -------- ------ 
|   n1    |  green    | <null> |  2   |
|   n1    |  <null>   | normal |  2   |
|   n1    |  red      | <null> |  1   |
|   n2    |  red      | <null> |  1   |
  

Для решения этой задачи я использую следующие запросы группировки:

 SELECT name, color, null, count(*) AS size
FROM things
GROUP BY name, color

UNION ALL

SELECT name, null, height, count(*) AS size
FROM things
GROUP BY name, height
  

Я попытался создать этот запрос с помощью Slick:

 val query1 = 
      things.groupBy(t => (t.name, t.color))
            .map { case ((name, color), g) => (name,color,None, g.size)} //Error#1

val query2 = 
      things.groupBy(t => (t.name, t.height)) 
            .map { case ((name, height), g) => (name,None,height,g.size)} //Error#1

val query = query1    query2
  

Но приведенный выше код не компилируется, потому что Slick не может определить тип для ConstColumn для None значений (см. //Error#1 Комментарий в приведенном выше коде).

Это сработало бы для ненулевых значений (таких как numbers , strings ), но не работает для обнуляемых значений, которые представлены, например, как Option[String]=None .

Как использовать значения ConstColumn for None в этом случае?

Вот ссылка на тот же вопрос

Ответ №1:

Я нашел другое решение для этой задачи. Возможно, это будет кому-то полезно.

Мы можем использовать Rep.None[T] или Rep.Some[T] для генерации ConstColumn значений для необязательных типов.

Этот пример тоже работает:

 val query1 = 
     things.groupBy(t => (t.name, t.color))
           .map { case ((name, color), g) => 
                        (name,color, Rep.None[String], g.size)
                }
  

Этот подход имеет два преимущества:

1) Мы можем назначить более общий тип. Например:

 val field: Rep[String] = ...
val x: (Rep[String], Rep[Option[String]]) = (field, Rep.None[String])

// it is not compiled because a tuple has type (Rep[String], Option[String])
val y: (Rep[String], Rep[Option[String]]) = (field, None: Option[String])
  

2) Этот подход немного короче

Ответ №2:

Ошибка, которую я ожидаю в этой ситуации, заключается в некотором несоответствии типов между Option[String] и None.type в двух //Error точках вашего кода.

Что вы можете сделать, так это указать аннотацию типа на None :

 val query1 = 
      things.groupBy(t => (t.name, t.color))
            .map { case ((name, color), g) => (name,color, None: Option[String], g.size)}
  

Это будет скомпилировано в SELECT name, color, null, count шаблон, который вы используете.