HQL and grouping - nhibernate

After much problems with using 'group by' in linq2nhibernate, I have tried to switch to HQL, but I am struggeling with a simple example.
I have the following table (ForumThreadRatings):
I would like to retrieve a list of the highest rated forum threads, which means I need to do a sum with the positive column and a group by the forumthread. I have tried for an example just to do a simple group by in HQL with no luck:
select ftr.ForumThread from ForumThreadRating ftr group by ftr.ForumThread
But I receive the following error:
Column 'ForumThreads.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
What might I be missing?

From the docs:
NHibernate currently does not expand a grouped entity, so you can't write group by cat if all properties of cat are non-aggregated. You have to list all non-aggregated properties explicitly.
In any case, that exact query can be accomplished by:
select distinct ftr.ForumThread from ForumThreadRating ftr
But of course you probably need to sum or count something, so you'll need to explicitly aggregate the properties.
Update: here's how to get the top 10 threads:
var topThreads = session.CreateQuery(#"
select (select sum(case
when rating.Positive = true then 1
else -1
end)
from ForumThreadRating rating
where rating.ForumThread = thread),
thread
from ForumThread thread
order by 1 desc
")
.SetMaxResults(10)
.List<object[]>()
As you can see, this query returns a list of object[] with two elements each: [0] is the rating and [1] is the ForumThread.
You can get just the ForumThreads using:
.Select(x => (ForumThread)x[1]);
Or project them into a DTO, etc.

Related

Agregating a subquery

I try to find what I missed in the code to retrieve the value of "Last_Maintenance" in a table called "Interventions".
I try to understand the order rules of SQL and the particularities of subqueries without success.
Did I missed something, something basic or an important step?
---Interventions with PkState "Schedule_Visit" with the Last_Maintenance aggregation
SELECT Interventions.ID AS Nro_Inter,
--Interventions.PlacesList AS Nro_Place,
MaintenanceContracts.Num AS Nro_Contract,
Interventions.TentativeDate AS Schedule_Visit,
--MaintenanceContracts.NumberOfVisits AS Number_Visits_Contracts,
--Interventions.VisitNumber AS Visit_Number,
(SELECT MAX(Interventions.AssignmentDate)
FROM Interventions
WHERE PkState = 'AE4B42CF-0003-4796-89F2-2881527DFB26' AND PkMaintenanceContract IS NOT NULL) AS Last_Maintenance --PkState "Maintenance Executed"
FROM Interventions
INNER JOIN MaintenanceContracts ON MaintenanceContracts.Pk = Interventions.PkMaintenanceContract
WHERE PkState = 'AE4B42CF-0000-4796-89F2-2881527ABC26' AND PkMaintenanceContract IS NOT NULL --PkState "Schedule_Visit"
GROUP BY Interventions.AssignmentDate,
Interventions.ID,
Interventions.PlacesList,
MaintenanceContracts.Num,
Interventions.TentativeDate,
MaintenanceContracts.NumberOfVisits,
Interventions.VisitNumber
ORDER BY Nro_Contract
I try to use GROUP BY and HAVING clause in a sub query, I did not succeed. Clearly I am lacking some understanding.
Output
The output of "Last_Maintenance" is the last date of entire contracts in the DB, which is not the desirable output. The desirable output is to know the last date the maintenance was executed for each row, meaning, for each "Nro-Contract". Somehow I need to aggregate like I did below.
In opposition of what mention I did succeed in another table.
In the table Contracts I did had success as you can see.
SELECT
MaintenanceContracts.Num AS Nro_Contract,
MAX(Interventions.AssignmentDate) AS Last_Maintenance
--MaintenanceContracts.Name AS Place
--MaintenanceContracts.StartDate,
--MaintenanceContracts.EndDate
FROM MaintenanceContracts
INNER JOIN Interventions ON Interventions.PkMaintenanceContract = MaintenanceContracts.Pk
WHERE MaintenanceContracts.ActiveContract = 2 OR MaintenanceContracts.ActiveContract = 1 --// 2 = Inactive; 1 = Active
GROUP BY MaintenanceContracts.Num, MaintenanceContracts.Name,
MaintenanceContracts.StartDate,
MaintenanceContracts.EndDate
ORDER BY Nro_Contract
I am struggling to understanding how nested queries works and how I can leverage in a simple manner the queries.
I think you're mixed up in how aggregation works. The MAX function will get a single MAX value over the entire dataset. What you're trying to do is get a MAX for each unique ID. For that, you either use derived tables, subqueries or windowed functions. I'm a fan of using the ROW_NUMBER() function to assign a sequence number. If you do it correctly, you can use that row number to get just the most recent record from a dataset. From your description, it sounds like you always want to have the contract and then get some max values for that contract. If that is the case, then you're second query is closer to what you need. Using windowed functions in derived queries has the added benefit of not having to worry about using the GROUP BY clause. Try this:
SELECT
MaintenanceContracts.Num AS Nro_Contract,
--MaintenanceContracts.Name AS Place
--MaintenanceContracts.StartDate,
--MaintenanceContracts.EndDate
i.AssignmentDate as Last_Maintenance
FROM MaintenanceContracts
INNER JOIN (
SELECT *
--This fuction will order the records for each maintenance contract.
--The most recent intervention will have a row_num = 1
, ROW_NUMBER() OVER(PARTITION BY PkMaintenanceContract ORDER BY AssignmentDate DESC) as row_num
FROM Interventions
) as i
ON i.PkMaintenanceContract = MaintenanceContracts.Pk
AND i.row_num = 1 --Used to get the most recent intervention.
WHERE MaintenanceContracts.ActiveContract = 2
OR MaintenanceContracts.ActiveContract = 1 --// 2 = Inactive; 1 = Active
ORDER BY Nro_Contract
;

COUNT Number of properties sold against their respective types?

I'm quite new to SQL and I'm trying to figure out how I can get this result:
Type Number Sold
Detached 5
Semi-detached 2
Terrace 1
Link 8
Using a query, I have tried:
SELECT SPropertyType AS Type, COUNT (SPropertyCurrentState) AS NumberSold
FROM SaleProperty
WHERE SPropertyCurrentState = 'Sold';
But it gave me an error.
You forget to use GROUP BY.
Use this:
SELECT
SPropertyType AS Type,
COUNT (SPropertyCurrentState) AS NumberSold
FROM
SaleProperty
WHERE
Upper(SPropertyCurrentState) = 'SOLD'
GROUP BY
SPropertyType;
you need to add group by SPropertyType clause
the final SQL will look like this
SELECT
SPropertyType AS Type,
COUNT (SPropertyCurrentState) AS NumberSold
FROM SaleProperty
WHERE
SPropertyCurrentState = 'Sold'
GROUP BY
SPropertyType;

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?)

