xsql
Language

FOREACH and WHERE

Loops, guards, nesting, scope, and BREAK.

FOREACH

FOREACH arm IN arms
    DELETE IGNORE arm.unlock_civi_science
    DELETE IGNORE arm.science
;

FOREACH v IN g iterates the group's direct children, binding each to v. Inside the loop, the loop variable, the group name, and a bare attribute name all refer to the current element — good.id, goods.id, and id are interchangeable.

WHERE

WHERE <expr> guards everything after it in the same loop body: elements that don't match are skipped for the remaining ops, not just the next one.

SELECT GROUP goods
FOREACH good IN goods
    WHERE goods.id > 52034301
;

A missing attribute participates as null, which compares as false — so elements without the attribute are silently skipped, not errored.

WHERE REQUIRED

SELECT GROUP goods
FOREACH good IN goods
    WHERE REQUIRED level >= 2
;

WHERE REQUIRED attr <cond> makes the attribute mandatory: an element without it aborts the whole run with an error, instead of being silently skipped. Use this when a missing attribute would indicate a malformed document rather than an expected absence.

Nested FOREACH

FOREACH office IN offices
    WHERE office.id = 216000
    FOREACH member IN office
        SET member.reviewed = 1
        SET office.total = office.total + member.cost
;

IN <outer var> iterates the current element's children; IN <subgroup> looks for a group by that name inside the current element. Either way, inner loops can read and write outer scopes — the innermost binding wins when names collide, but outer variables stay visible and mutable (office.total above is written from inside the member loop).

Ops written after a nested FOREACH belong to that inner loop, not the outer one — so a BREAK inside the inner loop only ends the inner loop.

BREAK

FOREACH office IN office
    WHERE office.id = 216000
    SET office.name = "New Office Name"
    BREAK;
;

Stops the (innermost) loop. UPDATE ... LIMIT 1 compiles to exactly this pattern.

A parallelism note

Loops over ≥1024 children evaluate expressions in parallel and apply mutations sequentially afterward — see Performance. Loops containing BREAK are the one exception: they always run fully sequential, since BREAK's semantics depend on visiting elements in order. You don't need to think about this when writing scripts — it's purely an execution-strategy detail, and results are identical either way.

On this page