Scala коллекции "один-ко-многим"
У меня есть база данных, которая содержит действия с отношением регистрации "один ко многим".
Цель состоит в том, чтобы получить все виды деятельности, со списком их регистраций.
Создавая декартово произведение деятельности с регистрациями, все необходимые данные для получения этих данных отсутствуют.
Но я не могу найти хороший способ получить его в коллекцию scala правильно;
пусть тип: Seq[(Activity, Seq[Registration])]
case class Registration(
id: Option[Int],
user: Int,
activity: Int
)
case class Activity(
id: Option[Int],
what: String,
when: DateTime,
where: String,
description: String,
price: Double
)
Предполагая, что существуют соответствующие слабые таблицы и табличные запросы, я бы написал:
val acts_regs = (for {
a <- Activities
r <- Registrations if r.activityId === a.id
} yield (a, r))
.groupBy(_._1.id)
.map { case (actid, acts) => ??? }
}
Но я не могу сделать соответствующее сопоставление. Каков идиоматический способ сделать это? Я надеюсь, что это лучше, чем работа с необработанным декартовым продуктом...
В Scala
В коде scala он достаточно прост и выглядит примерно так:
val activities = db withSession { implicit sess =>
(for {
a <- Activities leftJoin Registrations on (_.id === _.activityId)
} yield a).list
}
activities
.groupBy(_._1.id)
.map { case (id, set) => (set(0)._1, set.map(_._2)) }
Но это кажется довольно неэффективным из-за ненужных экземпляров Activity, которые создатель таблицы создаст для вас.
Также он не выглядит действительно элегантным...
Получение количества регистраций
Метод scala еще хуже, когда он интересуется только таким количеством регистраций:
val result: Seq[Activity, Int] = ???
В слайке
Моя лучшая попытка в slick будет выглядеть так:
val activities = db withSession { implicit sess =>
(for {
a <- Activities leftJoin Registrations on (_.id === _.activityId)
} yield a)
.groupBy(_._1.id)
.map { case (id, results) => (results.map(_._1), results.length) }
}
Но это приводит к ошибке, что пятно не может отображать данные типы в строке "map".
Ответы
Ответ 1
Я бы предложил:
val activities = db withSession { implicit sess =>
(for {
a <- Activities leftJoin Registrations on (_.id === _.activityId)
} yield a)
.groupBy(_._1)
.map { case (activity, results) => (activity, results.length) }
}
Проблема с
val activities = db withSession { implicit sess =>
(for {
a <- Activities leftJoin Registrations on (_.id === _.activityId)
} yield a)
.groupBy(_._1.id)
.map { case (id, results) => (results.map(_._1), results.length) }
}
заключается в том, что вы не можете создавать вложенные результаты в группе. results.map(_._1)
- это набор элементов. SQL делает неявные преобразования из коллекций в отдельные строки в некоторых случаях, но Slick является безопасным по типу. То, что вы хотели бы сделать в Slick, похоже на results.map(_._1).head
, но в настоящее время оно не поддерживается. Самое близкое, что вы можете получить, это что-то вроде (results.map(_.id).max, results.map(_.what).max, ...)
, что довольно утомительно. Таким образом, группировка по всей строке действий, вероятно, является наиболее возможным решением в настоящее время.
Ответ 2
Решение для получения всех регистраций за активность:
// list of all activities
val activities = Activities
// map of registrations belonging to those activities
val registrations = Registrations
.filter(_.activityId in activities.map(_.id))
.list
.groupBy(_.activityId)
.map { case (aid, group) => (aid, group.map(_._2)) }
.toMap
// combine them
activities
.list
.map { a => (a, registrations.getOrElse(a.id.get, List()))
Задание выполняется в 2 запросах. Это должно быть выполнимо, чтобы абстрагировать этот тип функции группировки в функцию scala.