Conditional sum in slick (scala) - sql

I'm trying to rewrite following SQL into slick:
SELECT id
SUM(
if (spend > 0, 1, 0)
)
FROM items
GROUP by id
My current code looks similar to this:
items.groupBy(r => r.id).map {
case (id, group) => (id, group.map { r => if (r.spend > 0) 1 else 0 }.sum)
}
But I got following error:
polymorphic expression cannot be instantiated to expected type;
found : [R]slick.lifted.Rep[R]
required: Boolean
I also tried to use filter and length, but with no success. How can I achieve my goal?

Slick already provides solution for this (documentation):
items.groupBy(r => r.id).map {
case (id, group) => (id, group.map { r =>
Case If r.spend > BigDecimal(0.0) Then 1 Else 0
}.sum)
}

Scala ternary expression could not be transferred to slick syntax.
Simplest approach is just to simplify the query here to
SELECT COUNT(id)
FROM items
WHERE spend > 0
GROUP by id
Corresponding slick will be
items.filter(_.spend > 0).groupBy(_.id) map {
case (id, group) => (id, group.size)
}
Or you could try to get access to the if function using SimpleFunction:
def ifFun[T] = SimpleFunction.ternary[Boolean, T, T, T]("if")
items.groupBy(_.id) map {
case (id, group) => (id, group.map(r => ifFun(r.spend > 0, 1, 0)).sum)
}

Related

SQL lambda output not correct using nested select

I have 2 tables, Terms and Site_Program_Term:
Terms columns: Id, SiteId, Name, IsHoliday, DateStart, DateEnd, Year, IsActive
Site_Program_Term columns: Id, ProgName, SiteId, TermId, Year
Using the above 2 tables, I need to display output with primarily below columns
Name, StartDate, EndDate, NumberOfPrograms
I am getting the desired output but the 'NumberOfPrograms' is not showing the correct values. It should give me a count of all the Programs for a set of particular Term, Year and Site.
Thanks in advance.
Below is the base code.
var terms = db.Terms.Where(t => t.SiteId == id && t.IsActive).ToList().Select(t => new
{
Id = t.Id,
SiteId = t.SiteId,
Name = t.Name,
IsHoliday = t.IsHoliday,
DateStart = t.DateStart,
DateEnd = t.DateEnd,
Year = t.Year,
PubProgram = (
db.Site_Program_Term
.Where (spt => spt.SiteId == id && spt.Year == t.Year)
.Select(s => s).Count()
),
}).OrderBy(n => n.DateStart).ToList();
It should give me a count of all the
Programs for a set of particular Term, Year and Site.
But your Where condition only includes Year and Site, but not the Term. So
.Where(spt => spt.SiteId == id && spt.Year == t.Year)
should actually be:
.Where(spt => spt.SiteId == id && spt.Year == t.Year && spt.TermId == t.Id)
Note that if you are by any chance using Entity Framework (Core) and you have properly configured navigation property on the Term class such as
public class Term
{
// ...
public List<Site_Program_Term> SiteProgramTerms { get; set; }
}
then you can rewrite your query as
var terms = db.Terms
.Where(t => t.SiteId == id && t.IsActive)
.Select(t => new
{
Id = t.Id,
SiteId = t.SiteId,
Name = t.Name,
IsHoliday = t.IsHoliday,
DateStart = t.DateStart,
DateEnd = t.DateEnd,
Year = t.Year,
PubProgram = t.SiteProgramTerms.Where(spt => spt.SiteId == id && spt.Year == t.Year).Count()
})
.OrderBy(n => n.DateStart)
.ToList();
Note that this time you don't need to include spt.TermId == t.Id condition, because SiteProgramTerms property contains only site program terms with TermId that matches the Id of the Term. Also note that the first .ToList() should be omitted, because it causes query to be sent to the SQL server as multiple SELECT queries (as opposed to just one SELECT when .ToList() is not present).

Constructor can not be instantiated Slick Scala

I was trying to convert a query from SQL into Scala code with Slick, but I have got a compiler error in filter clause: constructor cannot be instantiated to expected type.
My code in Slick:
val subquery = (for {
pit <- PassInTripTable.table
t <- TripTable.table if pit.tripNoFk === t.tripNo
} yield (pit, t))
.map{ case (pit, t) => ( pit, Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo) )}
.groupBy(_._1.idPsgFk)
.filter{ case ((pit, count), group) => ( group.map(_._2).countDistinct === 1)}
.map(_._1)
val query = PassengerTable.table.filter(_.idPsg in subquery).map(_.name)
db.run(query.result)
The query in SQL itself:
select name from passenger
where id_psg in
(
select id_psg from trip t,pass_in_trip pit
where t.trip_no=pit.trip_no
group by id_psg
having count(distinct case when town_from<=town_to then town_from+town_to else town_to+town_from end)=1
)
I would be very grateful if someone helped me to find an error.
From looking at your code, it looks like the type you are matching on is not supposed to be "((pit, count), group)".
groupBy in Slick only returns a collection of Tuple2s.
http://slick.lightbend.com/doc/3.0.0/queries.html
So, the filter might look something like...
.filter{ case (pit, count) => ( count.map(_._2).countDistinct === 1)}
The problem is that Slick .groupBy requires a .map call with aggregating functions afterwards. You can find detailed information here.
So, try this:
.groupBy(_._1.idPsgFk)
.map{ case (key, group) => (key, group.map(_._2).countDistinct)}
.filter{ case (_, count) => count === 1}
.map(_._1)
P.S.
I've also found "bad smells" in your code. You get pairs as a result of for-comrehension, but it looks like standard join would be more appropriate here (and more efficient), something like:
PassInTripTable.table.join(TripTable.table).on(_.tripNoFk === _.tripNo)
.map{ case (pit, t) => ...}
And why would you use such condition:
Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo)? Its branches are the same, so equals to t.townFrom ++ t.townTo.

