Linq Group By Sum based on a condition - sql

I have this SQL query but can't figure out how to convert it into VB.net LINQ
SELECT
clientid,
SUM(IF(`type` = 1, cost, 0)) AS advexp,
SUM(IF(`type` = 3, cost, 0)) AS genexp
FROM customerlist
WHERE clGroupId=pGroupId
GROUP BY clientid;
Table customerlist includes a field type and a field cost. The intent is to get a total of type 1 and type 3 expenses in one query.
This is what I tried:
From p In db.customerlist
Where p.clGroupId=1
Group By p.clientid,p.type Into g
Select New With { .clientid=g.clientd,.advexp=g.Sum(Function(c) If(c.type=1, cost,0)),.genexp=g.Sum(Function(c) If(c.type=3, cost,0))}
But not able to compile. The query is intended to hit the database. The error is Definition of method 'g' is not accessible in this context.

The question doesn't explain whether an ORM is used or what customerlist is. A list of customers in memory, or a DbSet ?
If this was a List, the LINQ query would be almost identical to the SQL query :
from customer in customerlist
where p.clGroupId=pGroupId
group by p.clientid into g
select new { clientid=g.Key,
advexp=g.Sum(c=>c.type==1?cost:0),
genexp=g.Sum(c=>c.type==3?cost:0)
}
Depending on the ORM and the database provider used, EF or NHibernate may be able to translate the same query into SQL

Related

Linq to sql, aggregate columns ,group by date into listview

