Multi-result mappings¶
A multi-result rule produces more than one output value per input row. The engine fans the source row into one CDM row per result — this is the standard pattern for "one source observation maps to several OMOP concepts" and similar 1-to-N transformations.
The [] and [N] suffixes¶
The suffix tells the parser the rule is multi-result.
| Suffix | Meaning |
|---|---|
[] |
Variable number of results — produce as many rows as matches. |
[N] |
Exactly N results expected. Fewer → row is skipped + logged. |
LoopOver declares how multi-results combine¶
When several CDM fields all carry multi-result rules, the engine
needs to know whether to zip them (i-th of one paired with i-th
of another) or take the cartesian product. LoopOver on the CDM
table is what declares this:
logic: LoopOver([:field_one, :field_two]) # zip
logic: LoopOver([:field_one], [:field_two]) # cartesian
logic: LoopOver([:field_one], [:field_two], [:f3]) # 3-way cartesian
Within a single bracketed group, all fields are paired by index — they must produce the same number of results at runtime. Across groups, every combination is generated.
The all= keyword pulls in fields that aren't multi-result but should
still be carried into every output row:
:person_id is single-valued and rides along; the
[:year_of_birth, :birth_datetime] group is zipped (i-th year paired
with i-th datetime); the [:month_of_birth] group joins as a
cartesian factor.
Worked example — multi-result UsagiMap with dependent ID generation¶
Source: intnum.csv with four integer columns. Goal: map them
through a Usagi file (which can return multiple matches per input)
and let the CDM generate person_id values that follow the
multi-result of year_of_birth.
CDM person.year_of_birth:
The [] says "every match becomes a result". AssertNot(21) runs
on each result — any result equal to 21 fails that path, leaving
only the surviving years.
CDM person.person_id:
AutoGenId takes
a list of dependency fields. With [] it produces one new id per
combined-result row — the engine sees that :year_of_birth is
multi-result and re-dispatches AutoGenId accordingly.
CDM person.birth_datetime (computed from the multi-result year):
logic: GetCDMFieldValue(:year_of_birth)[] => :year
logic: Add(1900) => :year
logic: MergeDate([:year, "year"])
GetCDMFieldValue(:year_of_birth)[] reads the multi-result of
another field on the same CDM table — this is the cleanest way to
keep two fields' multi-results in lockstep.
CDM person (table-level):
person_id is generated once per output row; year_of_birth and
birth_datetime are zipped (index-paired); month_of_birth factors
in as a cartesian dimension.
Result-index dispatch (block syntax)¶
When a multi-result rule produces a fixed number of results and each result deserves a different downstream chain, the block syntax dispatches by index:
Result [0] enters block 1, [1] block 2, [2] block 3. With []
(variable count) the same block runs against every result.
Caveats¶
- Empty results skip the row.
Rule()[]that returns zero matches drops the source row — log it with anAssertNotor a default if you need visibility. LoopOveris mandatory when a CDM table has any multi-result field, even if there is only one such field.Spawn(:array)is the new-mode replacement when the multi-result is collected as anARRAYvalue rather than fanned out immediately. See Arrays and New Mode.
See also¶
UsagiMap— the most common multi-result rule.AutoGenId— multi-result-aware ID generator.LoopOver— declares how multi-results combine.- DSL Guide → Multi-Sized Results.
Source tests¶
tests/test062/ — multi-result UsagiMap, AutoGenId with
dependencies, GetCDMFieldValue, the all= form of LoopOver.
tests/test081/ — multi-result Use()[N] and AutoGenId()[N].