简体   繁体   中英

Hibernate Criteria Row Count of Group By Query

I am trying to fetch the results of a group by query with a hibernate criteria. As you can see in the code below, I am adding lots of projections into a ProjectionList. I have a pagination in front-end so if the client wants to fetch 20 results, I'm setting firstResult to 0 and maxResults to 20 .

I need send total row count of this query, too. However, if I use setProjection(Projections.rowCount) it will overwrite my groupby , sum , and max projections.

How can I write something like select count(*) from ( myCriteria ) ?

@Override
public SearchResult<ServiceIncomeSO> findServiceIncomesBySearch(IncomeQuery query, Pager pager) {
    Criteria criteria = createCriteria(ServiceIncome.class, "sin");
    criteria.createAlias("sin.partner", "ptr", JoinType.LEFT_OUTER_JOIN);

    if (StringUtils.isNotBlank(query.getPartnerKey())) {
        criteria.add(eq("ptr.key", query.getPartnerKey()));
    }

    if (StringUtils.isNotBlank(query.getPartnerTxt())) {
        criteria.add(disjunction() //
            .add(ilike("ptr.key", query.getPartnerTxt(), ANYWHERE)) //
            .add(ilike("ptr.name", query.getPartnerTxt(), ANYWHERE)) //
            .add(ilike("ptr.title", query.getPartnerTxt(), ANYWHERE))); //
    }

    if (CollectionUtils.isNotEmpty(query.getPartnerTypes())) {
        criteria.createAlias("ptr.partnerTypes", "partnerType", JoinType.LEFT_OUTER_JOIN);
        criteria.add(in("partnerType." + COLLECTION_ELEMENTS, query.getPartnerTypes()));
    }

    if (CollectionUtils.isNotEmpty(query.getCategories())) {
        criteria.createAlias("ptr.categories", "cat", JoinType.LEFT_OUTER_JOIN);
        criteria.add(in("cat.key", query.getCategories()));
    }

    if (query.getPeriodFromMonth() != null && query.getPeriodFromYear() != null) {
        addPeriodFrom(criteria, "sin.year", "sin.month", query.getPeriodFromYear(), query.getPeriodFromMonth());
    }

    if (query.getPeriodToMonth() != null && query.getPeriodToYear() != null) {
        addPeriodTo(criteria, "sin.year", "sin.month", query.getPeriodToYear(), query.getPeriodToMonth());
    }

    String[] fields;
    ProjectionList projection;
    if (query.getBreakout() == QueryBreakout.PARTNER) {
        projection = Projections.projectionList() //
            .add(Projections.groupProperty("sin.partner.key")) //
            .add(Projections.groupProperty("sin.year")) //
            .add(Projections.groupProperty("sin.month")) //
            .add(Projections.sum("sin.subscriberCount")) //
            .add(Projections.sum("sin.income"));//
        fields = new String[] { "partnerKey", "year", "month", "subscriberCount", "income" };
    } else {
        projection = Projections.projectionList() //
            .add(Projections.groupProperty("sin.partner.key")) //
            .add(Projections.groupProperty("sin.service.id")) //
            .add(Projections.groupProperty("sin.year")) //
            .add(Projections.groupProperty("sin.month")) //
            .add(Projections.max("sin.shortNumber")) //
            .add(Projections.sum("sin.subscriberCount")) //
            .add(Projections.sum("sin.income")) //
        ; //
        fields = new String[] { "partnerKey", "serviceId", "year", "month", "shortNumber", "subscriberCount", "income" };
    }

    criteria.setProjection(projection);
    criteria.addOrder(desc("sin.year")).addOrder(desc("sin.month"));

    criteria.setFirstResult(pager.getOffset());
    criteria.setMaxResults(pager.getMax());

    List<ServiceIncomeSO> list = ((List<Object[]>) criteria.list()).stream() //
        .map(values -> JavaLangUtils.setProperties(new ServiceIncomeSO(), fields, values)) //
        .collect(Collectors.toList()); //

    // TODO How to get count without retrieving all the data?
    int totalResults = 0;

    return new SearchResult<>(list, totalResults, pager.getMax(), pager.getOffset());
}

