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

SELECT ... AS alias

AS alias after any selector (GROUP, TAG, or ROOT) renames the printed tag on the way out — display-only, the document itself is untouched:

SELECT GROUP goods AS items;      ; <items>...</items>, children unchanged
SELECT TAG ItemSpec AS Item;      ; every match printed as <Item>

; composes with FOREACH — applies to whatever gets printed, WHERE'd rows
; included, and to OUTPUT * (a whole-element projection)
SELECT GROUP goods AS items
FOREACH good IN goods
    WHERE good.cost > 500
;

Only the outermost element's tag changes; children keep their real tags. For a permanent rename that edits the document, see RENAME in Mutations.

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, FIRST, LAST, ANY, ALL

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 values — 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

FOREACH v IN ItemSpec WHERE v.type = 0 OUTPUT FIRST(v.name), LAST(v.name);
; -> a,b

FOREACH v IN ItemSpec OUTPUT COUNT(*);   ; every row, nulls included
; -> 3

FOREACH v IN ItemSpec WHERE v.type = 0 OUTPUT ANY(v.cost > 25), ALL(v.cost > 5);
; -> true,true

Rules:

  • COUNT(expr) counts elements where expr is non-null. COUNT(*) is the one function that takes a literal * instead of an expression: it counts every reached row unconditionally, including ones a plain COUNT(expr) would skip for being null.
  • MIN/MAX/SUM/AVG ignore elements where expr is missing or non-numeric (same null-skipping spirit as everywhere else in the language).
  • FIRST(expr)/LAST(expr) return the first/last non-null value of expr in reach order (nulls skipped, same as MIN/MAX).
  • ANY(expr)/ALL(expr) are boolean aggregates over every reached row's truthiness (a missing attribute is falsy, same as WHERE) — nothing is skipped, so every reached row counts toward the result.
  • Zero contributing elements reports 0 for COUNT/MIN/MAX/SUM/AVG, an empty value for FIRST/LAST, false for ANY, and true for ALL (vacuously — same convention as SQL's ALL over an empty set).
  • 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.

Possible future additions: CONCAT/GROUP_CONCAT (join values with a separator), a distinct-values COUNT.

On this page