ToRowCountQuery seems to ignore groupings
Asked Answered
T

1

4

I'm trying to create a rowcount-query from a regular query, but the resulting SQL seems to lack the GROUP BY resulting in a wrong count. Does anyone know what I'm doing wrong.

First the queries:

var query = Session.QueryOver<InkoopFactuurListItem>()
    .Where(i => i.KlantId == Klant.Id)
    .AndRestrictionOn(i => i.Status).IsIn(statussen)
    .SelectList(list => list
        .SelectGroup(h => h.Id).WithAlias(() => dto.Id)
        .SelectGroup(h => h.Banknummer).WithAlias(() => dto.Banknummer)
        .SelectGroup(h => h.CrediteurNaam).WithAlias(() => dto.CrediteurNaam)
        .SelectGroup(h => h.DienstType).WithAlias(() => dto.DienstType)
        .SelectGroup(h => h.DocumentId).WithAlias(() => dto.DocumentId)
        .SelectGroup(h => h.DocumentNaam).WithAlias(() => dto.DocumentNaam)
        .SelectGroup(h => h.Factuurbedrag).WithAlias(() => dto.Factuurbedrag)
        .SelectGroup(h => h.Klantnummer).WithAlias(() => dto.Klantnummer)
        .SelectGroup(h => h.Factuurbtw).WithAlias(() => dto.Factuurbtw)
        .SelectGroup(h => h.FactuurDatum).WithAlias(() => dto.FactuurDatum)
        .SelectGroup(h => h.Factuurnummer).WithAlias(() => dto.Factuurnummer)
        .SelectGroup(h => h.IMSNummer).WithAlias(() => dto.IMSNummer)
        .SelectGroup(h => h.KlantId).WithAlias(() => dto.KlantId)
        .SelectGroup(h => h.Soortfactuur).WithAlias(() => dto.Soortfactuur)
        .SelectGroup(h => h.Status).WithAlias(() => dto.Status)
        .SelectGroup(h => h.VerwerktOp).WithAlias(() => dto.VerwerktOp)
        .SelectMin(h => h.Van).WithAlias(() => dto.Van)
        .SelectMax(h => h.Tot).WithAlias(() => dto.Tot))
    .TransformUsing(Transformers.AliasToBean<InkoopFactuurListItem>());

var rowcount = query.ToRowCountQuery().FutureValue<int>();

IEnumerable<InkoopFactuurListItem> results;
if (command.Page > 0 && command.PageSize > 0)
{
    results = query
        .Skip((command.Page - 1) * command.PageSize)
        .Take(command.PageSize).Future<InkoopFactuurListItem>();
}
else
{
    results = query
        .Take(command.PageSize)
        .Future<InkoopFactuurListItem>();
}

count = rowcount.Value;

The resulting SQL:

SELECT count(*) as y0_ 
FROM vwInkoopFactuurListItem this_ 
WHERE this_.KlantId = @p0 and this_.Status in (@p1, @p2, @p3, @p4, @p5);

SELECT TOP (@p6) y0_, y1_, y2_, y3_, y4_, y5_, y6_, y7_, y8_, y9_, y10_, y11_, y12_, y13_, y14_, y15_, y16_, y17_ 
FROM (
    SELECT this_.InkoopFactuurId as y0_, this_.Banknummer as y1_, this_.CrediteurNaam as y2_, this_.DienstType as y3_, this_.DocumentId as y4_, this_.DocumentNaam as y5_, this_.Factuurbedrag as y6_, this_.Klantnummer as y7_, this_.Factuurbtw as y8_, this_.FactuurDatum as y9_, this_.Factuurnummer as y10_, this_.IMSNummer as y11_, this_.KlantId as y12_, this_.Soortfactuur as y13_, this_.Status as y14_, this_.VerwerktOp as y15_, min(this_.Van) as y16_, max(this_.Tot) as y17_,
    ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row
    FROM vwInkoopFactuurListItem this_
    WHERE this_.KlantId = @p8 and this_.Status in (@p9, @p10, @p11, @p12, @p13)
    GROUP BY this_.InkoopFactuurId, this_.Banknummer, this_.CrediteurNaam, this_.DienstType, this_.DocumentId, this_.DocumentNaam, this_.Factuurbedrag, this_.Klantnummer, this_.Factuurbtw, this_.FactuurDatum, this_.Factuurnummer, this_.IMSNummer, this_.KlantId, this_.Soortfactuur, this_.Status, this_.VerwerktOp
) as query 
WHERE query.__hibernate_sort_row > @p7
ORDER BY query.__hibernate_sort_row; ;@p0 = 1 [Type: Int64 (0)], @p1 = 'OverigMatchingInkoop' [Type: String (255)], @p2 = 'OverigMatchingVerkoop' [Type: String (255)], @p3 = 'Overig' [Type: String (255)], @p4 = 'Geboekt' [Type: String (255)], @p5 = 'Geexporteerd' [Type: String (255)], @p6 = 10 [Type: Int32 (0)], @p7 = 284590 [Type: Int32 (0)], @p8 = 1 [Type: Int64 (0)], @p9 = 'OverigMatchingInkoop' [Type: String (255)], @p10 = 'OverigMatchingVerkoop' [Type: String (255)], @p11 = 'Overig' [Type: String (255)], @p12 = 'Geboekt' [Type: String (255)], @p13 = 'Geexporteerd' [Type: String (255)]