Okay so I have a listview control with 3 columns:
Year | NetTotal | GrossTotal
also i have a table called Orders with several columns, they contain information about the order and store the ID of the Customer they belong to.
Question is: how can i query that table with (preferably) linq (the datacontext is from LinqToSql) to return the following data?
I want to search for any entry with the matching CustomerID which took place, group them by Year, Sum the Totals respectively and add them to the listview?
I now i could use lamda expressions and aggregate, its just not clear how (option infer on,db is a datacontext object,CustomerID is an int32 variable):
Dim Orders = (From order In db.Orders Where order.CustomerID = CustomerID).GroupBy(Function(p) p.Date.Year).GetEnumerator
I reckon i'd have to create an anonymous type like the following:
Dim tmpYears = From prevs In db.Orders Select New With {.CustID = prevs.CustomerID, .Year = prevs.PaymentDate.Year, .NetPurchase, .GrossPurchase}
But how do I aggregate the Purchased column in a group?
Dim CustomerOrders = From ord In db.Orders Where Ord.CustomerID = custID Select ord
Dim tot = From O in CustomerOrders Select Aggregate netTot In O Into Sum(netTot.Price * netTot.Quantity * 1+ (netTot.Discount/100))
I want to merge the two.
Any suggestions? (I've read this but i want it in Linq because its a team project and we agreed on using Linq instead of sending .ExecuteQuerys and etc to the db.Also its a LinqToSQL solution so would be better if i could make some use of it)
I can't guarantee I understand your requirements exactly, but long story short it seems you want to display orders, grouped by year, with the aggregated sums for net/gross value, where the orders match a provided CustomerID?
Sorry if the syntax is slightly out but I'm doing this freehand...
Dim results = db.Orders.Where(Function(n) n.CustomerId = customerId).GroupBy(Function(n) n.Date.Year, Function(key, values) New With {.Year = key, .NetTotal = values.Sum(Function(n) n.NetPurchase * n.Quantity * (1 + (n.Discount/100))), .GrossTotal = values.Sum(Function(n) n.GrossPurchase)})
This should provide you an anonymous type with Year, NetTotal, and GrossTotal populated as per the requirements I listed.
EDIT: Also apologies for the one liner but I'm sure you can reformat it to taste.

Problem in HQL when try to get count of a property (that is entity) and its value

In my project, I have two entity, first PaperEntity contains several properties (consisting of value types and also reference types -reference to other entities-) and second is PaperStatusEntity.
PaperEntity has a property named Result of type PaperStatusEntity (and also a property named locked of type bool)
Imagine you have near 500 papers and just 8 paper status defined in database.
I want to find how much every status is used? for example status1 used 58 times and status2 used 130 times and so on.
I write below HQL
select paper.Result, Count(paper.Result) from PaperEntity paper group by paper.Result
this hql generates below error:
Column 'Conference_PaperStatusesTable.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
generated sql is:
select paperentit0_.Result as col_0_0_, count(paperentit0_.Result) as col_1_0_, paperstatu1_.Id as Id48_, paperstatu1_.Version as Version48_, paperstatu1_.CreationTime as Creation3_48_, paperstatu1_.Portal as Portal48_, paperstatu1_.TitleCodeName as TitleCod5_48_, paperstatu1_.Enabled as Enabled48_, paperstatu1_.RefereeChoice as RefereeC7_48_, paperstatu1_.OrderIndex as OrderIndex48_, paperstatu1_.ContactMessageTemplate as ContactM9_48_ from Conference_PapersTable paperentit0_ inner join Conference_PaperStatusesTable paperstatu1_ on paperentit0_.Result=paperstatu1_.Id, Conference_PaperStatusesTable paperstatu2_ where paperentit0_.Result=paperstatu2_.Id group by paperentit0_.Result
If I try to group data with a value type property like 'Locked' (that is bool), no problems and all things are ok
also If I use Criteria instead of HQL, works truly:
IList result = NHibernateSessionManager.Instance.CurrentSession.CreateCriteria(typeof(PaperEntity))
.SetProjection(Projections.ProjectionList().Add(Projections.RowCount()).Add(Projections.GroupProperty("Result"))).List();
foreach (var item in result) {
object[] value = item as object[];
yield return new Pair<PaperStatusEntity, int>(value[1] as PaperStatusEntity, (int)value[0]);
}
HQL group by is not smart enough to project all the properties of an entity when grouping by it. You have to either:
Specify all the properties
Select just those you need (id, description, whatever)
Use a subquery to get the count
The reason why it's working with Criteria is that it selects just the id when you use Projections.GroupProperty, creating uninitialized proxies.
This can create a SELECT N+1 problem.
You need to provide a simple data type for the database engine to group by. If paper.Result is another entity you'll probably need to join to the PaperStatusEntity table.
Something like:
select paper.Result, Count(paper.Result)
from PaperEntity as paper
join paper.Result as status
group by status.Id
If PaperStatus is mapped as a component you can also access Result.Id directly.
You can try:
SELECT paper.Result, Count(paper.Result)
FROM PaperEntity paper
JOIN paper.Result as status
GROUP BY paper.Result
Note that you would rather use a "FROM PaperStatusEntity" and join the papers
Because with the actual thing you are trying to do, you may not get a count for a PaperStatusEntity if no paper use this status (this means you don't get a count=0, you just get no count)
You can also use the #Formula annotation so that, for a given status, you can do paperStatusEntity.getPaperNumber()
Just add something like
#Formula("select count() from PaperEntity paper where paper.result_id = id");
public int getPaperNumber();
(should be adapted)
(I use Hibernate but it should be the same?)

NHibernate Return Values

I am currently working on a project using NHiberate as the DAL with .NET 2.0 and NHibernate 2.2.
Today I came to a point where I had to join a bunch of entities/collections to get what I want. That is fine.
What got me was that I do not want the query to return a list of objects of a certain entity type but rather the result would include various properties from different entities.
The following query is not what I am doing but it is kind of query that I am talking about here.
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog.effectiveDate < sysdate
and catalog.effectiveDate >= all (
select cat.effectiveDate
from Catalog as cat
where cat.effectiveDate < sysdate
)
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc
My question is, in this case what type result is supposed to be returned? It is certainly not of type Order, neither is of type LineItems.
Thanks for your help!
John
you can always use List of object[] for returning data and it will work fine.
This is called a projection, and it happens any time you specify an explicit select clause that contains rows from various tables (or even aggregate / summary data from a single table).
Using LINQ you can create anonymous objects to store these rows of data, like this:
var crunchies = (from foo in bar
where foo.baz == quux
select new { foo.corge, foo.grault }).ToList();
Then you can do crunchies[0].corge for example to pull out the rows & columns.
If you are using NHibernate.Linq this will "just work".
If you're using HQL or Criteria API, then what Fahad mentioned will work. You'll get a List<object[]> as a result, and the index of the array references the order of the columns that you returned in your select clause.

getting count(*) using createSQLQuery in hibernate?

I have several sql queries that I simply want to fire at the database.
I am using hibernate throughout the whole application, so i would prefer to use hibernate to call this sql queries.
In the example below i want to get count + name, but cant figure out how to get that info when i use createSQLQuery().
I have seen workarounds where people only need to get out a single "count()" from the result, but in this case I am using count() + a column as ouput
SELECT count(*), a.name as count FROM user a
WHERE a.user_id IN (SELECT b.user_id FROM user b)
GROUP BY a.name
HAVING COUNT(*) BETWEEN 2 AND 5;
fyi, the above query would deliver a result like this if i call it directly on the database:
1, John
2, Donald
1, Ralph
...
Alternatively, you can use
SQLQuery query = session.createSQLQuery("SELECT count(*) as num, a.name as name FROM user a WHERE a.user_id IN (SELECT b.user_id FROM user b) GROUP BY a.name HAVING COUNT(*) BETWEEN 2 AND 5;";
query.addScalar("num", Hibernate.INTEGER).addScalar("name", Hibernate.STRING);
// you might need to use org.hibernate.type.StandardBasicTypes.INTEGER / STRING
// for Hibernate v3.6+,
// see https://hibernate.onjira.com/browse/HHH-5138
List<Object> result = query.list();
// result.get(2*i + 0) -> i-th row num
// result.get(2*i + 1) -> i-th row name
I'm using this in case of time-pressure, imo much faster to code then creating your own beans & transformers.
Cheers!
Jakub
cheers for the info Thomas, worked wonderful for generating objects
the problem i had with my initial query was that "count" was a reserved word :P
when i changed the name to something else it worked.
If your SQL statement looks like this SELECT count(*) as count, a.name as name... you could use setResultTransformer(new AliasToBeanResultTransformer(YourSimpleBean.class)) on your Query.
Where YourSimpleBean has the fields Integer count and String name respectively the setters setCount and setName.
On execution of the query with query.list() hibernate will return a List of YourSimpleBeans.

LINQ To SQL Paging

I've been using .Skip() and .Take() extension methods with LINQ To SQL for a while now with no problems, but in all the situations I've used them it has always been for a single table - such as:
database.Users.Select(c => c).Skip(10).Take(10);
My problem is that I am now projecting a set of results from multiple tables and I want to page on the overall set (and still get the benefit of paging at the DB).
My entity model looks like this:
A campaign [has many] groups, a group [has many] contacts
this is modelled through a relationship in the database like
Campaign -> CampaignToGroupMapping -> Group -> GroupToContactMapping -> Contact
I need to generate a data structure holding the details of a campaign and also a list of each contact associated to the campaign through the CampaignToGroupMapping, i.e.
Campaign
CampaignName
CampaignFrom
CampaignDate
Recipients
Recipient 1
Recipient 2
Recipient n...
I had tried to write a LINQ query using .SelectMany to project the set of contacts from each group into one linear data set, in the hope I could .Skip() .Take() from that.
My attempt was:
var schedule = (from c in database.Campaigns
where c.ID == highestPriority.CampaignID
select new PieceOfCampaignSchedule
{
ID = c.ID,
UserID = c.UserID,
Name = c.Name,
Recipients = c.CampaignGroupsMappings.SelectMany(d => d.ContactGroup.ContactGroupMappings.Select(e => new ContactData() { /*Contact Data*/ }).Skip(c.TotalSent).Take(totalRequired)).ToList()
}).SingleOrDefault();
The problem is that the paging (with regards to Skip() and Take()) is happening for each group, not the entire data set.
This means if I use the value 200 for the parameter totalRequired (passed to .Take()) and I have 3 groups associated with this campaign, it will take 200 from each group - not 200 from the total data from each group associated with the campaign.
In SQL, I could achieve this with a query such as:
select * from
(
select [t1].EmailAddress, ROW_NUMBER() over(order by CampaignID desc) as [RowNumber] from contacts as [t1]
inner join contactgroupmapping as [t2] on [t1].ID = [t2].ContactID
inner join campaigngroupsmapping as [t3] on [t3].ContactGroupID = [t2].GroupID
where [t3].CampaignID = #HighestPriorityCampaignID
) as [Results] where [Results].[RowNumber] between 500 and 3000
With this query, I'm paging over the combined set of contacts from each group associated with the particular campaign. So my question is, how can I achieve this using LINQ To SQL syntax instead?
To mimic the SQL query you provided you would do this:
var schedule = (from t1 in contacts
join t2 in contactgroupmapping on t1.ID equals t2.GroupID
join t3 in campaigngroupsmapping on t3.ContactGroupID = t2.GroupID
where t3.CampaignID = highestPriority.CampaignID
select new PieceOfCampaignSchedule
{
Email = t1.EmailAddress
}).Skip(500).Take(2500).ToList()
Are you trying to page over campaigns, recipients, or both?
Use a view to aggregate the results from the multiple tables and then use LINQ over the view
I think your attempt is really close; Maybe I'm missing something, but I think you just need to close your SelectMany() before the Skip/Take:
Recipients = c.CampaignGroupsMappings.SelectMany(d => d.ContactGroup.ContactGroupMappings.Select(e => new ContactData() { /*Contact Data*/ })).Skip(c.TotalSent).Take(totalRequired).ToList()
Note: added ")" after "/* Contact Data */ })" and removed ")" from after ".Take(totalRequired)"