private void addPeriodFrom(Criteria criteria, String yearColumn, String periodColumn, int yearFrom, int periodFrom) {
    criteria.add(disjunction() // OR
        .add(and(eq(yearColumn, yearFrom), ge(periodColumn, periodFrom))) // year == yearFrom && period >= periodFrom
        .add(gt(yearColumn, yearFrom))); // year > yearFrom
}

private void addPeriodTo(Criteria criteria, String yearColumn, String periodColumn, int yearFrom, int periodFrom) {
    criteria.add(disjunction() // OR
        .add(and(eq(yearColumn, yearFrom), le(periodColumn, periodFrom))) // year == yearFrom && period <= periodFrom
        .add(lt(yearColumn, yearFrom))); // year < yearFrom
}

I can write the same query in plain SQL but cannot do it in Hibernate Criteria API.

Select Query:

SELECT 
    PARTNER_KEY, 
    SERVICE_ID, 
    INCOME_YEAR, 
    INCOME_MONTH, 
    SUM(SUBSCRIBER_COUNT), 
    SUM(INCOME) 
FROM 
    PP_SERVICE_INCOME 
WHERE 
    PARTNER_KEY = 'PART32143' 
GROUP BY 
    PARTNER_KEY, 
    SERVICE_ID, 
    INCOME_YEAR, 
    INCOME_MONTH;

Select Result:

PARTNER_KEY SERVICE_ID INCOME_YEAR INCOME_MONTH SUM(SUBSCRIBER_COUNT) SUM(INCOME) 
----------- ---------- ----------- ------------ --------------------- ----------- 
PART32143   1          2016        1            1234                  175000      
PART32143   1          2017        1            1234                  175390      
PART32143   1          2016        6            1234                  151100      
PART32143   1          2017        0            1234                  157800      
PART32143   1          2016        7            1234                  175220      
PART32143   1          2016        2            1234                  143000      
PART32143   1          2016        0            1234                  150000      
PART32143   1          2017        2            1234                  143012      
PART32143   1          2016        8            1234                  143330      

Count Query:

SELECT 
    COUNT(*) 
FROM (
    SELECT 
        PARTNER_KEY, 
        SERVICE_ID, 
        INCOME_YEAR, 
        INCOME_MONTH, 
        SUM(SUBSCRIBER_COUNT), 
        SUM(INCOME) 
    FROM 
        PP_SERVICE_INCOME 
    WHERE 
        PARTNER_KEY = 'PART32143' 
    GROUP BY 
        PARTNER_KEY, 
        SERVICE_ID, 
        INCOME_YEAR, 
        INCOME_MONTH
    );

Count Result: 9

For this, first you do group by using DetachedCriteria like this,

DetachedCriteria dcrit = DetachedCriteria.forClass(Student.class);
dcrit.setProjection(Projections.projectionList()
            .add(Projections.groupProperty("studentname").as("name")));

After grouping get the row count using criteria by adding the DetachedCriteria as a subquery for the Criteria like the following,

Criteria crit = sessionFactory.getCurrentSession().createCriteria(Student.class);
crit.add(Subqueries.propertyIn("id", dcrit));
crit.setProjection( Projections.rowCount() );
return ((Number) crit.uniqueResult()).intValue();

What you first want to do is instead of creating a Criteria for your ServiceIncome inner query, you want to create it as a DetachedCriteria as follows:

DetachedCriteria myCriteria = DetachedCriteria.forClass( 
  ServiceIncome.class, 
  "sin"
);

Then you want to create your outer criteria like you normally would using the Session and then specify the subquery as the DetachedCriteria we specified above with the desired projection.

Criteria outerCriteria = session.createCriteria( ServiceIncome.class, "osin" );
outerCriteria.add( Subqueries.propertyIn( "id", myCriteria ) );
outerCriteria.setProjection( Projections.rowCount );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM