Scala Slick filter and join - scala

When performing filter-joins in Slick, what is the difference under the hood between the following two methods?
val query = for {
c <- coffees if c.price < 9.0
s <- c.supplier -- assuming there is a foreign key
} yield (c.name, s.name)
and
val query = for {
(cof, sup) <- coffees.filter(_.price < 9.0) join supplier on(_.supId === _.id)
} yield (cof.name, sup.name)

The first one is an implicit join and the second is an explicit join. Slick generates a WHERE clause for the former like: WHERE c.price < 9 AND c.supId = s.id. However the latter generates a JOIN like JOIN supplier s ON c.supId = s.id. You can have a look at these examples.

Related

Left outer join of three tables with two conditions each not working in Slick

I have the following left outer join that I'd like to represent in Slick:
select * from tableD d
left outer join tableE e on e.ds_sk=d.ds_sk and e.ds_type = d.ds_type
left outer join tableV v on v.es_sk=e.sk and v.var_name=d.var_name
where e.sk=30;
Note that each relationship contains two join conditions. The first join conditions are between tableD and tableE, and the second join conditions are between tableV, tableE and tableD. Finally, there's also a where condition.
This is my attempt (that throws a compilation error):
val query = for {
((d, e), v) <- tableE joinLeft tableD on ((x,y) => x.dsType === y.dsType && x.dsSk === y.dsSk)
joinLeft tableV on ((x,y) => x._1.sk === y.esSk && x._2.varName === y.varName)
if (e.sk === 30)
} yield (d,e,v)
The error I get is:
◾value varName is not a member of slick.lifted.Rep[Option[tableD]]
What is the error and how to fix this code?
There seems to be discrepancies between your SQL query (D leftjoin E leftjoin V) and Slick query (E leftjoin D leftjoin V).
Assuming your SQL query is the correct version, the Slick query should look like the following:
val query = for {
((d, e), v) <- tableD joinLeft tableE on ( (x, y) =>
x.dsType === y.dsType && x.dsSk === y.dsSk )
joinLeft tableV on ( (x, y) =>
x._2.map(_.sk) === y.esSk && x._1.varName) === y.varName )
if (e.sk === 30)
} yield (d,e,v)
Note that in the second joinLeft, you need to use map to access fields in tableE which is wrapped in Option after the first joinLeft. That also explains why you're getting the error message about Option[tableD] in your Slick query.
You can compare doing map by varName, example:
... && x._2.map(_.varName) === y.varName ...
Here I leave a similar example:
val withAddressesQuery = for {
(((person, phone), _), address) <- withPhonesQuery.
joinLeft(PersonAddress).on(_._1.personId === _.personId).
joinLeft(Address).on(_._2.map(_.addressId) === _.addressId)
} yield (person, phone, address)
Notice: ... on(_._2.map( _.addressId) === ...see complete
There's some more information about joinLeft in Chapter 6.4.3 of Essential Slick.

Slick join with subqueries

I build following SQL Query
SELECT
v.uuid, d.start_time, d.end_time
FROM
visits v
INNER JOIN
visit_dates d ON v.uuid = d.visit_uuid
WHERE
v.study_environment_site_uuid = (SELECT
study_environment_site_uuid
FROM
visits
WHERE
uuid = 'e4663612-39f9-4c43-bd86-c4c5a9235b03')
AND v.uuid != 'e4663612-39f9-4c43-bd86-c4c5a9235b03'
AND d.start_time < (SELECT start_time FROM visit_dates WHERE visit_uuid = 'e4663612-39f9-4c43-bd86-c4c5a9235b03' ORDER BY start_time LIMIT 1)
ORDER BY d.start_time;
now trying to reflect that into Slick
(for {
vSes <- visits.filter(_.uuid === uuid)
vDate <- visitDates.filter(_.visitUuid === uuid).sortBy(_.startTime).take(1)
(v, d) <- visits join visitDates on (_.uuid === _.visitUuid)
if (v.uuid =!= uuid && v.studyEnvironmentSiteUuid === vSes.studyEnvironmentSiteUuid && d.startTime < vDate.startTime)
} yield (v.uuid)).result.map(_.headOption)
But this produced wrong result. I am using Slick 3.2.1
Following SQL is generated
SELECT
x2.`uuid`,
x7.start_time
FROM
`visits` x3,
(SELECT
`visit_uuid` AS x4, `start_time` AS x5
FROM
`visit_dates`
WHERE
`visit_uuid` = ?
ORDER BY `start_time`
LIMIT 1) x6,
`visits` x2,
`visit_dates` x7
WHERE
((x2.`uuid` = x7.`visit_uuid`)
AND ((NOT (x2.`uuid` = ?))
AND (x7.`start_time` < x6.x5)))
AND ((x3.`uuid` = ?)
AND (x2.`study_environment_site_uuid` = x3.`study_environment_site_uuid`));
Generated query is not a join and returns multiple rows instead of one that the manual query produces.
Any ideas/pointers?
As there isn't an answer yet, I will post my workaround for the problem
for {
(v, d) <- visits join visitDates on (_.uuid === _.visitUuid)
vSes <- visits.filter(_.uuid === uuid)
vDate <- visitDates.filter(_.visitUuid === uuid).sortBy(_.startTime).take(1)
if v.uuid =!= uuid && v.studyEnvironmentSiteUuid === vSes.studyEnvironmentSiteUuid && d.startTime < vDate.startTime
} yield (v.uuid, d.startTime)).result.map {
case results#(_ +: _) => Some(results.maxBy(_._2)._1)
case _ => None
}

combining slick queries into single query

