коллекции Scala slick one-to-many
у меня есть база данных, которая содержит действия с отношением один ко многим регистрациям. Цель состоит в том, чтобы получить все виды деятельности, со списком их регистрации.
создавая декартовый продукт деятельности с регистрациями, все необходимые данные, чтобы получить эти данные, есть.
Но я не могу найти хороший способ правильно поместить его в коллекцию 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
)
предполагая, что существуют соответствующие скользкие таблицы и tablequeries, Я бы написал:
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)) }
но это кажется довольно неэффективным из-за ненужных экземпляров активности, которые создадут для вас сопоставитель таблиц. Не выглядишь элегантный...
получение количества регистраций
метод in 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) }
}
но это приводит к ошибке, что Слик не может сопоставить данные типы в строке "карта".
2 ответов
Я предлагаю:
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) }
}
заключается в том, что вы не можете создавать вложенные результаты в group by. results.map(_._1)
представляет собой набор элементов. В некоторых случаях SQL выполняет неявные преобразования из коллекций в отдельные строки, но Слик, безопасный для типов,-нет. То, что вы хотели бы сделать в Slick, это что-то вроде results.map(_._1).head
, но в настоящее время не поддерживается. Ближе всего вы можете получить что-то вроде (results.map(_.id).max, results.map(_.what).max, ...)
, что довольно утомительно. Поэтому группировка по вся строка действий, вероятно, является наиболее возможным обходным путем прямо сейчас.
решение для получения всех регистраций за одно действие:
// 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.