Skip to content

Joining streams — combine & merge

The question for this lesson: so far each pipeline has read from one source. But real data is scattered. Orders live in one file, the product catalog in another; web logins in one stream, mobile logins in a second. How do you bring two streams together?

There are two different ways, and choosing the right one is the whole lesson:

  • combine (join two streams on a key) makes each row wider (orders + their product details).
  • merge (stack same-shaped streams) makes more rows (web logins + mobile logins, one list).
  • Use a combine node to join two streams on a key, and say what match and on_miss do.
  • Use a merge node to stack same-shaped streams into one.
  • Say when you need combine vs merge.

A combine node takes two named inputs and joins them where a key matches. This is Clinker’s real order_fulfillment join: orders enriched with details from a product catalog.

- type: combine
name: product_lookup
input:
orders: normalize_fields # two NAMED inputs (not a list)
products: products
config:
where: "orders.product_code == products.product_code" # the join key
match: first # if several products match, take the first
on_miss: null_fields # no match? keep the order, product fields null
cxl: |
emit order_id = orders.order_id
emit product_name = products.product_name
emit category = products.category
🌱 New here? — input: as a named map

A normal node has a single input: naming one upstream node (lesson 03). A combine instead takes a named map of inputs (orders: and products:), so its where and cxl can say which side a field comes from (orders.order_id vs products.product_name). The names are yours to choose; they’re just labels for the two sides.

The two knobs that decide the join’s behavior:

  • match: when more than one row on the other side matches the key, first takes one; other modes keep all matches.
  • on_miss: when nothing matches, null_fields keeps the order and leaves the product columns null (a left join). The alternative is to drop the unmatched row.

When two streams are the same shape and you just want them in one list, use merge. This is Clinker’s multi_source_session example: web and mobile logins, combined into one stream.

- type: merge
name: all_logins
inputs: [src_web, src_mobile] # a LIST — order-agnostic union

No where, no key. merge simply interleaves the rows of every input into a single stream for the next node (often an aggregate). Note the shape of the wiring is different from combine: merge takes inputs: as a list, because the streams are peers; combine takes input: as a named map, because it must tell the two sides apart.

// quick check

You have last year's sales and this year's sales in two files, same columns, and want one combined dataset to total. combine or merge?

Combining streams isn’t the only thing you can do across rows. Restructuring rows (rewriting some of them, or dropping/routing whole groups) gets its own lesson next, with the reshape and cull nodes.