коллекции 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.