Linq Grouping - aggregate, outside of a group by

I've got a SQL query, that works as follows:
SELECT TOP 100
Max(Table_ID) as Max_ID,
Col1,
Col2,
Col3,
COUNT(*) AS Occurences
FROM myTable
GROUP BY Col1, Col2, Col3
ORDER BY Occurences DESC
How can I write an identical Linq query?
The issue is, that as soon as I apply my grouping, I cannot access the non-grouped columns Table_ID in my case.
var errors = from r in MyTable
group e by new {e.Col1, e.Col2} into g
orderby g.Count() descending
select new {MaxId = ???, Count = g.Count(), g.Key.Col1, g.Key.Col2};
Use g.Max(x => x.TableID):
var errors = from r in MyTable
group e by new {e.Col1, e.Col2} into g
orderby g.Count() descending
select new {MaxId = g.Max(x => x.TableID),
Count = g.Count(), g.Key.Col1, g.Key.Col2};
(Assuming you want the maximum within each group, of course.)
Jon's answer is good, I just want to elaborate a little on why:
The issue is, that as soon as i apply my grouping, I cannot access the non-grouped columns
r went out of scope... why is that?
The two ways of ending a query are select or group by clauses. When you add the query continuation clause into g to the group by, you are saying - the top level elements of the query are g and all variables introduced in the query up to this point are removed from scope.
If you search this msdn article for the word splice, you can see samples.
Don't confuse this use of into with join on equals into, which is a group join, not a query continuation. Group join does not remove previous variables from scope.

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.