Slick join with subqueries - scala

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
}

Related

Scala Slick filter with conditions over two left joined tables

I am trying to reproduce this query in Slick.
SELECT *
FROM A
JOIN LEFT B AS B1 ON B1.aId = A.id && B1.condition = 'b1'
JOIN LEFT B AS B2 ON B2.aId = A.id && B2.condition = 'b2'
- (no condition, the query in a plain way)
- WHERE B1.status = 'delete' OR B2.status = 'delete'
- WHERE ((B1.status = 'read' AND B2.status <> 'delete') OR (B1.status <> 'delete' AND B2.status = 'read')
- WHERE B1.status = 'write' AND B2.status = 'write'
- WHERE B1.status = 'full' AND B2.status = 'full'
- WHERE ((B1.status = 'full' AND B2.status = 'write') OR (B1.status = 'write' AND B2.status = 'full')
I am not sure if this is possible
Up to now I have something like this
val query = for { ((a, b1Opt), b2Opt) <- ATable.aQuery join
BTable.BQuery on ((joinTable, bTable) => join._1.id === _.AId && bTable.condition === "b1") join
BTable.BQuery on ((joinTable, bTable) => join._1.id === _.AId && bTable.condition === "b2")
} yield (a, b1Opt, b2Opt)
and I am trying something like this
val filterB = query {
case (a, b1Opt, b2Opt) => {
bStatus match {
case "delete" => b1Opt.map(b1 => b1.status === "delete") || b1Opt.map(b2 => b2.status === "delete")
}
}
}
From what you've described, the two successive left joins of table B on table A's id should translate to something similar to the following:
val joinQuery = for {
((a, b1), b2) <- tableA joinLeft tableB on ( (x, y) =>
x.id === y.aId && y.condition === "b1" )
joinLeft tableB on ( (x, y) =>
x._1.id = y.aId && y.condition === "b2" )
} yield (a, b1, b2)
And a where condition of B1.status = 'delete' and B2.status = 'delete' should look like this:
val filterB = joinQuery.filter{ case (_, b1, b2) =>
b1.filter(_.status === "delete").isDefined && b2.filter(_.status === "delete").isDefined
}
Note that with the left joins, b1 and b2 are wrapped in Option, hence the using of isDefined for the and operation.
As another side note, it might be worth considering to filter table B with B.condition = 'b?' to a reduced B1 and B2 before performing the left joins.

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.

How to get multiple sums that are subqueries

Im using Linqpad to test out my EF query and I cant seem to get my end result to include a few extra columns that represent sums of a field based on different conditions
StorePaymentInvoices table contains a FK over to CustomerStatementBatchPayments. So I need to sum the CustomerStatementBatchPayment.net field if there is a corresponding value in StorePaymentInvoices
Getting the sums is turning out to be a real mess. Any suggestions?
Sometimes what is hard to do in one statement, ends up being easier done in multiple steps.
var retval = (
from a in CustomerStatementBatches
join b in CustomerStatementBatchPayments on a.ID equals b.CustomerStatementBatchID into grp1
from c in grp1
where a.CustomerStatementID == StatementId
group c by c.CustomerStatementBatchID into grp2
from e in grp2
select new {
StatementId = e.CustomerStatementBatch.CustomerStatementID,
BatchId = e.CustomerStatementBatchID,
Applied = CustomerStatementBatchPayments.Where(csbp => !StorePaymentInvoices.Select (pi => pi.CustomerStatementBatchPaymentID ).ToList().Contains(e.ID)).Sum (csbp => csbp.Net )
}
).ToList();
retval.Dump();
[ UPDATE 1]
This is what Ive done to get the "conditional" sum values and I seem to be getting the correct numbers. The resulting SQL that it generates is kinda ugly, but executes in < 1 second.
var retval1 = (
from a in CustomerStatementBatches
join b in CustomerStatementBatchPayments on a.ID equals b.CustomerStatementBatchID into grp1
from c in grp1
where a.CustomerStatementID == StatementId
group c by new { a.CustomerStatementID, c.CustomerStatementBatchID} into grp2
from e in grp2.Distinct()
select new {
StatementId = e.CustomerStatementBatch.CustomerStatementID,
BatchId = e.CustomerStatementBatchID
}
).ToList()
.Distinct()
.Select(a => new
{
StatementId = a.StatementId,
BatchId = a.BatchId,
AppliedTotal = (from b in CustomerStatementBatchPayments.Where(r => r.CustomerStatementBatchID == a.BatchId)
join c in StorePaymentInvoices on b.ID equals c.CustomerStatementBatchPaymentID
group b by b.CustomerStatementBatchID into g1
from d in g1
select new{ Total = (decimal?)d.Net}).DefaultIfEmpty().Sum (at => (decimal?)at.Total ) ?? 0.0m,
Unappliedtotal = (from b in CustomerStatementBatchPayments.Where(r => r.CustomerStatementBatchID == a.BatchId)
.Where(s => !StorePaymentInvoices.Any (pi => pi.CustomerStatementBatchPaymentID == s.ID ) )
select new{ Total = (decimal?)b.Net}).DefaultIfEmpty().Sum (at => (decimal?)at.Total ) ?? 0.0m
})
.ToList();
Try this
from a in db.CustomerStatementBatches
join b in db.CustomerStatementBatchPayments
//.Where(i => ...)
.GroupBy(i => i.CustomerStatementBatchesId)
.Select(i => new {
CustomerStatementBatchesId = i.Key,
SumOfPayments = i.Sum(t => t.Net)
}
)
into tmp from b in tmp.DefaultIfEmpty()
on a.CustomerStatementBatchesId equals b.CustomerStatementBatchesId
select new
{
StatementId = a.CustomerStatementId,
BatchId = a.CustomerStatementBatchId,
Applied = ((b == null) ? 0 : b.SumOfPayments)
}

Scala Slick filter and join

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.

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