#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
шаблон, который вы используете.