Skip to content
catenav1.2.0
Theme
GitHub

Join

Join: JoinBy is a relational inner join between two streams of structs — the right side is indexed by key, the left side streams past it, and the joined stream is a plain Seq you keep chaining on. This example joins orders to customers, then rolls the joined stream up by country.

Run it: go run ./examples/07-join

// Join: JoinBy is a relational inner join between two streams of structs
// — the right side is indexed by key, the left side streams past it, and
// the joined stream is a plain Seq you keep chaining on. This example
// joins orders to customers, then rolls the joined stream up by country.
package main
import (
"fmt"
"maps"
"slices"
"github.com/NerdMeNot/catena"
)
type Customer struct {
ID int
Name string
Country string
}
type OrderRow struct {
ID int
Customer int
Amount int
}
var customers = []Customer{
{1, "ada", "NL"}, {2, "bob", "US"}, {3, "eve", "NL"}, {4, "kim", "JP"},
}
var orders = []OrderRow{
{101, 1, 250}, {102, 2, 90}, {103, 1, 40}, {104, 3, 700},
{105, 9, 999}, // customer 9 does not exist: dropped by the inner join
{106, 4, 120}, {107, 2, 310},
}
type Enriched struct {
Order OrderRow
Customer Customer
}
func main() {
// The join itself: left stream, right stream, a key selector for
// each side, and how to combine a matching pair.
enriched := catena.FromSlice(orders).JoinBy(
catena.FromSlice(customers),
func(o OrderRow) int { return o.Customer },
func(c Customer) int { return c.ID },
func(o OrderRow, c Customer) Enriched { return Enriched{o, c} },
)
fmt.Println("joined rows:")
for e := range enriched {
fmt.Printf(" order %d: %s (%s) — %d\n",
e.Order.ID, e.Customer.Name, e.Customer.Country, e.Order.Amount)
}
// Note order 105 is gone: no matching customer, and an inner join
// drops unmatched rows on both sides.
// The joined stream is ordinary, so aggregation composes directly:
// revenue per country, one pass over the join.
revenue := enriched.FoldBy(
func(e Enriched) string { return e.Customer.Country },
func(string) int { return 0 },
func(sum int, e Enriched) int { return sum + e.Order.Amount },
)
for _, country := range slices.Sorted(maps.Keys(revenue)) {
fmt.Printf("%s revenue: %d\n", country, revenue[country])
}
// Join key multiplicity is the relational one: duplicate keys on the
// right produce a cross product per key. Two tags per order here.
type Tag struct {
Customer int
Label string
}
tags := catena.Of(
Tag{1, "vip"}, Tag{1, "early-adopter"}, Tag{2, "trial"},
)
labeled := catena.FromSlice(orders).JoinBy(
tags,
func(o OrderRow) int { return o.Customer },
func(t Tag) int { return t.Customer },
func(o OrderRow, t Tag) string { return fmt.Sprintf("order %d is %s", o.ID, t.Label) },
).Collect()
fmt.Println("tagged:", len(labeled), "pairings")
for _, l := range labeled {
fmt.Println(" ", l)
}
}

View on GitHub