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
Michał Powaga
  • 22,561
  • 8
  • 51
  • 62
Pieter
  • 3,339
  • 5
  • 30
  • 63
  • I see a `GROUP BY` when I scroll to the right somewhere near the orbit of Mars. That's not the one you want? – Gert Arnold Nov 06 '11 at 19:18
  • 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. – Pieter Nov 07 '11 at 06:43
  • 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. – Gert Arnold Nov 07 '11 at 08:06
  • 1
    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? – Pieter Nov 07 '11 at 09:54
  • I think, that's the reason: https://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." – Michał Powaga Sep 15 '14 at 20:14

1 Answers1

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>();
Community
  • 1
  • 1
Firo
  • 30,626
  • 4
  • 55
  • 94
  • 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. – Pieter Nov 07 '11 at 10:57
  • 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. – Pieter Nov 07 '11 at 11:32
  • Firo, CountDistinct doesn't take an IProjection as argument. – Pieter Nov 07 '11 at 13:12
  • ah right sorry, here's another similar question with the same problem http://stackoverflow.com/questions/7832800/total-results-of-paged-projected-nhibernate-query-with-aggregates/7834112#7834112 – Firo Nov 07 '11 at 13:38
  • 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. – Pieter Nov 08 '11 at 08:30
  • @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().SelectList(list => list.GroupBy(x => x.Bar).GroupBy(x => x.Baz).List().Count;` – Firo Nov 07 '13 at 15:38
  • 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! – Ruba Nov 11 '13 at 06:47