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 Source
EQL 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 Source
then 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.