Query
June 4, 2026 · View on GitHub
Queries are a class of Morel expressions that operate on
collections. A typical query takes one or more collections as input
and returns a collection, but there are also variants that return a
scalar value such as a bool or a single record.
For example, the following query returns the name and job title of all employees in department 10:
from e in scott.emps
where e.deptno = 10
yield {e.ename, e.job};
ename job
------ ---------
CLARK MANAGER
KING PRESIDENT
MILLER CLERK
val it : {ename:string, job:string} bag
(Notice how this result is printed as a table. Morel automatically
uses tabular format if the value is a list of records or atomic
values, provided that you have set("output", "tabular"); in the
current session; see properties.)
If you know SQL, you might have noticed that this looks similar to a SQL query:
SELECT e.ename, e.job FROM scott.emps AS e WHERE e.deptno = 10;
There are deep similarities between Morel query expressions and SQL, which is expected, because both are based on relational algebra. Any SQL query has an equivalent in Morel, often with similar syntax.
Syntax
The formal syntax of queries is as follows.
exp → (other expressions)
| from [ scan1 , ... , scans ] step1 ... stept [ terminalStep ]
relational expression (s ≥ 0, t ≥ 0)
| exists [ scan1 , ... , scans ] step1 ... stept
existential quantification (s ≥ 0, t ≥ 0)
| forall [ scan1 , ... , scans ] step1 ... stept require exp
universal quantification (s ≥ 0, t ≥ 0)
scan → pat in exp [ on exp ] iteration
| pat = exp [ on exp ] single iteration
| val unbounded variable
step → distinct distinct step
| except [ distinct ] exp1 , ... , expe
except step (e ≥ 1)
| group exp1 [ compute exp2 ]
group step
| intersect [ distinct ] exp1 , ... , expi
intersect step (i ≥ 1)
| [ left | right | full ] join scan1 , ... , scans
join step (s ≥ 1)
| order exp order step
| skip exp skip step
| take exp take step
| through pat in exp through step
| union [ distinct ] exp1 , ... , expu
union step (u ≥ 1)
| where exp filter step
| yield exp yield step
| yieldAll exp yieldAll step
terminalStep → into exp into step
| compute exp compute step
groupKey → [ id = ] exp
agg → [ id = ] exp [ of exp ]
A query is a from, exists or forall keyword followed by one or
more scans, then followed by zero or more steps. (A forall query
must end with a require step, and a from query may end with an
into or compute terminal step.)
For example, the query
from e in scott.emps,
d in scott.depts on e.deptno = d.deptno
where e.deptno = 10
yield {d.dname, e.ename, e.job};
has two scans (e in scott.emps and
d in scott.depts on e.deptno = d.deptno)
and two steps (where e.deptno = 10 and
yield {d.dname, e.ename, e.job}).
In the following sections we will look at scans and
steps in more detail. We will focus on from for now, and
will cover exists and forall in
quantified queries.
Finally, remember that a query is an expression. You can evaluate a
query by typing it into the shell, just like any other expression.
Also, you can use a query anywhere in a Morel program that an
expression is valid, such as in a case expression, the body of a
fn lambda, or the argument to a function call. Because Morel is
strongly typed, the type of the query expression has to match where it
is being used. Most queries return a collection, but quantified
queries (exists and forall) and queries with a terminal step
(compute or into) return a scalar value, and therefore are
particularly easy to use in expressions.
Scan
A scan is a source of rows. The most common form, "id in
collection", assigns each element of collection to id in turn
and then invokes the later steps in the pipeline.
A scan is like a "for" loop in a language such as Java or Python.
The collection can have elements of any type. In SQL, the elements must be records, but in Morel they may be atomic values, lists, lists of lists, records that contain lists of records, or anything else.
(* Query over a list of integers. *) from i in [1, 2, 3, 4, 5] where i mod 2 = 0; 2 4 val it : int list
If the collection has a structured type, you can use a pattern to deconstruct it.
(* Query over a list of (string, int) pairs. *)
from (name, age) in [("shaggy", 17), ("scooby", 7)]
yield {s = name ^ " is " ^ Int.toString(age) ^ "."};
shaggy is 17.
scooby is 7.
val it : {s:string} list
Multiple scans
If there are multiple scans, the query generates a cartesian product:
from i in [2, 3],
s in ["ab", "cde"];
i s
- ---
2 ab
2 cde
3 ab
3 cde
val it : {i:int, s:string} list
If you want to add a join condition, you can append an on clause:
from e in scott.emps,
d in scott.depts on e.deptno = d.deptno
where e.job = "MANAGER"
yield {e.ename, d.dname};
dname ename
---------- -----
RESEARCH JONES
SALES BLAKE
ACCOUNTING CLARK
val it : {dname:string, ename:string} bag
(The on clause is not allowed on the first scan.)
If you want scans later in a query, use the join step.
from c in clients
where c.city = "BOSTON"
join e in scott.emps on c.contact = e.empno,
d in scott.depts on e.deptno = d.deptno
yield {c.cname, e.ename, d.dname};
cname dname ename
------ ----- ------
Apple SALES MARTIN
Disney SALES ALLEN
Ford SALES WARD
IBM SALES MARTIN
Lateral scans and nested data
Multiple scans are a convenient way of dealing with nested data.
(* Define the shipments data set; each shipment has one or
more nested items. *)
val shipments =
[{id=1, shipping=10.0, items=[{product="soda", quantity=12},
{product="beer", quantity=3}]},
{id=2, shipping=7.5, items=[{product="cider",quantity=4}]}];
(* Flatten the data set by joining each shipment to its own
items. *)
from s in shipments,
i in s.items
yield {s.id, i.product, i.quantity};
id product quantity
-- ------- --------
1 soda 12
1 beer 3
2 cider 4
val it : {id:int, product:string, quantity:int} list
Note that the second scan uses current row from the first scan (s
appears in the expression s.items). SQL calls this a lateral join
(because lateral means "sideways" and one scan is looking "sideways"
at the other scan). Lateral joins are only activated in SQL when you
use the keywords LATERAL or UNNEST, but Morel's scans and joins
are always lateral. As a result, queries over nested data are easy and
concise in Morel.
Single-row scan
A scan with = syntax iterates over a single value. While
pat = exp is just syntactic sugar for
pat in [exp], it is nevertheless a useful way to
add a column to the current row.
(* Iterate over a list of integers and compute whether
they are odd. *)
from i in [1, 2, 3, 4, 5],
odd = (i mod 2 = 1);
i odd
- -----
1 true
2 false
3 true
4 false
5 true
val it : {i:int, odd:bool} list
(* Equivalent using "in" and a singleton list. *)
from i in [1, 2, 3, 4, 5],
odd in [(i mod 2 = 1)];
i odd
- -----
1 true
2 false
3 true
4 false
5 true
val it : {i:int, odd:bool} list
Empty scan
In case you are wondering, yes, a query with no scans is legal. It produces one row with zero fields.
from;val it = [()] : unit list
You can even feed that one row into a pipeline.
from where true yield 1 + 2; 3 val it : int list
Step
A query is a pipeline of data flowing through relational operators. The scans introduce rows into the pipeline, and the steps are the relational operators that these rows flow through.
Each step has a contract with its preceding and following step: what fields does it consume, and what fields are produced. A query begins with a set of scans, and each scan defines a number of variables (usually one, unless the scan has a complex pattern).
The following query defines two fields: deptno of type int and
emp with a record type.
from deptno in [10, 20],
emp in scott.emps on emp.deptno = deptno;
(Unlike SQL, the fields of a record are not automatically unnested. If
you wish to access the job field of an employee record, then you
must write emp.job; the unqualified expression job is invalid.)
The deptno and emp fields can be consumed in a following yield
step, which produces fields deptno, job, initial:
from deptno in [10, 20],
emp in scott.emps on emp.deptno = deptno
yield {deptno, emp.job, initial = String.sub(emp.ename, 1)};
deptno initial job
------ ------- ---------
10 L MANAGER
10 I PRESIDENT
10 I CLERK
20 M CLERK
20 O MANAGER
20 C ANALYST
20 D CLERK
20 O ANALYST
val it : {deptno:int, initial:char, job:string} bag
In the following sections, we define each of Morel's step types and how they map input fields to output fields.
Step expressions
Expressions within query steps use the same syntax as expressions elsewhere in Morel, but they execute within an enhanced environment that provides additional operations.
Query step expressions have access to three special operations not available in other contexts:
current- references the current row being processedelements- references the collection of elements in the current groupordinal- provides the position/index of the current row
Most query steps evaluate once per row and run within a specialized environment. This applies to all steps except:
computeexceptintersectintoskiptakethroughunion
An expression in a per-row step has access to:
- Field definitions from the preceding step in the query pipeline
- Special operations:
ordinalandcurrentexpressions for row-specific processing
The current expression
The current expression refers to the current row. If there are
multiple fields, its type is a record with the fields defined by the
preceding step. If there is only one field, its type is the type of
that field.
(* Multiple fields. Current is a record. *)
from i in [1, 2],
j in ["a", "b"]
yield current;
i j
- -
1 a
1 b
2 a
2 b
val it : {i:int, j:string} list
(* Single anonymous field. Current is an atom. *)
from i in [1, 2, 3]
yield i mod 2
yield current;
val it = [1,0,1] : int list
(* Single field named "dname". Current is an atom, but you
can also refer to the field by name. *)
from d in scott.depts
yield d.dname
yield current;
val it = ["ACCOUNTING","RESEARCH","SALES","OPERATIONS"]
: string bag
from d in scott.depts
yield d.dname
yield dname;
val it = ["ACCOUNTING","RESEARCH","SALES","OPERATIONS"]
: string bag
(* Yield is a record expression with a single field.
Current is a record. *)
from i in [1, 2, 3]
yield {j = i + 1}
yield current;
val it = [{j=2}, {j=3}, {j=4}] : {j:int} list
The elements expression
The elements expression returns the collection of elements in the
current group. It can only be used in the compute clause of a
group step, or in a compute step.
(* For each department, compute the total salary of the clerks. *)
from e in scott.emps
group e.deptno
compute {s = (from x in elements
where x.job = "CLERK"
compute sum over x.sal)};
deptno s
------ ------
10 1300.0
20 1900.0
30 950.0
val it : {deptno:int, s:real} list
(* Gather the departments in each region. *)
let
fun region d =
case d.loc of
"CHICAGO" => "MIDWEST"
| "DALLAS" => "SOUTH"
| _ => "NORTHEAST"
in
from d in scott.depts
group {r = region d} compute elements
order r
end;
val it =
[{r="MIDWEST",
elements=[{deptno=30,dname="SALES",loc="CHICAGO"}]},
{r="NORTHEAST",
elements=
[{deptno=10,dname="ACCOUNTING",loc="NEW YORK"},
{deptno=40,dname="OPERATIONS",loc="BOSTON"}]},
{r="SOUTH",
elements=[{deptno=20,dname="RESEARCH",loc="DALLAS"}]}]
: {elements:{deptno:int, dname:string, loc:string} bag, r:string} list
(* How many employees in each department? *)
from e in scott.emps
group e.deptno compute {c = length elements}
order deptno;
c deptno
- ------
3 10
5 20
6 30
val it : {c:int, deptno:int} list
elements is only available inside a compute step, or the compute
clause of a group step.
from e in scott.emps yield length elements; stdIn:2.16-2.24 Error: 'elements' is only valid in a 'compute' clause raised at: stdIn:2.16-2.24
The ordinal expression
The ordinal expression refers to the ordinal number of the current
row, starting at 0.
(* Print the top 5 employees by salary, and their rank. *)
from e in scott.emps
order DESC e.sal
take 5
yield {e.ename, e.sal, rank = ordinal + 1};
ename rank sal
----- ---- ------
KING 1 5000.0
SCOTT 2 3000.0
FORD 3 3000.0
JONES 4 2975.0
BLAKE 5 2850.0
val it : {ename:string, rank:int, sal:real} list
ordinal is only available in steps
whose input is ordered.
from e in scott.emps
yield {e.ename, e.sal, rank = ordinal + 1};
> stdIn:2.33-2.40 Error: cannot use 'ordinal' in unordered query
> raised at: stdIn:2.33-2.40
Step list
| Name | Summary |
|---|---|
distinct | Removes duplicate rows from the current collection. |
except | Returns the set (or multiset) difference between the current collection and one or more argument collections. |
group | Performs aggregation across groups of rows. |
intersect | Returns the set (or multiset) intersection between the current collection and one or more argument collections. |
join | Joins one or more scans to the current collection. |
order | Sorts the current collection by an expression. |
skip | Skips a given number of rows from the current collection. |
take | Limits the number of rows to return from the current collection. |
through | Calls a table function, with the current collection as an argument, and starts a scan over the collection it returns. |
union | Returns the set (or multiset) union between the current collection and one or more argument collections. |
unorder | Makes the current collection unordered. |
where | Emits rows of the current collection for which a given predicate evaluates to true. |
yield | For each row in the current collection, evaluates an expression and emits it as a row. |
The following steps produce a single scalar or record value. Because the output is not a collection, no further steps are possible, and therefore they are called terminal steps.
It can be unwieldy to use a query in an expression such as if or
case if the query returns a collection. Queries with a terminal
step, and forall and exists queries, are easy to embed in an
expression.
| Name | Summary |
|---|---|
compute | Applies aggregate functions to the current collection. |
into | Applies a function to the current collection. |
require | Evaluates the predicate of a forall query. |
Distinct step
distinct
Description
Removes duplicate rows from the current collection.
The output fields are the same as the input fields.
Example
(* Compute the set of distinct rolls of two dice. *)
from i in [1, 2, 3, 4, 5, 6],
j in [1, 2, 3, 4, 5, 6]
yield i + j
distinct;
val it = [2,3,4,5,6,7,8,9,10,11,12] : int list
Except step
except [ distinct ] exp1 , ... , expe (e ≥ 1)
Description
Returns the set (or multiset) difference between the current collection and one or more argument collections.
The except step returns the distinct elements from the input
collection that are not present in any of the arguments (the
collections resulting from evaluating the expi
expressions).
With the distinct keyword, the except step returns the elements
from the input collection that are not present in any of the argument
collections. If an element occurs x times in the input collection
and a total of y times in the argument collections, the step emits the
element x - y times. If y is greater than x, the element is not
emitted.
except is equivalent to SQL EXCEPT ALL; except distinct is
equivalent to SQL EXCEPT or EXCEPT DISTINCT. (Some SQL dialects
use MINUS rather than EXCEPT.)
The output fields are the same as the input fields.
Example
(* Which job titles exist in department 10 but not in
department 20 or 30? *)
from e in scott.emps
where e.deptno = 10
yield e.job
except distinct
(from e in scott.emps
where e.deptno = 20
yield e.job),
(from e in scott.emps
where e.deptno = 30
yield e.job);
val it = ["PRESIDENT"] : string bag
(* I have two sodas and three candies and give my friend one soda and two candies. What do I have left? *) from i in ["candy", "soda", "soda", "candy", "candy"] except ["soda", "candy"]; val it = ["soda","candy","candy"] : string list
Group step
group exp1 [ compute exp2 ]
Description
Performs aggregation across groups of rows.
Groups the rows of the input collection by the group key,
exp1. If there is a compute clause,
for each group, computes the aggregate expressions specified in
exp2.
The output is an atom if there is a single field: if group is an
atom and compute is missing, or group is empty and compute is an
atom. Otherwise, the output is a record; group and compute must
both be records, or be atomic expressions from which a name can be
derived, and the output fields are the combined fields of those
records, whose names must be disjoint.
Field names are derived in the usual way for record fields. An
explicit field name can be specified using an id =
prefix. The explicit field name can be omitted if an implicit field
name can be derived: if the expression is id then
the implicit field name is id; if the expression
is record.field then the implicit field
name is field; if the expression is
agg over arg then the implicit field
name is agg.
The over operator applies an aggregate function to a collection of
values. The left operand of over is an aggregate expression; it is
typically an aggregate function such as count or sum but may be an
expression such as min 0 or fn x => x. The right operand of over
is an expression that is evaluated for each element in the group.
An over expression is just an expression (albeit one that can only
be used in a compute clause), and that means that it can be part of
a larger expression. That larger expression can include other over
expressions, for example ((min over e.sal) + (max over e.sal)) / 2.
Example
(* Count employees and compute total salary for each
department. *)
from e in scott.emps
group e.deptno
compute {count over (), sumSal = sum over e.sal};
count deptno sumSal
----- ------ -------
5 20 10875.0
3 10 8750.0
6 30 9400.0
val it : {count:int, deptno:int, sumSal:real} bag
(* One group key and no compute expressions gives an
atomic result. *)
from e in scott.emps
group e.deptno;
val it = [20,10,30] : int bag
(* Empty group key and one compute expression gives an
atomic result. *)
from e in scott.emps
group {} compute min over e.sal + e.comm;
val it = [800.0] : real bag
Intersect step
intersect [ distinct ] exp1 , ... , expi (i ≥ 1)
Description
Returns the set (or multiset) intersection between the current collection and one or more argument collections.
The intersect step returns the distinct elements that are present in
the input collection and all argument collections (the collections
resulting from evaluating the expi
expressions).
With the distinct keyword, the intersect emits an element if it
occurs in the input and all argument collections, and may emit it
multiple times. If an element occurs x times in the input
collection, y1 times in argument collection 1,
y2 times in argument collection 2, and so
forth, the step will emit it z times, where z is
min(x, y1, ..., yi).
intersect is equivalent to SQL INTERSECT ALL; intersect distinct
is equivalent to SQL INTERSECT or INTERSECT DISTINCT.
The output fields are the same as the input fields.
Example
(* Which job titles exist in department 10 and also in
departments 20 and 30? *)
from e in scott.emps
where e.deptno = 10
yield e.job
intersect distinct
(from e in scott.emps
where e.deptno = 20
yield e.job),
(from e in scott.emps
where e.deptno = 30
yield e.job);
val it = ["MANAGER","CLERK"] : string bag
(* I have two sodas and three candies, and my friend
has one soda, one donut, and four candies. What do we
have in common? *)
from i in ["candy", "soda", "soda", "candy", "candy"]
intersect ["soda", "donut", "candy",
"donut", "candy", "candy"];
val it = ["candy","candy","candy","soda"] : string list
Join step
[ left | right | full ] join scan1 , ... , scans (s ≥ 1)
scan → pat in exp [ on exp ]
| pat = exp [ on exp ]
| var
Description
Joins one or more scans to the current collection.
The output fields are the input fields plus the identifiers in the
pat and var of each of the
scans. Field names must be unique.
If any scan has an on clause, the expression must be of type
bool and may reference any variable in the environment,
including the output fields of the previous step, and fields defined
by any preceding scans in this join.
A plain join is an inner join: a row appears in the output only if
its on condition holds. The left, right and full variants are
outer joins, which also keep rows that have no match on the other
side, filling the missing fields with NONE:
left joinkeeps every input row; the scan's fields becomeoption(NONEwhen the scan has no matching row);right joinkeeps every row of the scan; the input fields becomeoption(NONEwhen the input has no matching row);full joinkeeps rows from both sides; the fields on either side becomeoption.
The on condition always sees the non-optional values. Outer joins are
applied one step at a time, so a field that is already optional gains
another option layer (for example int option option); the
safe-navigation operator ?. is a convenient way to read through the
layers.
The source exp of a right join or full join may
produce rows that match no input row, so it must not depend on the
query's input: it may not reference the variables of earlier steps, nor
current or ordinal. (A left join source is evaluated for each
input row, so it may be correlated.)
Example
(* Find the name of each department and the name of all
employees in those departments. *)
from d in scott.depts
join e in scott.emps on e.deptno = d.deptno
yield {d.dname, e.ename};
dname ename
---------- ------
ACCOUNTING CLARK
ACCOUNTING KING
ACCOUNTING MILLER
RESEARCH SMITH
RESEARCH JONES
RESEARCH SCOTT
RESEARCH ADAMS
RESEARCH FORD
SALES ALLEN
SALES WARD
SALES MARTIN
SALES BLAKE
SALES TURNER
SALES JAMES
val it : {dname:string, ename:string} bag
(* Each department, and its president if it has one. A 'left join'
keeps every department, so departments with no president (every
one except ACCOUNTING) have a blank 'president' cell. *)
from d in scott.depts
left join e in scott.emps
on e.deptno = d.deptno andalso e.job = "PRESIDENT"
yield {d.dname, president = e?.ename};
dname president
---------- ---------
ACCOUNTING KING
RESEARCH
SALES
OPERATIONS
val it : {dname:string, president:string option} bag
Order step
order exp
Description
Sorts the current collection by an expression.
Ordering is determined by the data type, and is defined not just for
scalar expressions like int and string, but structured types like
tuples, records, lists, and sum types. The rules are as follows:
- Primitive types
boolordersfalse<true;charorders as defined byChar.compare;intorders as defined byInt.compare;realorders as defined byReal.compare;stringorders as defined byString.compare;unithas only value,(), which compares equal to itself.
- Lists are ordered lexicographically,
e.g.
[]<[3]<[3, 1]<[3, 2]<[4]. - Tuples are ordered lexicographically on fields left-to-right,
e.g.
(1, "b")<(1, "c")<(2, "a"). - Records are ordered lexicographically on fields in alphabetical order,
e.g.
{x=1, y="b"}<{x=1, y="c"}<{x=2, y="a"}. - Sum types are ordered by the label in declaration order, then by
values. For example, a sum type
foodeclared asdatatype foo = EMPTY | SINGLE of int | PAIR of int * stringordersEMPTY<SINGLE 1<SINGLE 2<PAIR (1, "b")<PAIR (2, "a"). Optionis declared as a sum typedatatype 'a Option = NONE | SOME of 'aand follows the usual rules for sum types, thereforeNONE<SOME 1<SOME 2. (SinceNONEis Morel's equivalent of SQL's null value, this ordering corresponds to SQLNULLS FIRST.)Descendingis declared as a sum typedatatype 'a Descending = DESC of 'a
but has special ordering semantics, reversing the value's order. ThusDESC 3<DESC 2<DESC 1.
Common sort specifications can be achieved using tuple types and/or
DESC. For example, the equivalent of SQL ORDER BY e.job, e.sal DESC
is order (e.job, DESC e.sal).
The output fields are the same as the input fields.
The order step is not stable. Even in the event of ties (multiple
elements with the same sort key) the order that it outputs elements is
not affected by the order in which elements arrived. (If you wish to
achieve a stable sort, you can use the ordinal expression in a
secondary sort key.)
Example
(* List employees ordered by salary (descending) then
name. *)
from e in scott.emps
order (DESC e.sal, e.ename)
yield {e.ename, e.job, e.sal};
ename job sal
------ --------- ------
KING PRESIDENT 5000.0
FORD ANALYST 3000.0
SCOTT ANALYST 3000.0
JONES MANAGER 2975.0
BLAKE MANAGER 2850.0
CLARK MANAGER 2450.0
ALLEN SALESMAN 1600.0
TURNER SALESMAN 1500.0
MILLER CLERK 1300.0
MARTIN SALESMAN 1250.0
WARD SALESMAN 1250.0
ADAMS CLERK 1100.0
JAMES CLERK 950.0
SMITH CLERK 800.0
val it : {ename:string, job:string, sal:real} list
If you sort by a record whose fields do not appear in alphabetical order, Morel gives a warning.
(* Record expressions are sorted lexicographically on field
name, that is, by job then by salary. Morel gives a
warning, in case you were expecting to sort on salary
first. *)
from e in scott.emps
order {e.sal, e.job}
yield {e.ename, e.job, e.sal};
ename job sal
------ --------- ------
SCOTT ANALYST 3000.0
FORD ANALYST 3000.0
SMITH CLERK 800.0
JAMES CLERK 950.0
ADAMS CLERK 1100.0
MILLER CLERK 1300.0
CLARK MANAGER 2450.0
BLAKE MANAGER 2850.0
JONES MANAGER 2975.0
KING PRESIDENT 5000.0
WARD SALESMAN 1250.0
MARTIN SALESMAN 1250.0
TURNER SALESMAN 1500.0
ALLEN SALESMAN 1600.0
val it : {ename:string, job:string, sal:real} list
Warning: Sorting on a record whose fields are not in
alphabetical order. Sort order may not be what you expect.
raised at: stdIn:2.9-2.23
Skip step
skip exp
Description
Skips a given number of rows from the current collection.
The expression exp must evaluate to an integer,
which specifies the number of rows to skip from the beginning of the
current collection. It is an error if the value is negative. If the
value exceeds the number of rows in the collection, no rows are
returned.
The output fields are the same as the input fields.
Example
(* Skip the first 3 rows of a collection. *) from i in [1, 2, 3, 4, 5, 6, 7] skip 3; val it = [4,5,6,7] : int list
Take step
take exp
Description
Limits the number of rows to return from the current collection.
The expression exp must evaluate to an integer,
which specifies the maximum number of rows to return from the current
collection. If the value is zero, no rows are returned. It is an error
if the value is negative.
The output fields are the same as the input fields.
Example
(* Return only the first 3 rows of a collection. *) from i in [1, 2, 3, 4, 5, 6, 7] take 3; val it = [1,2,3] : int list
YieldAll step
yieldAll exp
Description
Evaluates a collection-valued expression for each row and emits each
of its elements, flattening the results. It is the relational
equivalent of a flatMap (or monadic bind) operation, and the
flatten-shaped sibling of yield: where yield emits
one row per input row, yieldAll emits every element of a collection.
The expression exp must evaluate to a collection
(a list or a bag). The step returns one row per element of that
collection, so yieldAll exp is equivalent to
a scan , v in exp yield v. As
for a comma-join scan, the output is a list if both the input and
exp are lists, and a bag otherwise.
A subsequent step refers to the flattened element as current (or
current.field if the element is a record), just as it would refer
to the value produced by a yield step. Within exp
itself, ordinal and current refer to the input row. If the
expression has type t list, the output collection has element type
t.
Example
(* Flatten a collection of lists. *) from i in [[1, 2], [3], [4, 5]] yieldAll i; val it = [1,2,3,4,5] : int list
Emitting a singleton to keep a row, or an empty collection to drop it,
lets yieldAll express the same filtering as where:
from i in [1, 2, 3, 4] yieldAll (if i mod 2 = 0 then [i] else []); val it = [2,4] : int list
Because yieldAll expands a collection that may depend
on the current row, it expresses a correlated (lateral) join. In the
following query the subquery references the outer variable
e:
from e in [1, 2, 3] yieldAll (from i in [1, 2] yield e * i); val it = [1,2,2,4,3,6] : int list
Through step
through pat in exp
Description
Calls a table function, with the current collection as an argument, and starts a scan over the collection it returns.
The expression exp must evaluate to a function
that takes the current collection as an argument and returns a new
collection. The pattern pat is bound to each
element of the returned collection.
The output fields are the fields defined by the pattern
pat.
Example
(* Define a table function that returns the even numbers
from a collection. *)
fun evenNumbers (xs: int list) =
from x in xs
where x mod 2 = 0;
(* Use the table function in a query. *)
from i in [1, 2, 3, 4, 5, 6, 7]
through j in evenNumbers;
val it = [2,4,6] : int list
The previous example can be generalized to find multiples of any given
number. The table function now takes two arguments, and we provide
the first argument in the through clause; the input collection
becomes the second argument.
(* Define a table function that returns the numbers from
a collection that are multiples of base. *)
fun multiplesOf base (xs: int list) =
from x in xs
where x mod base = 0;
(* Use the table function to find multiples of 3. *)
from i in [1, 2, 3, 4, 5, 6, 7]
through j in multiplesOf 3;
val it = [3,6] : int list
Description
Calls a table function, with the current collection as an argument, and starts a scan over the collection it returns.
Union step
union [ distinct ] exp1 , ... , expi (i ≥ 1)
Description
Returns the set (or multiset) union between the current collection and one or more argument collections.
The union step returns the distinct elements that are present in
the input collection or any of the argument collections (the collections
resulting from evaluating the expu
expressions).
With the distinct keyword, the union step may emit an element
multiple times. If an element occurs x times in the input
collection, y1 times in argument collection 1,
y2 times in argument collection 2, and so
forth, the step will emit it z times, where z is
x + y1 + ... + yu).
union is equivalent to SQL UNION ALL; union distinct
is equivalent to SQL UNION or UNION DISTINCT.
The output fields are the same as the input fields.
Example
(* Which job titles exist in departments 10, 20 and 30? *)
from e in scott.emps
where e.deptno = 10
yield e.job
union distinct
(from e in scott.emps
where e.deptno = 20
yield e.job),
(from e in scott.emps
where e.deptno = 30
yield e.job);
val it = ["MANAGER","PRESIDENT","CLERK","ANALYST","SALESMAN"]
: string bag
(* I have two sodas and three candies, and my friend has one soda and one candy. What do we have if we combine our stashes? *) from i in ["candy", "soda", "soda", "candy", "candy"] union ["soda", "candy"]; val it = ["candy","soda","soda","candy","candy","soda","candy"] : string list
Unorder step
unorder
Description
Removes the ordering of the current collection.
The output fields are the same as the input fields.
Why would you want to remove ordering? Some relational operators can be more expensive when the input collection is ordered, and if you don't need the ordering, you can remove it to improve performance.
Consider the union step. When applied to lists, its output must be
ordered, consisting of the elements of the input in order, followed by
the elements the first argument in order, and so forth. This limits
parallelism. When the output of a union step is unordered, such as
when it is applied to bags or when followed by an unorder step, it
can execute its arguments in parallel. Furthermore, its inputs can
operate in unordered mode, which may lead to further efficiencies.
Unordered collections can be propagated towards inputs except for the
skip and take steps, and any step that
evaluates the ordinal expression.
Surprisingly, the order step is somewhat similar to unorder.
While its output is ordered, of course, order disregards the order
of its input. This allows upstream steps to operate in unordered mode,
if that is more efficient.
Example
(* Find the top 3 employees by salary, as an unordered
collection. *)
from e in scott.emps
order DESC e.sal
take 3
unorder
yield {e.ename, e.job, e.sal};
ename job sal
----- --------- ------
KING PRESIDENT 5000.0
SCOTT ANALYST 3000.0
FORD ANALYST 3000.0
val it : {ename:string, job:string, sal:real} bag
Where step
where exp
Description
Emits rows of the current collection for which a given predicate
evaluates to true.
The expression exp must evaluate to a boolean
value. Only rows for which the expression evaluates to true are
emitted to the output.
The output fields are the same as the input fields.
Example
(* Find employees who work in department 20. *)
from e in scott.emps
where e.deptno = 20
yield {e.ename, e.job};
ename job
----- -------
SMITH CLERK
JONES MANAGER
SCOTT ANALYST
ADAMS CLERK
FORD ANALYST
val it : {ename:string, job:string} bag
Yield step
yield exp
Description
For each row in the current collection, evaluates an expression and emits it as a row.
The expression exp defines the output fields. If
exp is a record expression, its field names become
the output field names.
If this is the last step in the query, the expression may be a non-record type. In this case, there are no output fields, and the result of the query is a collection of that non-record type.
Example
(* Create a new record from each employee with modified
fields. *)
from e in scott.emps
yield {name = String.map Char.toUpper e.ename,
position = String.map Char.toLower e.job,
annualSalary = e.sal * 12.0};
annualSalary name position
------------ ------ ---------
9600.0 SMITH clerk
19200.0 ALLEN salesman
15000.0 WARD salesman
35700.0 JONES manager
15000.0 MARTIN salesman
34200.0 BLAKE manager
29400.0 CLARK manager
36000.0 SCOTT analyst
60000.0 KING president
18000.0 TURNER salesman
13200.0 ADAMS clerk
11400.0 JAMES clerk
36000.0 FORD analyst
15600.0 MILLER clerk
val it : {annualSalary:real, name:string, position:string} bag
(* Return a list of strings describing each employee in department 20. *) from e in scott.emps where e.deptno = 20 yield e.ename ^ " is a " ^ e.job; val it = ["SMITH is a CLERK","JONES is a MANAGER","SCOTT is a ANALYST", "ADAMS is a CLERK","FORD is a ANALYST"] : string bag
Compute terminal step
compute agg1 , ... , agga (a ≥ 1) agg → [ id = ] exp [ of exp ]
Description
Applies aggregate functions to the current collection.
Unlike the group step, which groups rows and computes
aggregates for each group, the compute terminal step computes
aggregates across the entire collection and returns a single record or
scalar value.
The output is a scalar value if there is one aggregate, or a record if there is more than one. That value becomes the result of the query expression.
Field names are derived in the same way as the group step. An
explicit field name of an agg can be specified
using an id = prefix. The explicit field name can
be omitted if an implicit field name can be derived: if the aggregate
function is id then the implicit field name is
id; if the aggregate function is
record.field then the implicit field name
is field.
Example
(* Compute total number of employees and average salary. *)
from e in scott.emps
compute total = count,
avgSal = avg of e.sal,
minSal = min of e.sal,
maxSal = max of e.sal;
val it = {maxSal=5000.0,minSal=800.0,sumSal=29025.0,total=14}
: {maxSal:real, minSal:real, sumSal:real, total:int}
(* Compute total number of employees. *) from e in scott.emps compute count; val it = 14 : int
Into terminal step
into exp
Description
Applies a function to the current collection.
The expression exp must evaluate to a function
that takes the current collection as an argument. The result of the
query is the result of applying that function to the collection.
Example
(* Apply a custom function to the query results. *)
fun analyzeResults (emps: {deptno: int, sal: real} bag) =
let
val {count, sumSal} =
from e in emps
compute {count over (), sumSal = sum over e.sal}
val avgSal = sumSal / real count
in
{employeeCount = count,
averageSalary = avgSal,
classification = if avgSal > 2000.0 then "High" else "Low"}
end;
from e in scott.emps
where e.deptno = 10
yield {e.deptno, e.sal}
into analyzeResults;
val it = {employeeCount=3, averageSalary=2916.67, classification="High"}
: {employeeCount:int, averageSalary:real, classification:string}
Require terminal step
require exp
Description
Evaluates the predicate of a forall query.
This step is only valid as the last step of a forall query. The
expression exp must evaluate to a boolean
value. The result of query is true if the predicate evaluates to
true for every row in the collection, or if the collection is empty.
Example
(* Check whether all employees earn more than \$2000. *) forall e in scott.emps require e.sal > 2000.0; val it = false : bool
(* Check whether all managers earn more than \$2000. *) forall e in scott.emps where e.job = "MANAGER" require e.sal > 2000.0; val it = true : bool
Quantified queries
Morel provides query forms for existential and universal quantification:
existsreturns whether at least one row in the query satisfies the critera (existential quantification);forallreturns whether all rows in the query satisfy the criteria (universal quantification).
Exists query
exists [ scan1 , ... , scans ] step1 ... stept (s ≥ 0, t ≥ 0)
An exists query returns true if the query returns at least one
row, and false otherwise.
Example
(* Do any employees earn more than \$3,000? *) exists e in scott.emps where e.sal > 3000.0; val it = true : bool
Forall query
forall [ scan1 , ... , scans ] step1 ... stept (s ≥ 0, t ≥ 0) require exp
A forall query returns true if the predicate specified in the
require step evaluates to true for every row that reaches that
step, or no rows reach that step. It returns false if the predicate
evaluates to false for at least one row.
Rows that are eliminated by previous steps (such as where) and do
not reach the require step do not count as evaluations of the
predicate.
Example
(* Do all employees have a job title of clerk, manager or president? *) forall e in scott.emps require e.job elem ["CLERK", "MANAGER", "PRESIDENT"]; val it = false : bool
(* Do all employees in department 10 have a job title of clerk, manager or president? *) forall e in scott.emps where e.deptno = 10 require e.job elem ["CLERK", "MANAGER", "PRESIDENT"]; val it = true : bool
(* Are all employees in department 10 and have a job
title of clerk, manager or president? *)
forall e in scott.emps
require e.deptno = 10
andalso e.job elem ["CLERK", "MANAGER", "PRESIDENT"];
val it = false : bool
Correspondence between SQL and Morel query
Many of the keywords in a SQL query have an equivalent in Morel.
| SQL | Morel | Remarks |
|---|---|---|
SELECT | yield | While SELECT must be the first keyword of a SQL query, you may use yield at any point in a Morel pipeline. It often occurs last, and you can omit it if the output record already has the right shape. |
FROM | from | Unlike SQL FROM, from is the first keyword in a Morel query. |
JOIN | join | SQL JOIN is part of the FROM clause, but Morel join is a step. |
WHERE | where | Morel where is equivalent to SQL WHERE. |
HAVING | Use a where after a group. | |
DISTINCT | distinct | SQL DISTINCT is part of the SELECT clause, but Morel distinct is a step, shorthand for group |
ORDER BY | order | Morel order is equivalent to SQL ORDER BY. |
LIMIT | take | Morel take is equivalent to SQL LIMIT. |
OFFSET | skip | Morel skip is equivalent to SQL OFFSET. |
UNION | union | Morel union is equivalent to SQL UNION ALL; union distinct is equivalent to UNION. |
INTERSECT | intersect | Morel intersect is equivalent to SQL INTERSECT ALL; intersect distinct is equivalent to INTERSECT. |
EXCEPT | except | Morel except is equivalent to SQL EXCEPT ALL; except distinct is equivalent to EXCEPT. (Some dialects use MINUS rather than EXCEPT.) |
EXISTS | exists | SQL EXISTS is a unary operator whose operand is a query, but Morel exists is a query that returns true if the query has at least one row. |
| - | forall | Morel forall is a query that returns true if a predicate is true for all rows. |
IN | elem | SQL IN is a binary operator whose right operand is either a query or a list (but not an array or multiset); Morel elem is the equivalent operator, and its right operand can be any collection, including a query. |
NOT IN | notelem | Morel notelem is equivalent to SQL NOT IN, but without SQL's confusing NULL-value semantics. |
| - | yieldall | Morel yieldall evaluates a collection expression and outputs one row for each element of that collection. |