1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.expression; |
7 | |
8 | import org.h2.engine.Database; |
9 | import org.h2.message.DbException; |
10 | import org.h2.util.ValueHashMap; |
11 | import org.h2.value.DataType; |
12 | import org.h2.value.Value; |
13 | import org.h2.value.ValueBoolean; |
14 | import org.h2.value.ValueDouble; |
15 | import org.h2.value.ValueLong; |
16 | import org.h2.value.ValueNull; |
17 | |
18 | /** |
19 | * Data stored while calculating an aggregate. |
20 | */ |
21 | class AggregateDataDefault extends AggregateData { |
22 | private final int aggregateType; |
23 | private long count; |
24 | private ValueHashMap<AggregateDataDefault> distinctValues; |
25 | private Value value; |
26 | private double m2, mean; |
27 | |
28 | /** |
29 | * @param aggregateType the type of the aggregate operation |
30 | */ |
31 | AggregateDataDefault(int aggregateType) { |
32 | this.aggregateType = aggregateType; |
33 | } |
34 | |
35 | @Override |
36 | void add(Database database, int dataType, boolean distinct, Value v) { |
37 | if (v == ValueNull.INSTANCE) { |
38 | return; |
39 | } |
40 | count++; |
41 | if (distinct) { |
42 | if (distinctValues == null) { |
43 | distinctValues = ValueHashMap.newInstance(); |
44 | } |
45 | distinctValues.put(v, this); |
46 | return; |
47 | } |
48 | switch (aggregateType) { |
49 | case Aggregate.SUM: |
50 | if (value == null) { |
51 | value = v.convertTo(dataType); |
52 | } else { |
53 | v = v.convertTo(value.getType()); |
54 | value = value.add(v); |
55 | } |
56 | break; |
57 | case Aggregate.AVG: |
58 | if (value == null) { |
59 | value = v.convertTo(DataType.getAddProofType(dataType)); |
60 | } else { |
61 | v = v.convertTo(value.getType()); |
62 | value = value.add(v); |
63 | } |
64 | break; |
65 | case Aggregate.MIN: |
66 | if (value == null || database.compare(v, value) < 0) { |
67 | value = v; |
68 | } |
69 | break; |
70 | case Aggregate.MAX: |
71 | if (value == null || database.compare(v, value) > 0) { |
72 | value = v; |
73 | } |
74 | break; |
75 | case Aggregate.STDDEV_POP: |
76 | case Aggregate.STDDEV_SAMP: |
77 | case Aggregate.VAR_POP: |
78 | case Aggregate.VAR_SAMP: { |
79 | // Using Welford's method, see also |
80 | // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance |
81 | // http://www.johndcook.com/standard_deviation.html |
82 | double x = v.getDouble(); |
83 | if (count == 1) { |
84 | mean = x; |
85 | m2 = 0; |
86 | } else { |
87 | double delta = x - mean; |
88 | mean += delta / count; |
89 | m2 += delta * (x - mean); |
90 | } |
91 | break; |
92 | } |
93 | case Aggregate.BOOL_AND: |
94 | v = v.convertTo(Value.BOOLEAN); |
95 | if (value == null) { |
96 | value = v; |
97 | } else { |
98 | value = ValueBoolean.get(value.getBoolean().booleanValue() && |
99 | v.getBoolean().booleanValue()); |
100 | } |
101 | break; |
102 | case Aggregate.BOOL_OR: |
103 | v = v.convertTo(Value.BOOLEAN); |
104 | if (value == null) { |
105 | value = v; |
106 | } else { |
107 | value = ValueBoolean.get(value.getBoolean().booleanValue() || |
108 | v.getBoolean().booleanValue()); |
109 | } |
110 | break; |
111 | case Aggregate.BIT_AND: |
112 | if (value == null) { |
113 | value = v.convertTo(dataType); |
114 | } else { |
115 | value = ValueLong.get(value.getLong() & v.getLong()).convertTo(dataType); |
116 | } |
117 | break; |
118 | case Aggregate.BIT_OR: |
119 | if (value == null) { |
120 | value = v.convertTo(dataType); |
121 | } else { |
122 | value = ValueLong.get(value.getLong() | v.getLong()).convertTo(dataType); |
123 | } |
124 | break; |
125 | default: |
126 | DbException.throwInternalError("type=" + aggregateType); |
127 | } |
128 | } |
129 | |
130 | @Override |
131 | Value getValue(Database database, int dataType, boolean distinct) { |
132 | if (distinct) { |
133 | count = 0; |
134 | groupDistinct(database, dataType); |
135 | } |
136 | Value v = null; |
137 | switch (aggregateType) { |
138 | case Aggregate.SUM: |
139 | case Aggregate.MIN: |
140 | case Aggregate.MAX: |
141 | case Aggregate.BIT_OR: |
142 | case Aggregate.BIT_AND: |
143 | case Aggregate.BOOL_OR: |
144 | case Aggregate.BOOL_AND: |
145 | v = value; |
146 | break; |
147 | case Aggregate.AVG: |
148 | if (value != null) { |
149 | v = divide(value, count); |
150 | } |
151 | break; |
152 | case Aggregate.STDDEV_POP: { |
153 | if (count < 1) { |
154 | return ValueNull.INSTANCE; |
155 | } |
156 | v = ValueDouble.get(Math.sqrt(m2 / count)); |
157 | break; |
158 | } |
159 | case Aggregate.STDDEV_SAMP: { |
160 | if (count < 2) { |
161 | return ValueNull.INSTANCE; |
162 | } |
163 | v = ValueDouble.get(Math.sqrt(m2 / (count - 1))); |
164 | break; |
165 | } |
166 | case Aggregate.VAR_POP: { |
167 | if (count < 1) { |
168 | return ValueNull.INSTANCE; |
169 | } |
170 | v = ValueDouble.get(m2 / count); |
171 | break; |
172 | } |
173 | case Aggregate.VAR_SAMP: { |
174 | if (count < 2) { |
175 | return ValueNull.INSTANCE; |
176 | } |
177 | v = ValueDouble.get(m2 / (count - 1)); |
178 | break; |
179 | } |
180 | default: |
181 | DbException.throwInternalError("type=" + aggregateType); |
182 | } |
183 | return v == null ? ValueNull.INSTANCE : v.convertTo(dataType); |
184 | } |
185 | |
186 | private static Value divide(Value a, long by) { |
187 | if (by == 0) { |
188 | return ValueNull.INSTANCE; |
189 | } |
190 | int type = Value.getHigherOrder(a.getType(), Value.LONG); |
191 | Value b = ValueLong.get(by).convertTo(type); |
192 | a = a.convertTo(type).divide(b); |
193 | return a; |
194 | } |
195 | |
196 | private void groupDistinct(Database database, int dataType) { |
197 | if (distinctValues == null) { |
198 | return; |
199 | } |
200 | count = 0; |
201 | for (Value v : distinctValues.keys()) { |
202 | add(database, dataType, false, v); |
203 | } |
204 | } |
205 | |
206 | } |