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)

scanpat in exp [ on exp ]    iteration
    | pat = exp [ on exp ]      single iteration
    | val                       unbounded variable

stepdistinct                 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

terminalStepinto 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 processed
  • elements - references the collection of elements in the current group
  • ordinal - 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:

  • compute
  • except
  • intersect
  • into
  • skip
  • take
  • through
  • union

An expression in a per-row step has access to:

  • Field definitions from the preceding step in the query pipeline
  • Special operations: ordinal and current expressions 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

NameSummary
distinctRemoves duplicate rows from the current collection.
exceptReturns the set (or multiset) difference between the current collection and one or more argument collections.
groupPerforms aggregation across groups of rows.
intersectReturns the set (or multiset) intersection between the current collection and one or more argument collections.
joinJoins one or more scans to the current collection.
orderSorts the current collection by an expression.
skipSkips a given number of rows from the current collection.
takeLimits the number of rows to return from the current collection.
throughCalls a table function, with the current collection as an argument, and starts a scan over the collection it returns.
unionReturns the set (or multiset) union between the current collection and one or more argument collections.
unorderMakes the current collection unordered.
whereEmits rows of the current collection for which a given predicate evaluates to true.
yieldFor 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.

NameSummary
computeApplies aggregate functions to the current collection.
intoApplies a function to the current collection.
requireEvaluates 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)

scanpat 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 join keeps every input row; the scan's fields become option (NONE when the scan has no matching row);
  • right join keeps every row of the scan; the input fields become option (NONE when the input has no matching row);
  • full join keeps rows from both sides; the fields on either side become option.

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
    • bool orders false < true;
    • char orders as defined by Char.compare;
    • int orders as defined by Int.compare;
    • real orders as defined by Real.compare;
    • string orders as defined by String.compare;
    • unit has 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 foo declared as
    datatype foo =
        EMPTY
      | SINGLE of int
      | PAIR of int * string
    orders EMPTY < SINGLE 1 < SINGLE 2 < PAIR (1, "b") < PAIR (2, "a").
  • Option is declared as a sum type
    datatype 'a Option =
        NONE
      | SOME of 'a
    and follows the usual rules for sum types, therefore NONE < SOME 1 < SOME 2. (Since NONE is Morel's equivalent of SQL's null value, this ordering corresponds to SQL NULLS FIRST.)
  • Descending is declared as a sum type
    datatype 'a Descending = DESC of 'a
    but has special ordering semantics, reversing the value's order. Thus DESC 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:

  • exists returns whether at least one row in the query satisfies the critera (existential quantification);
  • forall returns 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.

SQLMorelRemarks
SELECTyieldWhile 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.
FROMfromUnlike SQL FROM, from is the first keyword in a Morel query.
JOINjoinSQL JOIN is part of the FROM clause, but Morel join is a step.
WHEREwhereMorel where is equivalent to SQL WHERE.
HAVINGUse a where after a group.
DISTINCTdistinctSQL DISTINCT is part of the SELECT clause, but Morel distinct is a step, shorthand for group
ORDER BYorderMorel order is equivalent to SQL ORDER BY.
LIMITtakeMorel take is equivalent to SQL LIMIT.
OFFSETskipMorel skip is equivalent to SQL OFFSET.
UNIONunionMorel union is equivalent to SQL UNION ALL; union distinct is equivalent to UNION.
INTERSECTintersectMorel intersect is equivalent to SQL INTERSECT ALL; intersect distinct is equivalent to INTERSECT.
EXCEPTexceptMorel except is equivalent to SQL EXCEPT ALL; except distinct is equivalent to EXCEPT. (Some dialects use MINUS rather than EXCEPT.)
EXISTSexistsSQL 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.
-forallMorel forall is a query that returns true if a predicate is true for all rows.
INelemSQL 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 INnotelemMorel notelem is equivalent to SQL NOT IN, but without SQL's confusing NULL-value semantics.
-yieldallMorel yieldall evaluates a collection expression and outputs one row for each element of that collection.