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.