Problem
I have a list of orders and I want the ones that meet two or three
conditions. Same shape as a SQL WHERE with AND, OR, NOT — but
as a first-class expression rather than a statement.
Setup
The shared tea-and-coffee importer dataset. Every recipe in this section uses it.
val orders = [
{ id = 1, productId = 101, quantity = 12, customer = "Bramble Cafe", region = "north", date = "2025-09-02", status = SOME "fulfilled" },
{ id = 2, productId = 204, quantity = 30, customer = "Granary Foods", region = "south", date = "2025-09-03", status = SOME "fulfilled" },
{ id = 3, productId = 103, quantity = 8, customer = "Bramble Cafe", region = "north", date = "2025-09-05", status = SOME "fulfilled" },
{ id = 4, productId = 204, quantity = 50, customer = "Ironbridge Hotel", region = "west", date = "2025-09-07", status = SOME "fulfilled" },
{ id = 5, productId = 101, quantity = 6, customer = "Kiln & Crumb", region = "east", date = "2025-09-10", status = SOME "cancelled" },
{ id = 6, productId = 202, quantity = 20, customer = "Granary Foods", region = "south", date = "2025-09-12", status = SOME "fulfilled" },
{ id = 7, productId = 103, quantity = 14, customer = "Harbour Roastery", region = "west", date = "2025-09-14", status = SOME "fulfilled" },
{ id = 8, productId = 102, quantity = 25, customer = "Ironbridge Hotel", region = "west", date = "2025-09-18", status = SOME "fulfilled" },
{ id = 9, productId = 201, quantity = 40, customer = "Bramble Cafe", region = "north", date = "2025-09-21", status = SOME "cancelled" },
{ id = 10, productId = 103, quantity = 9, customer = "Kiln & Crumb", region = "east", date = "2025-09-24", status = SOME "fulfilled" },
{ id = 11, productId = 202, quantity = 16, customer = "Harbour Roastery", region = "west", date = "2025-09-27", status = NONE },
{ id = 12, productId = 101, quantity = 22, customer = "Granary Foods", region = "south", date = "2025-09-30", status = SOME "fulfilled" }
];
Example
Bulk orders outside the north region. andalso is the same as
&& — both predicates must be true for a row to survive.
from ord in orders
where ord.quantity >= 20 andalso ord.region <> "north"
yield { ord.id, ord.customer, ord.quantity };
val it =
[{customer="Granary Foods",id=2,quantity=30},
{customer="Ironbridge Hotel",id=4,quantity=50},
{customer="Granary Foods",id=6,quantity=20},
{customer="Ironbridge Hotel",id=8,quantity=25},
{customer="Granary Foods",id=12,quantity=22}]
: {customer:string, id:int, quantity:int} list
What's happening
where is a filter step. Think of from … where … yield as the
pipeline SQL can't quite write: each clause is an expression that
takes a collection and returns one. The predicate can be any boolean
expression Morel knows how to check — equality, comparisons,
andalso, orelse, not, and any function you've defined that
returns bool.
Two things to keep straight. First, = in Morel is equality, not
assignment, so where ord.region = "north" reads correctly in both
SQL and Morel senses. <> is "not equal". Second, short-circuiting:
andalso and orelse evaluate left-to-right and stop as soon as the
answer is known, so put the cheaper predicate first if one is
expensive.
A SQL equivalent for the example, for comparison:
select id, customer, quantity
from orders
where quantity >= 20 and region <> 'north';
Variations
orelse to widen the net — either predicate is enough:
from ord in orders
where ord.region = "north" orelse ord.region = "east"
yield { ord.id, ord.region };
not wraps any boolean expression — here it negates a substring
match, which is Morel's answer to SQL's NOT LIKE '%Cafe%':
from ord in orders
where not (String.isSubstring "Cafe" ord.customer)
yield ord.customer;
See also
- Recipe 01 — First query — the simplest
from…where…yieldbefore the predicates start stacking. - Recipe 08 — Select and rename columns — the
yieldside of the same pipeline. - Recipe 10 — Group and aggregate — filter first, then summarise.