Relix

Problem solving

None, never, missing

Grain: one row per entity · Class: Absence · Signals: none, never, no, without, missing, not in, orphaned · Operators: ▷, −

The problem

"Which customers have never placed an order? Which catalogue items are not stocked at all?"

How to recognise it

The question says none, never, no, without, missing, not in, or orphaned. It keeps an entity when no matching row exists — the mirror of existence. Two operators express it, and which one you want depends on what you already have:

The trap that defines this class is NULL: absence and unknown are different, and a careless test conflates them.

The data

Customers := [
| customer |
|----------|
| Ann      |
| Bo       |
| Cy       |
| Dee      |
];

Orders := [
| order_id | customer | amount |
|----------|----------|--------|
| 1        | Ann      | 120    |
| 2        | Ann      | 40     |
| 3        | Bo       | 200    |
| 4        | Cy       | 30     |
];

Dee has never ordered.

Recipe 1: never (anti-join)

▷ keeps the left rows with no matching right row. Which customers have never ordered?

Query
query { Customers ▷ Customers.customer = Orders.customer Orders };
Result
 customer
 ────────
 Dee
(1 row)

"None over £100" is not the same as "never ordered". No order over £100 means everyone except the customers who have at least one such order — so build the positive set and take it away. Ann (120) and Bo (200) qualify as big spenders; the rest do not:

Query
BigSpenders := { δ (π customer (σ amount > 100 (Orders))) };
query { Customers − BigSpenders };
Result
 customer
 ────────
 Cy
 Dee
(2 rows)

Note the shape: none is everyone minus the ones with at least one. Reaching straight for σ amount ≤ 100 would be wrong — it would keep Cy's small order but say nothing about Dee, and it would wrongly admit a customer who has both a large and a small order.

Recipe 2: missing from a reference list (−)

When you have two lists of the same kind of thing — what should be there and what is — the entries missing from the actual list are the set difference. Both sides must be projected to the same heading first.

Query
Catalogue := [
| sku |
|-----|
| A   |
| B   |
| C   |
| D   |
];

Stocked := [
| sku | qty |
|-----|-----|
| A   | 5   |
| C   | 0   |
| D   | 2   |
];

query { π sku (Catalogue) − π sku (Stocked) };
Result
 sku
 ───
 B
(1 row)

Missing is not the same as present-but-empty. C is stocked with a quantity of zero — it has a row, so it is not in the difference. "Not stocked" (B) and "out of stock" (C) are different questions:

Query
query { π sku (σ qty = 0 (Stocked)) };
Result
 sku
 ───
 C
(1 row)

Variations

Pitfalls

Check it