org.h2.expression.AggregateDataDefault Maven / Gradle / Ivy
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.expression;
import org.h2.engine.Database;
import org.h2.expression.Aggregate.AggregateType;
import org.h2.message.DbException;
import org.h2.util.ValueHashMap;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueDouble;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
/**
* Data stored while calculating an aggregate.
*/
class AggregateDataDefault extends AggregateData {
private final AggregateType aggregateType;
private long count;
private ValueHashMap distinctValues;
private Value value;
private double m2, mean;
/**
* @param aggregateType the type of the aggregate operation
*/
AggregateDataDefault(AggregateType aggregateType) {
this.aggregateType = aggregateType;
}
@Override
public void add(Database database, int dataType, boolean distinct, Value v) {
if (v == ValueNull.INSTANCE) {
return;
}
count++;
if (distinct) {
if (distinctValues == null) {
distinctValues = ValueHashMap.newInstance();
}
distinctValues.put(v, this);
return;
}
switch (aggregateType) {
case SUM:
if (value == null) {
value = v.convertTo(dataType);
} else {
v = v.convertTo(value.getType());
value = value.add(v);
}
break;
case AVG:
if (value == null) {
value = v.convertTo(DataType.getAddProofType(dataType));
} else {
v = v.convertTo(value.getType());
value = value.add(v);
}
break;
case MIN:
if (value == null || database.compare(v, value) < 0) {
value = v;
}
break;
case MAX:
if (value == null || database.compare(v, value) > 0) {
value = v;
}
break;
case STDDEV_POP:
case STDDEV_SAMP:
case VAR_POP:
case VAR_SAMP: {
// Using Welford's method, see also
// http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
// http://www.johndcook.com/standard_deviation.html
double x = v.getDouble();
if (count == 1) {
mean = x;
m2 = 0;
} else {
double delta = x - mean;
mean += delta / count;
m2 += delta * (x - mean);
}
break;
}
case BOOL_AND:
v = v.convertTo(Value.BOOLEAN);
if (value == null) {
value = v;
} else {
value = ValueBoolean.get(value.getBoolean() && v.getBoolean());
}
break;
case BOOL_OR:
v = v.convertTo(Value.BOOLEAN);
if (value == null) {
value = v;
} else {
value = ValueBoolean.get(value.getBoolean() || v.getBoolean());
}
break;
case BIT_AND:
if (value == null) {
value = v.convertTo(dataType);
} else {
value = ValueLong.get(value.getLong() & v.getLong()).convertTo(dataType);
}
break;
case BIT_OR:
if (value == null) {
value = v.convertTo(dataType);
} else {
value = ValueLong.get(value.getLong() | v.getLong()).convertTo(dataType);
}
break;
default:
DbException.throwInternalError("type=" + aggregateType);
}
}
@Override
public Value getValue(Database database, int dataType, boolean distinct) {
if (distinct) {
count = 0;
groupDistinct(database, dataType);
}
Value v = null;
switch (aggregateType) {
case SUM:
case MIN:
case MAX:
case BIT_OR:
case BIT_AND:
case BOOL_OR:
case BOOL_AND:
v = value;
break;
case AVG:
if (value != null) {
v = divide(value, count);
}
break;
case STDDEV_POP: {
if (count < 1) {
return ValueNull.INSTANCE;
}
v = ValueDouble.get(Math.sqrt(m2 / count));
break;
}
case STDDEV_SAMP: {
if (count < 2) {
return ValueNull.INSTANCE;
}
v = ValueDouble.get(Math.sqrt(m2 / (count - 1)));
break;
}
case VAR_POP: {
if (count < 1) {
return ValueNull.INSTANCE;
}
v = ValueDouble.get(m2 / count);
break;
}
case VAR_SAMP: {
if (count < 2) {
return ValueNull.INSTANCE;
}
v = ValueDouble.get(m2 / (count - 1));
break;
}
default:
DbException.throwInternalError("type=" + aggregateType);
}
return v == null ? ValueNull.INSTANCE : v.convertTo(dataType);
}
private static Value divide(Value a, long by) {
if (by == 0) {
return ValueNull.INSTANCE;
}
int type = Value.getHigherOrder(a.getType(), Value.LONG);
Value b = ValueLong.get(by).convertTo(type);
a = a.convertTo(type).divide(b);
return a;
}
private void groupDistinct(Database database, int dataType) {
if (distinctValues == null) {
return;
}
count = 0;
for (Value v : distinctValues.keys()) {
add(database, dataType, false, v);
}
}
}