Skip to content

Instantly share code, notes, and snippets.

@missingfaktor
Last active April 9, 2020 11:21
Show Gist options
  • Save missingfaktor/aa6c264c5b7411fa48a6a5b654dd0917 to your computer and use it in GitHub Desktop.
Save missingfaktor/aa6c264c5b7411fa48a6a5b654dd0917 to your computer and use it in GitHub Desktop.
implicit class RichQuery[E, U, C[_]](val underlying: Query[E, U, C]) {
/**
* To group by elements by some columns, say "column1" and "column2", and then pick latest in each group, you
* would normally "ORDER BY column1, column2, created_at DESC" and then "DISTINCT ON (column1, column2)".
* However Slick currently does not support DISTINCT ON. There are a number of alternatives that can be used to
* circumvent this limitation. The following function uses "sub-select method" described here:
* https://robots.thoughtbot.com/ordering-within-a-sql-group-by-clause#sub-select-method
*
* This function only deals with arity 2. You will need to define variations of it for different arities. We
* attempted abstracting over arity but could not get it working.
*/
def latestPer[A, B](
column1: E => Rep[A],
column2: E => Rep[B]
)(
implicit
groupByEvidence1: Shape[_ <: FlatShapeLevel, (Rep[A], Rep[B]), (A, B), (Rep[A], Rep[B])],
groupByEvidence2: Shape[_ <: FlatShapeLevel, E, _, E],
mapEvidence1: Shape[_ <: FlatShapeLevel, (Rep[A], Rep[B], Rep[Option[DateTime]]), (A, B, Option[DateTime]), (Rep[A], Rep[B], Rep[Option[DateTime]])],
mapEvidence2: Shape[_ <: FlatShapeLevel, E, U, E],
liskov: Liskov[E, HasCreatedAt],
optionMapperA: OptionMapper2[A, A, Boolean, A, A, Boolean],
optionMapperB: OptionMapper2[B, B, Boolean, B, B, Boolean]
): Query[E, U, C] = {
underlying
.join {
underlying
.groupBy(record => (column1(record), column2(record)))
.map { case ((key1, key2), values) => (key1, key2, values.map(value => liskov.coerce(value).createdAt).max) }
}
.on { case (record, (key1, key2, createdAt)) =>
new BaseColumnExtensionMethods(column1(record)) === key1 &&
new BaseColumnExtensionMethods(column2(record)) === key2 &&
liskov.coerce(record).createdAt === createdAt
}
.map { case (record, _) => record }
}
}
// Usage: Just write `.latestPer(_.col1, _.col2)` where you would have written
// `.sortBy(r => (r.col1, r.col2, r.map(_.createdAt).max)).distinctOn(r => (r.col1, r.col2))`.
@dpoetzsch
Copy link

Hey, thanks for the gist. I could get it to work by adapting it for a concrete instantiation but I fail to provide the necessary imports to get your generic implementation working.

Could you maybe add the required imports?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment