select()
,
in any of the forms already mentioned, can be preceded by a call to
group(
gexprn0
,
gexprn1
,
...)
,
where each gexprni
is an abstract_mapper<
T
>
for some mapped type T
.
E.g.:
const query<double> sums_per_y = points .group(points->y).select(sum(points->x));
group(
...)
is quince's counterpart of SQL's GROUP
BY
clause.
If q
is the query to which the group
/select
operation is applied, then:
q
's value mapper is visible
to each of the expressions gexprni
that
are passed to group(
...)
.
q
's value mapper is not
visible to each of the expressions exprni
that are passed to select(
...)
.
However:
q
's value mapper is visible to any expression,
within an exprni
, that is a call of an
aggregate function, and
gexprni
are visible to each exprni
.
The example above demonstrates 3. The following demonstrates 4:
const query<float> unique_ys = points .group(points->y).select(points->y);
And the next demonstrates 2, i.e. something that group(
...)
prevents you from doing:
const query<float> nonsense = points .group(points->y).select(points->x); // Wrong
On the other hand, here is something group(
...)
allows you to do, which would have been illegal without group(
...)
(because one argument to select(
..)
uses an aggregate function and the other
doesn't):
const query<std::tuple<float, double>> histogram = points .group(points->y).select(points->y, sum(points->x));
It doesn't.
q
.group(
gexprn0
,
gexpr1
,
...).select(
exprn0
,
exprn1
,
...)
or q
.group(
gexprn0
,
gexpr1
,
...).select<
C
>(
exprn0
,
exprn1
,
...)
returns a query of the same type, and with the same value type and value
mapper, as it would if you deleted .group(
gexprn0
,
gexpr1
,
...)
.
(See the range of cases in the preceding sections.)
Imagine that we executed q
, and divided the
output records into sets, such that two records are in the same set if
and only if each gexprni
evaluates to equal
values for both records.
Then, when q
.group(
gexprn0
,
gexpr1
,
...).select(
exprn0
,
exprn1
,
...)
(or q
.group(
gexprn0
,
gexpr1
,
...).select<
C
>(
exprn0
,
exprn1
,
...)
)
is executed, the process differs from the execution of an ungrouped
select(
...)
in two ways:
exprni
s
are evaluated as follows:
exprni
does not use an aggregate
function, it is evaluated using an arbitrary record from s,
and
exprni
uses an aggregate function,
it is evaluated based on the totality of records in s.
Often, when we chain together quince calls, we are building queries from
queries in pipeline fashion, e.g. points.where(points->x > 0).order(points->y).limit(2).select(points->y)
.
Then each period (.
) marks
a point of loose coupling. On its left we build a query; on its right
we use that query; but the code on the right won't mind if we replace
the code on the left with something else that makes the same output.
The period in the middle of group(
...).select(
...)
is not like that. The group(
...)
call on its left does not
build a query. Its return type is an obscure class, which has no output,
and no methods other than select(
...)
.
My advice is: treat group(
...).select(
...)
as indivisible.
The pipeline metaphor is still valid; we just need to stipulate that
each section of pipe stretches from the creation of one query to the
creation of another. So group(
...).select(
...)
counts as one pipe-section, not two.