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:
Roland Praml
2018-03-02 11:46:41 +13:00
committed by Rob Bygrave
parent 021d57f4b2
commit c1b022c123
11 changed files with 329 additions and 83 deletions
@@ -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,
}
+33
View File
@@ -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;
}
}
+10
View File
@@ -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();