So where is the GROUP BY in the rowcount-query?

UPDATE As it turns out, ToRowCountQuery strips the selection and groupings from the original query. So how can I do something like this:

select count(*)
from (... query ...)

The answer by Firo seems to be a step in the right direction, but I cannot use CountDistinct as I need the count over all fields, including the min/max fields as shown in the selectlist. Next to that, CountDistinct doesn't take an IProjection as argument:

//
// Summary:
//     A distinct property value count
public static CountProjection CountDistinct(Expression<Func<object>> expression);
//
// Summary:
//     A distinct property value count
public static CountProjection CountDistinct<T>(Expression<Func<T, object>> expression);
//
// Summary:
//     A distinct property value count
//
// Parameters:
//   propertyName:
public static CountProjection CountDistinct(string propertyName);

UPDATE 2

Based on the links in Firo's answer, I came up with this alternative. I modified query to be a detached QueryOver object. Next, I tried this:

var rowcount = Session.QueryOver<InkoopFactuurListItem>()
    .Select(
        Projections.Alias(
            Projections.Count(Projections.SubQuery(detachedQuery)), "count"
        )
    )
    .FutureValue<int>();

However, this results in an ArgumentOutOfRangeException:

Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Tektite answered 4/11, 2011 at 16:54 Comment(5)
I see a GROUP BY when I scroll to the right somewhere near the orbit of Mars. That's not the one you want?Scifi
If you look closely, you see not 1 but 2 queries. One with a group by and one without. They should both have a group by.Tektite
Alright! Easily overlooked. The first query is only a single count over the condition in your subquery to obtain a scalar value for the TOP clause. It is not supposed to group.Scifi
Gert, that's exactly my question. Why doesn't it group?! The grouping is in the main query by means of the selectlist, so shouldn't it be in the rowcount-query as well?Tektite
I think, that's the reason: nhibernate.jira.com/browse/NH-2411. @RichardBrown answer for same problem "Sorry for taking so long to get back to this. Unfortunately I don't think this query can be written in ICriteria, which ultimately means it also can't be done in QueryOver."Erasion
I
4

Edited:

ToRowCountQuery removes all Projections even the groupings. If you run the query yourself you will see that count(*) in a query with group by will return the count of each group not the number of groups. you have to COUNT DISTINCT all group-columns to get the number of groups.

You need someting like SELECT count(*) FROM (query) which is not directly possible. i can only think of a fragile solution where you generate the sql like shown here and then

string sql = GenerateSQL(query.UnderlyingCriteria);
rowcount = session.CreateSQLQuery(string.Format("SELECT Count(*) FROM ({0})", sql)).SetParameter("???", KlantId).FutureValue<int>();
Intersidereal answered 7/11, 2011 at 9:52 Comment(7)
Firo, that looks like what I need, but how do I go about sending the paged query and the rowcount-query to the database in one go? That was the whole idea behind ToRowCount and using Futures.Tektite
Note that I cannot just count on a single field, due to the Min and Max functions. I don't see how to do that with your suggestion.Tektite
Firo, CountDistinct doesn't take an IProjection as argument.Tektite
ah right sorry, here's another similar question with the same problem #7833300Intersidereal
I'm accepting this as the answer, as it answer my original question as to what happened to the groupings in ToRowCountQuery. I'll create a new question regarding counting the rows in a subquery with a complex grouping as that hasn't been asked and answered yet here on SO.Tektite
@Ruba unfertunatly this is a limitation of sql. you could query the grouped values alone and Count Client side. Not as efficient but should work. var groupCount = session.QueryOver<Foo>().SelectList(list => list.GroupBy(x => x.Bar).GroupBy(x => x.Baz).List().Count;Intersidereal
I've already tried this and yea it works, but it takes a very long time to execute because the number of rows is very large!Citystate

© 2022 - 2024 — McMap. All rights reserved.