Как писать удобочитаемые вложенные запросы на соединение с помощью Slick 3.0

Этот код создает запрос для получения профиля пользователя на веб-сервере. Он создает запрос, который собирает необходимую информацию в DTO (который является просто классом case), который впоследствии отправляется обратно в виде JSON.

  def getProfile(userId: Long)={
    val q = for{
    ((((u,p),a),b), ba) <- filterById(userId) join
               People on (_.personId === _.id) joinLeft
               Addresses on (_._2.addressId === _.id) joinLeft
               Businesses on (_._1._2.businessId === _.id) joinLeft
               Addresses on (_._2.flatMap(_.addressId) === _.id)
    }yield(p, a, b, ba)

    db.run(q.result.headOption).map{ _.map{case(p,a,b,ba) =>
      val business = b match {
      case Some(b) => Some(dtos.Business(b.name, b.abn, b.adminFee, ba, b.id))
      case _ => None
    }

    dtos.ProfileInfo(p, a, business)
  }}
}

Я включил обработку результатов (db.run(...)) только для контекста.

Я ищу более читаемый способ выразить конструкцию запроса.

Мой опыт чтения этого таков: «Подождите, что?? ... on (_._1._2.flatMap(_.addressId) .... что это делает?? Почему плоская карта там, а не здесь: on (_._1._2.businessId. На самом деле это прямые вещи, но не читайте прямо вперед. .

Я ищу способ выразить это, который не требует количества умозаключений, необходимых для прочтения этой версии. Мне нужно «вывести», что такое _._1._2 и почему его нужно сглаживать, что мне не нужно делать с эквивалентным SQL.

Примечания:

  • Этот код исходит из существующего приложения (не написанного мной), которое я расширяю.
  • Пользователи, Люди, Адреса, Компании (очевидно?) Таблицы.
  • У людей и предприятий есть адреса.
  • У пользователей есть человек(*), у людей есть бизнес
  • filterByUserId(userId) в основном эквивалентно Users.filter(_.id === userId)
  • Эквивалентный SQL:

    select p.*, a1.*, b.*, a2.* from Users u 
    innerJoin People p on (u.personId == p.id) 
    leftJoin Addresses a1 on (p.addressId == a1.id) 
    leftJoin Businesses b on (p.businessId == b.id) 
    leftJoin Addresses a2 on ( b.addressId == a2.id)
    

person GreenAsJade    schedule 24.08.2016    source источник
comment
Я был бы счастлив обменять краткость на удобочитаемость. Я искал способы постепенного составления запроса, но не вижу, как это сделать. Я хочу избавиться от вложенного кортежа в левой части и вложенного . в правой.   -  person GreenAsJade    schedule 26.08.2016


Ответы (1)


Вы должны поэкспериментировать с чем-то вроде этого:

val q = Users join People joinLeft Addresses joinLeft Businesses joinLeft Addresses on {
  case ((((u, p), a), b), ba) => u.personId === p.id && p.addressId === a.flatMap(_.id) && p.businessId === b.flatMap(_.id) && b.flatMap(_.addressId) === ba.id
} map {
  case ((((u, p), a), b), ba) => (p, a, b, ba)
}

Другим решением было бы выполнять соединения без использования для понимания, поэтому вам не нужно было бы использовать символы подчеркивания для извлечения значений из кортежей:

val q = Users join People on {
  case (u, p) => u.personId === p.id
} joinLeft Addresses on {
  case ((u, p), a) => p.addressId === a.id
} joinLeft Businesses on {
  case (((u, p), a), b) => p.businessId === b.id
} joinLeft Addresses on {
  case ((((u, p), a), b), ba) => b.flatMap(_.addressId) === ba.id
} map {
  case ((((u, p), a), b), ba) => (p, a, b, ba)
}

Вы не предоставили полные определения своих данных, поэтому я не смог полностью протестировать эти решения, но это должно дать вам некоторое представление о другом способе определения объединений в Slick. Дайте мне знать, если это было полезно вообще.

person Paweł Jurczenko    schedule 29.08.2016
comment
Ну, у меня не было ваших структур данных, поэтому мне пришлось кое-что догадаться. Я предположил, что после использования leftJoin правая часть предложения присоединения будет Option. Используя .?, вы также поднимаете обычный столбец до Option, так что теперь они сопоставимы. Теперь я бы посоветовал вам удалить все эти операции .? и .map и попытаться завершить код, постепенно компилируя его и пытаясь исправить каждую ошибку шаг за шагом. PS Я отредактировал свой пост и предоставил другое возможное решение вашей проблемы. - person Paweł Jurczenko; 30.08.2016
comment
Спасибо - все интересно и многообещающе: посмотрю, что из этого получится. - person GreenAsJade; 30.08.2016
comment
Я считаю, что ваше второе решение (измененное мной - легкое для меня, потому что у меня есть компилятор :)) достойно награды: оно избавляется от вложенного кортежа reference в RHS. Я должен сказать, что все еще надеюсь на что-то более элегантное... строка вложенных кортежей в LHS все еще довольно неопрятна, но она соответствует моим критериям краткости обмена для понятности. Я собираюсь попытаться заставить оригинальное решение работать сейчас. - person GreenAsJade; 31.08.2016
comment
Теперь я подправил ваше первое решение на основе того, что сказал мне компилятор. Большое спасибо за вашу помощь. Если бы вы не возражали прокомментировать, почему первому нужно больше flatMap(), чем второму, это было бы здорово. (Может быть, я должен задать отдельный вопрос для этого?) - person GreenAsJade; 31.08.2016
comment
Интересное обновление: первый вариант компилируется, но не работает. Он возвращает неправильный профиль! Второй работает. Интересно, чем они отличаются!? - person GreenAsJade; 31.08.2016