xsql
Language

SELECT and OUTPUT

Querying elements and shaping printed rows.

SELECT never modifies a document — it only prints.

SELECT GROUP

; print the whole group as-is
SELECT GROUP goods;

; without FOREACH, SELECT loops implicitly over the group's children
SELECT GROUP goods OUTPUT id, level;

; with FOREACH: filter and shape rows explicitly
SELECT GROUP goods
FOREACH good IN goods
    WHERE good.cost > 500
    OUTPUT good.id, good.cost, good.cost * 2 AS double_cost
;

SELECT TAG / SELECT ROOT

SELECT TAG t prints every element with tag t, at any depth; SELECT ROOT drops the tag filter and prints every element in the document, any tag — see Group vs. Tag vs. Root:

SELECT TAG ItemSpec;   all ItemSpec elements, any depth
SELECT ROOT;           every element in the document, any tag

OUTPUT

OUTPUT is the emission point — like a SQL column list. It's evaluated when execution reaches it, and prints a flat element containing only the cited attributes/expressions:

  • OUTPUT * — the whole current element. This is the implicit default of a SELECT loop that has no OUTPUT at all.
  • OUTPUT good.id, good.cost — only those attributes.
  • OUTPUT good.cost * 2 AS double_cost — a computed expression needs AS to name its output column; bare attribute references don't.
  • A missing attribute is silently omitted from the row (not printed as empty) — contrast with WHERE, where a missing attribute evaluates as false.

OUTPUT also works inside a plain FOREACH (not just SELECT loops), and inside nested loops — where it joins scopes from multiple levels into one flat row:

FOREACH office IN offices
    FOREACH member IN office
        WHERE member.cost > 5
        OUTPUT office.id AS office, member.name, member.cost
;

Here each printed row combines the outer office element with the inner member element currently being visited.

Aggregate OUTPUT: COUNT, MIN, MAX, SUM, AVG

A regular OUTPUT prints one row per element the loop reaches. An aggregate OUTPUT instead summarizes every reached element into a single line of comma-joined plain numbers — no XML wrapper:

FOREACH v IN ItemSpec WHERE v.type = 0 OUTPUT COUNT(v.id);
; -> 2

FOREACH v IN ItemSpec WHERE v.type = 0
    OUTPUT MIN(v.cost), MAX(v.cost), SUM(v.cost), AVG(v.cost)
;
; -> 10,30,40,20

Rules:

  • COUNT(expr) counts elements where expr is non-null; MIN/MAX/SUM/ AVG ignore elements where expr is missing or non-numeric (same null-skipping spirit as everywhere else in the language).
  • Zero contributing elements reports 0 for every function, not an error.
  • An aggregate OUTPUT must be the loop's only OUTPUT — mixing it with a plain attribute/expression, or with a second OUTPUT, is a parse error, since there's no single row shape that could satisfy both a per-element projection and a whole-loop summary.
  • Aliases (AS name) are accepted for readability but aren't printed — a single summary row has nothing to disambiguate.
  • Aggregate functions are only valid as a direct OUTPUT item, not inside WHERE or a SET expression.

COUNT/MIN/MAX/SUM/AVG cover the common cases. Possible future additions: FIRST/LAST (first/last matching value), CONCAT/ GROUP_CONCAT (join values with a separator), a distinct-values COUNT.

On this page