Слайк 3 множественных внешних соединения

из документации Slick, ясно, как сделать одно левое соединение между двумя таблицами.

val q = for {
  (t, v) <- titles joinLeft volumes on (_.uid === _.titleUid)
} yield (t, v)

Запрос q будет, как и ожидалось, иметь атрибуты: _1 типа Titles и _2 типа Rep[Option[Volumes]] для покрытия несуществующих томов.

Дальнейшее каскадирование проблематично:

val q = for {
  ((t, v), c) <- titles 
                     joinLeft volumes on (_.uid === _.titleUid)
                     joinLeft chapters on (_._2.uid === _.volumeUid)
} yield /* etc. */

Это не сработает, потому что _._2.uid === _.volumeUid недействителен, если _.uid не существует.

Согласно различным источникам в сети, это не должно быть проблемой, но, опять же, источники, как правило, нацелены на разные версии slick, а 3.0 все еще довольно новые. У кого-нибудь есть ключ к этой проблеме? Чтобы пояснить, идея состоит в том, чтобы использовать два левых соединения для извлечения данных из 3 каскадных таблиц 1: n: n. Эквивалентным SQL будет:

Select *
from titles
left join volumes
    on titles.uid = volumes.title_uid
left join chapters
    on volumes.uid = chapters.volume_uid

Ответы

Ответ 1

Второе левое соединение больше не работает на TableQuery[Titles], а вместо того, что эффективно Query[(Titles, Option[Volumes])] (игнорируя параметры результата и типа коллекции). Когда вы присоедините результирующий запрос к вашему TableQuery[Chapters], вы можете получить доступ ко второй записи в кортеже, используя поле _2 (так как это Option вам нужно map получить доступ к полю uid):

val q = for {
  ((t, v), c) <- titles 
                     joinLeft volumes on (_.uid === _.titleUid)
                     joinLeft chapters on (_._2.map(_.uid) === _.volumeUid)
} yield /* etc. */

Избегание TupleN

Если синтаксис синтаксиса _N неясен, вы также можете использовать способность Slick для пользовательских типов записей для сопоставления своих строк:

// The `Table` variant of the joined row representation
case class TitlesAndVolumesRow(title: Titles, volumes: Volumes)

// The DTO variant of the joined row representation
case class TitleAndVolumeRow(title: Title, volumes: Volume)

implicit object TitleAndVolumeShape
  extends CaseClassShape(TitlesAndVolumesRow.tupled, TitleAndVolumeRow.tupled)