mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
NEW: FetchCountDistinct - query can return distinct values with their counts (#1300)
* NEW: FetchCountDistinct - query can return distinct values with their counts * Have to disable asserts, because of #1298
This commit is contained in:
committed by
Rob Bygrave
parent
021d57f4b2
commit
c1b022c123
@@ -0,0 +1,27 @@
|
||||
package io.ebean;
|
||||
/**
|
||||
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public enum CountDistinctOrder {
|
||||
NO_ORDERING,
|
||||
|
||||
/** order by attribute ascending */
|
||||
ATTR_ASC,
|
||||
|
||||
/** order by attribute descending */
|
||||
ATTR_DESC,
|
||||
|
||||
/** order by count ascending and attribute ascending */
|
||||
COUNT_ASC_ATTR_ASC,
|
||||
|
||||
/** order by count ascending and attribute descending */
|
||||
COUNT_ASC_ATTR_DESC,
|
||||
|
||||
/** order by count descending and attribute ascending */
|
||||
COUNT_DESC_ATTR_ASC,
|
||||
|
||||
/** order by count descending and attribute descending */
|
||||
COUNT_DESC_ATTR_DESC,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
/**
|
||||
* Holds a distinct value with it's count.
|
||||
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class CountedValue<A> implements Serializable {
|
||||
private static final long serialVersionUID = -2267971668356749695L;
|
||||
|
||||
private final A value;
|
||||
private final long count;
|
||||
|
||||
public CountedValue(A value, long count) {
|
||||
this.value = value;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public A getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return count + ": " + value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -828,6 +828,11 @@ public interface Query<T> {
|
||||
*/
|
||||
<A> A findSingleAttribute();
|
||||
|
||||
/**
|
||||
* Return true if this is countDistinct query.
|
||||
*/
|
||||
boolean isCountDistinct();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -1272,6 +1277,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setDistinct(boolean isDistinct);
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
/**
|
||||
* Return the first row value.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
@@ -822,4 +823,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
* Simplify nested expression lists where possible.
|
||||
*/
|
||||
void simplifyExpressions();
|
||||
|
||||
/**
|
||||
* Returns the count distinct order setting.
|
||||
*/
|
||||
CountDistinctOrder getCountDistinctOrder();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.RawSql;
|
||||
import io.ebean.RawSqlBuilder;
|
||||
@@ -190,7 +191,7 @@ class CQueryBuilder {
|
||||
CQueryPlan queryPlan = request.getQueryPlan();
|
||||
if (queryPlan != null) {
|
||||
predicates.prepare(false);
|
||||
return new CQueryFetchSingleAttribute(request, predicates, queryPlan);
|
||||
return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct());
|
||||
}
|
||||
|
||||
// use RawSql or generated Sql
|
||||
@@ -201,7 +202,7 @@ class CQueryBuilder {
|
||||
|
||||
queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
|
||||
request.putQueryPlan(queryPlan);
|
||||
return new CQueryFetchSingleAttribute(request, predicates, queryPlan);
|
||||
return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,8 +550,13 @@ class CQueryBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(select.getSelectSql());
|
||||
if (query.isCountDistinct() && query.isSingleAttribute()) {
|
||||
sb.append("r1.attribute_, count(*) from (select ");
|
||||
sb.append(select.getSelectSql());
|
||||
sb.append(" as attribute_");
|
||||
} else {
|
||||
sb.append(select.getSelectSql());
|
||||
}
|
||||
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
|
||||
// add the orderBy columns to the select clause (due to distinct)
|
||||
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
|
||||
@@ -642,10 +648,15 @@ class CQueryBuilder {
|
||||
sb.append(" having ").append(dbHaving);
|
||||
}
|
||||
|
||||
if (dbOrderBy != null) {
|
||||
if (dbOrderBy != null && !query.isCountDistinct()) {
|
||||
sb.append(" order by ").append(dbOrderBy);
|
||||
}
|
||||
|
||||
if (query.isCountDistinct() && query.isSingleAttribute()) {
|
||||
sb.append(") r1 group by r1.attribute_");
|
||||
sb.append(toSql(query.getCountDistinctOrder()));
|
||||
}
|
||||
|
||||
if (useSqlLimiter) {
|
||||
// use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation
|
||||
SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform);
|
||||
@@ -657,6 +668,25 @@ class CQueryBuilder {
|
||||
|
||||
}
|
||||
|
||||
private String toSql(CountDistinctOrder orderBy) {
|
||||
switch(orderBy) {
|
||||
case ATTR_ASC:
|
||||
return " order by r1.attribute_";
|
||||
case ATTR_DESC:
|
||||
return " order by r1.attribute_ desc";
|
||||
case COUNT_ASC_ATTR_ASC:
|
||||
return " order by count(*), r1.attribute_";
|
||||
case COUNT_ASC_ATTR_DESC:
|
||||
return " order by count(*), r1.attribute_ desc";
|
||||
case COUNT_DESC_ATTR_ASC:
|
||||
return " order by count(*) desc, r1.attribute_";
|
||||
case COUNT_DESC_ATTR_DESC:
|
||||
return " order by count(*) desc, r1.attribute_ desc";
|
||||
default:
|
||||
throw new IllegalArgumentException("Illegal enum: "+ orderBy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append where or and based on the hasWhere flag.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebean.CountedValue;
|
||||
import io.ebeaninternal.api.SpiProfileTransactionEvent;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
@@ -60,18 +61,21 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
|
||||
|
||||
private final ScalarType<?> scalarType;
|
||||
|
||||
private final boolean containsCounts;
|
||||
|
||||
private long profileOffset;
|
||||
|
||||
/**
|
||||
* Create the Sql select based on the request.
|
||||
*/
|
||||
CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan queryPlan) {
|
||||
CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan queryPlan, boolean containsCounts) {
|
||||
this.request = request;
|
||||
this.queryPlan = queryPlan;
|
||||
this.query = request.getQuery();
|
||||
this.sql = queryPlan.getSql();
|
||||
this.desc = request.getBeanDescriptor();
|
||||
this.predicates = predicates;
|
||||
this.containsCounts = containsCounts;
|
||||
this.scalarType = queryPlan.getSingleAttributeScalarType();
|
||||
query.setGeneratedSql(sql);
|
||||
}
|
||||
@@ -101,7 +105,11 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
|
||||
|
||||
List<Object> result = new ArrayList<>();
|
||||
while (dataReader.next()) {
|
||||
result.add(scalarType.read(dataReader));
|
||||
Object value = scalarType.read(dataReader);
|
||||
if (containsCounts) {
|
||||
value = new CountedValue<>(value, dataReader.getLong());
|
||||
}
|
||||
result.add(value);
|
||||
dataReader.resetColumnPosition();
|
||||
rowCount++;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public final class SqlTreeBuilder {
|
||||
this.query = request.getQuery();
|
||||
this.temporalMode = SpiQuery.TemporalMode.of(query);
|
||||
this.disableLazyLoad = query.isDisableLazyLoading();
|
||||
this.subQuery = Type.SUBQUERY == query.getType() || Type.ID_LIST == query.getType();
|
||||
this.subQuery = Type.SUBQUERY == query.getType() || Type.ID_LIST == query.getType() || Type.DELETE == query.getType() || query.isCountDistinct();
|
||||
this.includeJoin = query.getM2mIncludeJoin();
|
||||
this.manyWhereJoins = query.getManyWhereJoins();
|
||||
this.queryDetail = query.getDetail();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.Expression;
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.ExpressionList;
|
||||
@@ -217,6 +218,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
|
||||
private boolean singleAttribute;
|
||||
|
||||
private CountDistinctOrder countDistinctOrder;
|
||||
|
||||
/**
|
||||
* Set to true if this query has been tuned by autoTune.
|
||||
*/
|
||||
@@ -655,6 +658,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return singleAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CountDistinctOrder getCountDistinctOrder() {
|
||||
return countDistinctOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the Id should be included in the query.
|
||||
*/
|
||||
@@ -1021,7 +1029,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
queryPlanKey = new OrmQueryPlanKey(beanDescriptor.getDiscValue(), m2mIncludeJoin, type, detail, maxRows, firstRow,
|
||||
disableLazyLoading, orderBy,
|
||||
distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions,
|
||||
temporalMode, forUpdate, rootTableAlias, rawSql, updateProperties);
|
||||
temporalMode, forUpdate, rootTableAlias, rawSql, updateProperties, countDistinctOrder);
|
||||
}
|
||||
return queryPlanKey;
|
||||
}
|
||||
@@ -1484,6 +1492,17 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmQuery<T> setCountDistinct(CountDistinctOrder countDistinctOrder) {
|
||||
this.countDistinctOrder = countDistinctOrder;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountDistinct() {
|
||||
return countDistinctOrder != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this query uses SQL DISTINCT either explicitly by the user or internally defined
|
||||
* by ebean.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.Query;
|
||||
import io.ebeaninternal.api.BindParams;
|
||||
@@ -23,7 +24,7 @@ class OrmQueryPlanKey implements CQueryPlanKey {
|
||||
OrmQueryPlanKey(String discValue, TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading,
|
||||
OrderBy<?> orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams,
|
||||
SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode,
|
||||
Query.ForUpdate forUpdate, String rootTableAlias, SpiRawSql rawSql, OrmUpdateProperties updateProperties) {
|
||||
Query.ForUpdate forUpdate, String rootTableAlias, SpiRawSql rawSql, OrmUpdateProperties updateProperties, CountDistinctOrder countDistinctOrder) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(300);
|
||||
if (type != null) {
|
||||
@@ -62,6 +63,9 @@ class OrmQueryPlanKey implements CQueryPlanKey {
|
||||
if (mapKey != null) {
|
||||
sb.append(",mapKey:").append(mapKey);
|
||||
}
|
||||
if (countDistinctOrder != null) {
|
||||
sb.append(",countdistinctoder:").append(countDistinctOrder.name());
|
||||
}
|
||||
this.maxRows = maxRows;
|
||||
this.firstRow = firstRow;
|
||||
this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey();
|
||||
|
||||
Reference in New Issue
Block a user