Which of your rows have a partner in the other table? Sometimes that is the whole question: you do not want the other table’s columns, only to keep the rows that appear in it. That is a question about a row, so it is written as one, and the verb that takes a question is keep.
The other table is stocked, a list of one product:
run(' sales then keep where matching(stocked, by [product])')
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-04-11
West
Widget
2
50
20
2026-08-24
North
Widget
3
75
30
2026-10-02
West
Widget
5
125
50
sales |>keep(matching(stocked, by = product))
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-04-11
West
Widget
2
50
20
2026-08-24
North
Widget
3
75
30
2026-10-02
West
Widget
5
125
50
sales >> keep(matching(stocked, by = col.product))
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-04-11
West
Widget
2
50
20
2026-08-24
North
Widget
3
75
30
2026-10-02
West
Widget
5
125
50
sales then keep where matching(stocked, by [product])
Nothing was added. The columns are the ones sales started with, and only the rows changed, which is why this is not spelled join. Negate it and you get exactly the rows the first one dropped.
run(' sales then keep where (not matching(stocked, by [product]))')
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
sales |>keep(!matching(stocked, by = product))
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
sales >> keep(~matching(stocked, by = col.product))
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
by works here exactly as it does on join, and leaving it out means the same thing: god matches on the column names both tables share. The note naming its choice arrives beside the answer, as it does for join.
That sameness is the whole point, so it extends to keys the two tables name differently. Write is between them, this table’s name first, exactly as another table does.
listed names the same products under a column of its own:
There is one thing matching guarantees that join cannot. If the other table has three rows for a product, a join gives you three rows back for each of yours. A question does not: a row either has a partner or it does not, and how many it has never reaches the answer.
collect(sales |>keep(matching(products, by = product) & revenue >100))
Error:
!
illegal: `matching` chooses whole rows, so it is the whole question `keep` asks rather than one part of it. Ask it in its own step: `then keep where matching(products, by [id]) then keep where ...`
|
2 | then keep where (matching(products, by [product]) and ([revenue] > 100))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
try: collect(sales>> keep(matching(products, by = col.product) & (col.revenue >100)))except GodError as refusal:print(refusal)
illegal: `matching` chooses whole rows, so it is the whole question `keep` asks rather than one part of it. Ask it in its own step: `then keep where matching(products, by [id]) then keep where ...`
|
2 | then keep where (matching(products, by [product]) and ([revenue] > 100))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
run(' sales then keep where matching(products, by [product]) then keep where ([revenue] > 100)')
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-01-26
West
Gadget
5
300
200
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-10-02
West
Widget
5
125
50
sales |>keep(matching(products, by = product)) |>keep(revenue >100)
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-01-26
West
Gadget
5
300
200
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-10-02
West
Widget
5
125
50
(sales>> keep(matching(products, by = col.product))>> keep(col.revenue >100))
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2025-12-22
North
Widget
6
150
60
2026-01-09
East
Widget
8
200
80
2026-01-26
West
Gadget
5
300
200
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-10-02
West
Widget
5
125
50
The two keep steps read as one sentence: first the rows with a partner, then the rows whose revenue is over 100. However long the pipeline grows, matching can only remove rows, which is the property the word exists to guarantee.
That property is what separates it from a join, because the two sentences look alike and mean different things.
sales |>keep(matching(products, by = product)) |>show_steps()
show_steps(sales >> keep(matching(products, by = col.product)))
products gets a line of its own, the same as it would under a join, and its maker column stays where it is. Nothing crossed over, and the last line says the rows can only be fewer. A join in the same place says they may multiply. That difference is the whole reason both words exist.