Mauricio's response gets you most of the way there, but I wanted to
add a little detail: The keyword 'preserve' can be added before any
join variable to preserve all the elements from that input, even if
they fail the join condition. (I cannot remember what we do with any
nulls in the input, but most of the time, as in this case, it is
irrelevant.) The 'into' clause will be fired with a 'null' for that
input variable. What Mauricio wrote is a full outer join of the two
inputs. A left outer join (or is it a right outer -- I can never
remember, which is why we don't use that syntax!) is uses just one
'preserve':
X = [
{ attr: 'a', value: 1 },
{ attr: 'c', value: 2 },
];
Y = [
{ attr: 'a' },
{ attr: 'b' }
];
join x in X,
preserve y in Y
where x.attr == y.attr
into { y.attr, value: firstNonNull( x.value, -1 ) }
;
[ { "attr": "b", "value": -1 },
{ "attr": "a", "value": 1 } ]
Notice that "c" was not returned the in result. The following
illustrates the difference:
join preserve x in X,
preserve y in Y
where x.attr == y.attr
into { firstNonNull(x,y).attr, value: firstNonNull( x.value, -1 ) }
;
[ { "attr": "b", "value": -1 },
{ "attr": "c", "value": 2 },
{ "attr": "a", "value": 1 } ]