How To Read an Outer Join?

The principle is the same as for FULL JOIN.

The only thing you need change is to make the JOIN-ed table an Option[T]:

case class CustomerWithPhoneOpt(c: Customer, p: Option[Phone]) derives SqlReadRow

def customerwithPhoneOpts: List[CustomerWithPhoneOpt] = ctx.run {
  sql"SELECT ...".readRows[CustomerWithPhoneOpt]()
}

The p: Option[Phone] will be None when all its returned columns are NULL.