Subqueries, Having and GroupBy in Slick

I am currently learning Slick. I was trying to translate this query from SQL into Scala:
SELECT name FROM Passenger
WHERE ID_psg in
(SELECT ID_psg FROM Pass_in_trip
GROUP BY place, ID_psg
HAVING count(*)>1)
But I have only managed to write something like this, and it gives compile errors:
PassengerTable.table.filter(_.idPsg in (PassInTripTable.table.map(_.idPsgFk)))
.filter(PassengerTable.table.count(_.name) > 1)
.map(_.name)
I do not really know how to apply count and having on queries in Slick.
So I would be really grateful for some help.
Try
val subquery = PassInTripTable.table.groupBy(p => (p.place, p.idPsgFk))
.map { case ((place, id), group) => (id, group.length) }
.filter { case (id, count) => count > 1 }
.map { case (id, count) => id }
val query = PassengerTable.table.filter(_.idPsg in subquery).map(_.name)
val action = query.result
db.run(action)
http://slick.lightbend.com/doc/3.0.0/sql-to-slick.html#groupby
http://slick.lightbend.com/doc/3.0.0/sql-to-slick.html#subquery
http://slick.lightbend.com/doc/3.0.0/sql-to-slick.html#having

How to convert this SQL to LINQ or Lambda expression?

here's my sql query below:
Can you guys help me to convert this to a much cleaner one??
SELECT [PurchaseRequestID], [ProjectID],[FullName]
FROM PurchaseRequest
WHERE [PurchaseRequestID] IN
(SELECT [PurchaseRequestID] FROM PurchaseRequestDetail )
AND [PurchaseRequestID] NOT IN
(SELECT [PurchaseRequestID] FROM [PurchaseOrder] )
Though i have already converted this successfuly, i think this is not readable and needs to be rewritten:
var query = from a in db.PurchaseRequests
where
(from b in db.PurchaseRequestDetails
select new
{
b.PurchaseRequestID
}).Contains(new { a.PurchaseRequestID }) &&
!(from c in db.PurchaseOrders
select new
{
c.PurchaseRequestID
}).Contains(new { a.PurchaseRequestID })
select a;
thanks
you really don't need all those anonymous objects. Use the let keyword to introduce temporary variables instead of doing operations on the subqueries directly.
from a in db.PurchaseRequests
let b = from b in db.PurchaseRequestDetails select b.PurchaseRequestID
let c = from c in db.PurchaseOrders select c.PurchaseRequestID
where b.Contains(a.PurchaseRequestID) && !c.contains(a.PurchaseRequestID)
select a;
var query = from a in db.PurchaseRequests
where
db.PurchaseRequestDetails.Any(x => x.PurchaseRequestID == a.PurchaseRequestID) &&
!db.PurchaseOrders.Any(x => x.PurchaseRequestID == a.PurchaseRequestID)
select a;
If you have navigation properties set up, you can write the query like this:
IQueryable<PurchaseRequest> query =
from purchaseRequest in myDataContext.PurchaseRequests
where purchaseRequest.PurchaseRequestDetail.Any()
where !purchaseRequest.PurchaseOrder.Any()
select purchaseRequest;
Or this lambda/method style if you prefer...
IQueryable<PurchaseRequest> query2 = myDataContext.PurchaseRequests
.Where(purchaseRequest => purchaseRequest.PurchaseRequestDetail.Any())
.Where(purchaseRequest => !purchaseRequest.PurchaseOrder.Any());

Nested selects in LINQ expression, how to?

I don't know how to work with nested selects in LINQ.
How could I convert this SQl expression to LINQ?
Select i.ID, i.Impression,
(Select COUNT(ImpressionsId)
from DiaryImpressions
where DiaryPostsId = '2' AND ImpressionsId = i.ID) as Num from Impressions i
Seriously? DiaryPostsId is a string? Oh well...
from i in context.Impressions
select new {
i.ID,
i.Impressions,
Num = (from d in context.DiaryImpressions
where d.DiaryPostsId == "2"
&& d.ImpressionsId == i.ID
select d).Count()
}
from ...
select new {
i.Id,
i.Impression,
Count = context.DiaryImpressions.Count(d => d.DiaryPostsId == 2 && d.ImpressionsId == i.Id)
}
If you map your objects properly, you can use child relations directly:
Count = i.DiaryImpressions.Count(d => d.DiaryPostsId == 2)