PrevUpHomeNext

With group(...)

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.

How it affects visibility

If q is the query to which the group/select operation is applied, then:

  1. q's value mapper is visible to each of the expressions gexprni that are passed to group(...).
  2. q's value mapper is not visible to each of the expressions exprni that are passed to select(...). However:
  3. q's value mapper is visible to any expression, within an exprni, that is a call of an aggregate function, and
  4. The 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));
How it affects the query type, value type, and value mapper

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.)

How it affects output

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:

What about compositionality?

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.


PrevUpHomeNext