Nano Hash - криптовалюты, майнинг, программирование

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 это достаточно просто и будет выглядеть примерно так:

  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, которые создаст для вас сопоставитель таблиц. И не очень элегантно выглядит...

Получение подсчета регистраций

Метод 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) }
  }

Но это приводит к ошибке, что slick не может отображать данные типы в строке «map».

28.08.2014

  • По крайней мере, версия count имеет простой путь, описанный в документах 28.08.2014
  • Разница с документами в том, что меня интересует вся активность вместе с агрегацией по ее регистрациям. И я не знаю, как это отобразить. Вы можете помочь? 28.08.2014
  • Просто для ясности: я хотел бы что-то вроде: SELECT l.*, count(r.id) from activities l join registrations r where l.id = r.activityId; и сопоставьте его с Seq[(Activity, Int)] 28.08.2014
  • Просто прочитайте ваш последний комментарий. Не уверен, что это сработает, но пробовали ли вы groupBy(_._1), который затем должен выдать весь объект активности в качестве ключа. 28.08.2014
  • Кроме того.. Ваш первоначальный вопрос касался действия и списка связанных регистраций. У меня была аналогичная проблема ранее, и я решил это требование, выполнив два вызова базы данных, один спроецирован с .head, а другой с .list. Я понимаю, что это делает два «вызова» к базе данных, но поскольку у нас есть открытый сеанс/соединение, фактические накладные расходы очень минимальны. Если вы не против выразить свою проблему в два вызова (т.е. больше кода), то я думаю, что вы можете достичь своей цели таким образом. 28.08.2014
  • Группировка по чему-то большему, чем один столбец, у меня не работает с SQLException. 28.08.2014
  • А по поводу получения списка регистраций: я не против двух звонков. Я приземлился на что-то похожее для получения действий + счетчиков сейчас (список действий, список сгруппированных регистраций для всех действий). Но это не очень "элегантно". 28.08.2014

Ответы:


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, ...), что довольно утомительно. Таким образом, группировка по всей строке действий, вероятно, является наиболее подходящим обходным решением прямо сейчас.

28.08.2014
  • Я действительно думал, что пробовал это. Но, похоже, теперь это работает. Я думал, что groupBy для нескольких столбцов доставляет мне проблемы, потому что я получил SQLException, говорящий, что он ожидает только один операнд. Но я полагаю, что тогда это было для чего-то другого. 29.08.2014
  • Я бы принял этот ответ (также спасибо за понимание того, почему он не работает), за исключением того, что он работает только в том случае, когда вас интересует агрегат. Если groupBy не работает, если меня интересует вся последовательность регистраций, что бы вы тогда предложили? 29.08.2014
  • Кстати: я подозреваю, что «неявное преобразование», которое делает SQL, именно поэтому этот случай обманчиво неприятный. SQL сделал это таким простым... Но я бы посоветовал добавить в документы что-то из того, что вы здесь объясняете; документы охватывают типичный случай, но мне пришлось много экспериментировать, чтобы выяснить, каковы границы возможного; а затем мой вывод был неправильным из-за ошибки в другом месте. 29.08.2014
  • SQL и Slick's groupBy не позволяют вам возвращать вложенные коллекции. Если вам это нужно, вам нужно вместо этого сделать внешнее соединение или два отдельных запроса и привести их в желаемую форму на стороне клиента, используя старые простые коллекции scala. 29.08.2014
  • Спасибо за полный ответ. Мой ответ охватывает один из возможных способов получения полного списка регистраций для каждого действия за 2 запроса. 29.08.2014

  • 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.

    28.08.2014
  • Для совокупного случая (количество регистраций) предпочтительнее ответ cvogt. 29.08.2014
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..