The FOREACH
function performs a computation on every member of a set or sets and assembles the results into a set.
FOREACH
may be used in any context within an EQL statement that accepts expressions: LET
, SELECT
, row function or aggregator arguments, WHERE
, HAVING
, or ORDER BY
. Because FOREACH
always evaluates to a set, the context must accept set-typed expressions, or EQL will signal a checking error.
Syntax
FOREACH
function is:
FOREACH id_1 IN set_1[, id_n IN set_n] RETURN(expression)where:
expression
is an EQL expression. The expression must be enclosed within parentheses and the RETURN
keyword is required. The function must have only one RETURN
regardless of the number of id parameters are used.At a minimum, you must specify one identifier/set pair (id IN
set) and the RETURN
expression.
Scope and Shadowing
FOREACH
binds the id_1 through id_n identifiers within the RETURN
expression; they are not in scope in the universes set_1 through set_n. These bindings shadow any other bindings for id_1 through id_n that may be in scope at the point of the FOREACH
function.
EVERY
and SOME
quantifiers, EQL does not allow references to these bound variables to be qualified with data-source aliases. For example, in this statement:
RETURN results AS SELECT Source.x AS x, FOREACH x IN {1, 2}, y in {3, 4} RETURN (results.x + Source.y) AS vals FROM SourceEQL interprets the reference to results.x (in the
FOREACH
RETURN
expression) as a reference to the x defined by the SELECT
clause (that is, as an alias for Source.x and not as a reference to the x bound by FOREACH
). Similarly, EQL interprets the reference Source.y as a reference to the attribute y in Source.RETURN results AS SELECT Source.x AS x, FOREACH x IN {1, 2}, y in {3, 4} RETURN (x + y) AS vals FROM Sourcethen EQL interprets x and y (in the
FOREACH
RETURN
expression) as references to the FOREACH
-bound identifiers, even though x and y are already in scope from the earlier SELECT
and from the data source. Therefore, vals always has the value {4, 5, 6}.Types
FOREACH
syntax, set_1 through set_n must be a set data type (such as mdex:string-set
); EQL signals an error otherwise. The corresponding x_1 through x_n identifiers must have the type of the elements of these sets. To illustrate, consider this example:
FOREACH x IN {1, 2}, y IN {'abc', 'def'} RETURN (x + y)Here, the first universe is a set of integers, and thus x has type integer within the
RETURN
expression. Similarly, the second universe is a set of strings, so y has type string. As a result, the RETURN
expression x + y is thus ill-typed (and EQL signals an error accordingly).If the RETURN
expression has atomic type t, then the entire FOREACH
expression has type "set of t". Therefore, if the RETURN
expression has type integer, then the containing FOREACH
expression has type integer set.
If, on the other hand, the RETURN
expression has a set type, then the FOREACH
expression has the same type as the RETURN
expression. (This corresponds to the case where FOREACH
takes the union of the values of the RETURN
expression.) So, if the RETURN
expression produces a set of string, then the FOREACH
expression does also.
FOREACH and aggregation
FOREACH
expressions may appear in both pre-grouping and post-grouping computation (including WHERE
, HAVING
, and ORDER BY
clauses). They interact with aggregations in much the same way that quantifier expressions (EVERY
and SOME
) do:
FOREACH
expressions can appear inside aggregator arguments:
RETURN results AS SELECT SET_UNIONS(FOREACH x IN e RETURN(b)) AS unions FROM Source GROUP
FOREACH
and its bound variable. That is, the following is invalid, and EQL signals an error accordingly:
FOREACH x IN e RETURN(SUM(x))
Operational details
FOREACH
works, we start with a simple example:
FOREACH x IN {1, 2, 3} RETURN(x * 2 + 1)
This expression evaluates to the set {3, 5, 7}. This is because, notionally, EQL evaluates the RETURN
expression x * 2 + 1 once for each member of the set {1, 2, 3}, with x taking on each element of that set in turn. Finally, EQL assembles the results of these evaluations into another set.
Because the universe and result are both sets, you cannot specify the order in which the traversal visits the elements of the universe, or the order in which the result values appear in the final set.
FOREACH
to iterate over multiple sets:
FOREACH x IN {1, 2, 3}, y IN {40, 50, 60} RETURN(x + y)This expression evaluates to the set {41, 42, 43, 51, 52, 53, 61, 62, 63}. Here, EQL evaluates the
RETURN
expression x + y for all possible combinations of values x and y from the two sets:
x = 1, y = 40: x + y = 41 x = 2, y = 40: x + y = 42 x = 3, y = 40: x + y = 43 x = 1, y = 50: x + y = 51 ... x = 3, y = 60: x + y = 63
FOREACH
expression may be smaller than the universe (or the cross product of the universes), as illustrated by these two examples:
FOREACH x IN {-1, 1, 2} RETURN (ABS(x)) // returns {1, 2} FOREACH x IN {'3', '4', 'y'} RETURN (TO_STRING(x)) // returns {3, 4}In the first example, ABS(-1) = ABS(1) = 1, so the final set contains only two elements; the value 1 may not appear twice. In the second example, TO_INTEGER('x') is NULL, so this value does not appear in the final set.
RETURN
expression itself produces a set (rather than a single value), then FOREACH
evaluates as described above, but computes the union of all of the RETURN
sets as the final result. For example:
FOREACH x in {3, 4, 5}, y IN {-1, 0, 2} RETURN ({x + y, x - y})
// Note: "body" is the RETURN expression | x | y | body | ------------------ | 3 | -1 | {2,4} | | 3 | 0 | {3} | | 3 | 2 | {1,5} | | 4 | -1 | {3,5} | | 4 | 0 | {4} | | 4 | 2 | {2,6} | | 5 | -1 | {4,6} | | 5 | 0 | {5} | | 5 | 2 | {3,7} | ------------------
Computing the union of all of the sets in the "body" column produces the final result.
| xs | ys | ----------------- | {1,2} | {3} | | {} | {4,5} | | {6} | {} | -----------------
RETURN results AS SELECT xs, ys, FOREACH x IN xs, y IN ys RETURN (x + y) AS zs FROM InputState
| xs | ys | zs | ------------------------- | {1,2} | {3} | {4,5} | | {} | {4,5} | {} | | {6} | {} | {} | -------------------------
FOREACH Examples
FOREACH
expression, as only one multi-assign integer attribute (Score) is used and the RETURN
expression just returns the values of each Score set:
RETURN Results AS SELECT WineID AS id, FOREACH x IN Score RETURN(x) AS ratings FROM WineState ORDER BY id
RETURN Results AS SELECT WineID AS id, FOREACH x IN Body, y IN Flavors RETURN (CONCAT(x, ' ', y)) AS bodyflavor FROM WineState WHERE IS_NOT_EMPTY(Body) AND IS_NOT_EMPTY(Flavors) ORDER BY id
Note that the WHERE
clause uses two IS_NOT_EMPTY
functions to prevent empty sets being selected.
LET
and FOREACH
, and also uses the EXTRACT
function in the RETURN
expression:
RETURN Results AS LET (FOREACH d IN ShipDate RETURN (EXTRACT(d, YEAR))) AS yearSet SELECT SET(Price) AS prices FROM WineState GROUP BY MEMBERS(yearSet) AS shipyear
RETURN
expression, and it shadows any other attribute by the same name within that expression. Note that, because yearSet is defined in a LET
clause rather than a SELECT
clause, it will not appear in the statement's results.