Using Slick 3.1, how do I combine multiple Queries into a single query for the same type? This is not a join or a union, but combining query "segments" to create a single query request. These "segments" can be any individually valid query.
val query = TableQuery[SomeThingValid]
// build up pieces of the query in various parts of the application logic
val q1 = query.filter(_.value > 10)
val q2 = query.filter(_.value < 40)
val q3 = query.sortBy(_.date.desc)
val q4 = query.take(5)
// how to combine these into a single query ?
val finalQ = ??? q1 q2 q3 q4 ???
// in order to run in a single request
val result = DB.connection.run(finalQ.result)
EDIT:
the expected sql should be something like:
SELECT * FROM "SomeThingValid" WHERE "SomeThingValid"."value" > 10 AND "SomeThingValid"."valid" < 40 ORDER BY "MemberFeedItem"."date" DESC LIMIT 5
val q1 = query.filter(_.value > 10)
val q2 = q1.filter(_.value < 40)
val q3 = q2.sortBy(_.date.desc)
val q4 = q3.take(5)
I think you should do something like the above (and pass around Querys) but if you insist on passing around query "segments", something like this could work:
type QuerySegment = Query[SomeThingValid, SomeThingValid, Seq] => Query[SomeThingValid, SomeThingValid, Seq]
val q1: QuerySegment = _.filter(_.value > 10)
val q2: QuerySegment = _.filter(_.value < 40)
val q3: QuerySegment = _.sortBy(_.date.desc)
val q4: QuerySegment = _.take(5)
val finalQ = Function.chain(Seq(q1, q2, q3, q4))(query)
I've used this "pattern" with slick2.0
val query = TableQuery[SomeThingValid]
val flag1, flag3 = false
val flag2, flag4 = true
val bottomFilteredQuery = if(flag1) query.filter(_.value > 10) else query
val topFilteredQuery = if(flag2) bottomFilteredQuery.filter(_.value < 40) else bottomFilteredQuery
val sortedQuery = if(flag3) topFilteredQuery.soryBy(_.date.desc) else topFilteredQuery
val finalQ = if(flag4) sortedQuery.take(5) else sortedQuery
It's Just a worth remark to mention here from the slick essential book, it seems that you might need to avoid combining multiple queries in one single query.
Combining actions to sequence queries is a powerful feature of Slick.
However, you may be able to reduce multiple queries into a single
database query. If you can do that, you’re probably better off doing
it.
I think, it should work. But I didn't test it yet.
val users = TableQuery[Users]
val filter1: Query[Users, User, Seq] = users.filter(condition1)
val filter2: Query[Users, User, Seq] = users.filter(condition2)
(filter1 ++ filter2).result.headOption

How to make aggregations with slick

I want to force slick to create queries like
select max(price) from coffees where ...
But slick's documentation doesn't help
val q = Coffees.map(_.price) //this is query Query[Coffees.type, ...]
val q1 = q.min // this is Column[Option[Double]]
val q2 = q.max
val q3 = q.sum
val q4 = q.avg
Because those q1-q4 aren't queries, I can't get the results but can use them inside other queries.
This statement
for {
coffee <- Coffees
} yield coffee.price.max
generates right query but is deprecated (generates warning: " method max in class ColumnExtensionMethods is deprecated: Use Query.max instead").
How to generate such query without warnings?
Another issue is to aggregate with group by:
"select name, max(price) from coffees group by name"
Tried to solve it with
for {
coffee <- Coffees
} yield (coffee.name, coffee.price.max)).groupBy(x => x._1)
which generates
select x2.x3, x2.x3, x2.x4 from (select x5."COF_NAME" as x3, max(x5."PRICE") as x4 from "coffees" x5) x2 group by x2.x3
which causes obvious db error
column "x5.COF_NAME" must appear in the GROUP BY clause or be used in an aggregate function
How to generate such query?
As far as I can tell is the first one simply
Query(Coffees.map(_.price).max).first
And the second one
val maxQuery = Coffees
.groupBy { _.name }
.map { case (name, c) =>
name -> c.map(_.price).max
}
maxQuery.list
or
val maxQuery = for {
(name, c) <- Coffees groupBy (_.name)
} yield name -> c.map(_.price).max
maxQuery.list

scalaquery retrieve values

I have few tables, lets say 2 for simplicity. I can create them in this way,
...
val tableA = new Table[(Int,Int)]("tableA"){
def a = column[Int]("a")
def b = column[Int]("b")
}
val tableB = new Table[(Int,Int)]("tableB"){
def a = column[Int]("a")
def b = column[Int]("b")
}
Im going to have a query to retrieve value 'a' from tableA and value 'a' from tableB as a list inside the results from 'a'
my result should be:
List[(a,List(b))]
so far i came upto this point in query,
def createSecondItr(b1:NamedColumn[Int]) = for(
b2 <- tableB if b1 === b1.b
) yield b2.a
val q1 = for (
a1 <- tableA
listB = createSecondItr(a1.b)
) yield (a1.a , listB)
i didn't test the code so there might be errors in the code. My problem is I cannot retrieve data from the results.
to understand the question, take trains and classes of it. you search the trains after 12pm and you need to have a result set where the train name and the classes which the train have as a list inside the train's result.
I don't think you can do this directly in ScalaQuery. What I would do is to do a normal join and then manipulate the result accordingly:
import scala.collection.mutable.{HashMap, Set, MultiMap}
def list2multimap[A, B](list: List[(A, B)]) =
list.foldLeft(new HashMap[A, Set[B]] with MultiMap[A, B]){(acc, pair) => acc.addBinding(pair._1, pair._2)}
val q = for (
a <- tableA
b <- tableB
if (a.b === b.b)
) yield (a.a, b.a)
list2multimap(q.list)
The list2multimap is taken from https://stackoverflow.com/a/7210191/66686
The code is written without assistance of an IDE, compiler or similar. Consider the debugging free training :-)