Rel Primer: Basic Syntax
This Primer is an introduction to the basic syntax of Rel.
Introduction
This Rel Primer is an introduction to the main features of Rel. It assumes basic knowledge of database concepts and programming.
Rel is RelationalAI’s language for building models and querying data.
Rel has a simple but powerful syntax designed to express a large set of relational operations, build declarative models, and capture general domain knowledge, including, in particular, knowledge graphs.
Relations
Everything in Rel is a relation. Rel imports data into relations, combines relations to define new ones, and queries relations to get answers back — in the form of new relations.
Relations are sets of tuples.
A tuple is a list of elements, where the order matters.
It corresponds to a row in a traditional database.
The elements are the basic objects in the database — for example, numbers, strings, symbols, or dates.
A tuple may be specified in Rel by separating its elements with commas (,
) and enclosing the list of elements in parentheses (()
).
For example,
("Neymar", "PSG")
is a tuple with two elements that represent the name of a soccer player and the club he currently plays for.
("store8", "product156", 12.99)
is a tuple with three elements that represent a store ID, a product ID, and the price of the product at that store.
A relation may be defined explicitly by using semicolons (;
) to separate tuples and curly braces to enclose them:
// read query
def players {
("Neymar", "PSG"); // single-line comment
("Messi", "BFC");
("Werner", "Chelsea");
("Pulisic", "Chelsea")
}
/*
This is a multiline comment in Rel.
*/
def output = players
Note that the order of the tuples does not matter, since relations are sets of tuples. The order of the elements inside each tuple, by contrast, can matter a lot.
Rel chooses a default order to display the tuples in the relation,
which may be different from the order used to write the relation down, as seen in this example.
You should not assume that tuples will appear in any particular order; but you can use utilities like enumerate
, top
, and bottom
to sort and rank them.
The contents of the relation output
are automatically displayed.
In other respects it’s a relation like any other.
A Note About Definitions
The syntactic construct that begins with def
is called a definition.
You could have also written the definition of output
in the last example as def output { players }
.
To define players
, you could have written:
def players = { ("Neymar", "PSG"); ("Messi", "BFC");
("Werner", "Chelsea"); ("Pulisic", "Chelsea") }
Regardless of how you write a definition, it associates the name that follows def
with the relation that is specified on the right-hand side of the definition.
You can think of it as inserting a copy of the relation on the right into the relation named on the left.
For example, you can think of def output = players
as inserting a copy of players
into output
.
However, you should be aware that:
- A relation is copied only when necessary.
In the case of
def output = players
this is not necessary, and bothoutput
andplayers
refer to the same relation. - In general, a definition may introduce a dependency between two relations. This will be explained in Definitions.
Arity and Cardinality
If you think of relations as tables, the arity of a relation is the number of columns, and the cardinality is the number of rows.
Arity
To compute the arity of a relation, simply use arity
.
// read query
def R = {(1, "a"); (1, "b"); (1, "c")}
def output = arity[R]
In fact, arity
computes the lengths of all tuples in a relation.
If a relation contains tuples of various length, arity
will return all tuple lengths that occur in the relations.
// read query
def R {
("SKI CHAMPIONSHIPS", 2009);
("SKI CHAMPIONSHIPS", 2009, "Lindsey", "Vonn", "Downhill")
}
def output = arity[R]
In some relatively rare cases arity
might give an over-approximation of the actual arity.
The only guarantee is that no tuple in a relation R
has more than arity[R]
elements.
Cardinality
You can calculate the cardinality of a relation using count
:
// read query
def R = {1; 3; 5; 7; 9}
def output = count[R]
It does not matter — in contrast to arity
— if a relation has tuples with various lengths: count
returns the number of tuples, regardless of length.
You only have to watch out when calculating the cardinality of the empty relation {}
.
For the empty relation, count
evaluates to false
.
To get the zero for the cardinality, you can use one of the override relations.
Consider the two examples below:
// read query
def output = count[{}]
// read query
def output = (count[{}] <++ 0)
Notice that in the first example there was no output, because count[{}]
evaluates to false
.
Single Elements
In Rel, a single element is identified with a relation of arity 1 and cardinality 1.
For example, the number 7 is the same as the relation {(7)}
.
Curly braces are not required for single-element relations; so {4}
and 4
are the same relation,
with arity 1 and cardinality 1.
The constants true
and false
are also relations, of arity 0.
There are only two of these: false
is another name for {}
(the empty relation), and true
is another name for {()}
, that is, the relation with one empty tuple (arity 0 and cardinality 1).
Though this might seem unusual, it is compatible with other features of the language, as you will see below.
This way, everything is a relation, including the results of arithmetical operations
(+
, -
, *
, etc.)
and boolean operations (and
, or
, not
, etc.).
Relations Are Sets
Rel relations are sets of tuples.
This means that a relation cannot contain two identical tuples, and that its tuples are not ordered.
Thus, for example, {1; 1; 1}
is the same unary relation as {1}
.
And {(1, 2); (3, 4); (1, 2)}
is the same binary relation
as {(3, 4); (1, 2)}
.
This is important when computing aggregations, as explained in the Group-By section in the next part of this Primer.
Thinking of relations as tables again, this means that the order of the rows does not matter, and there are no duplicate rows. The order of the columns, by contrast, is important.
The parser allows but does not require parentheses and brackets when there is no ambiguity. So you can write:
// read query
def output = 1, 2 ; 3, 4
Relational Application
Assume you have the following relation with information about the surnames, first names, and ages of a group of people:
// model
def age {
("Smith", "Jane", 20);
("Smith", "John", 21);
("Smith", "Jane", 22);
("Miller", "Jane", 21);
("Miller", "John", 20)
}
You can copy the entire relation to another relation:
// read query
def output = age
Recall that the above does not actually create another copy of the relation — see A Note About Definitions.
Recall also that output
is special only in that its contents are printed out.
Otherwise, it’s just like any other relation.
Square Brackets on the Right
If you are interested only in those tuples in age
that begin with a particular surname, you can use square brackets, like this:
// read query
def output = age["Smith"]
The expression age["Smith"]
is an example of a partial relational application.
In this case the relation age
is applied to the argument "Smith"
.
Here is what such an expression does:
- It selects those tuples in the relation whose initial element matches the argument.
- It makes a copy of each of the selected tuples, removing the first element from each copied tuple.
- Finally, it inserts the copied and modified tuples into a new relation. This relation is the value of the expression.
In the example above the resulting relation was copied to output
.
If there are no tuples whose initial element matches the argument, then the resulting relation is empty.
In Rel the empty relation {}
is equivalent to false
, so it is natural to interpret an empty result as “No, there are no tuples whose initial element matches the argument”:
// read query
def output = age["Schmidt"]
Square Brackets on the Left
Square brackets can also be written on the left-hand side of a definition. The effect of adding something in square brackets to the left-hand side is to extend each tuple of the relation that is copied from the right-hand side with an additional initial element.
You can use this feature to preserve the elements removed during partial relational application, thus restricting its effect to selecting the interesting tuples:
// read query
def output["Smith"] = age["Smith"]
In general, you can add anything you want:
// read query
def R["Cadet"] = age["Smith"]
def output = R
A relation name followed by square brackets on the left-hand side of a definition is not a partial relational application. You can think of it as almost the opposite to a relational application, which can be used to remove the added elements:
// read query
def R["Cadet"] = age["Smith"]
def output = R["Cadet"]
Multiple Square Brackets
The result of a partial relational application is a relation. If the arity of that relation is not zero, you can use it in a partial relational application, for example like this:
// read query
def output = age["Smith"]["Jane"]
Here you got a relation that contains the ages of those people whose surname is "Smith"
and whose first name is "Jane"
.
When you copy the relation you can extend it by using multiple square brackets on the left-hand side:
// read query
def output["Space"]["Cadet"]["Jane"]["Smith"] = age["Smith"]["Jane"]
Variable Arguments
The argument of a partial relational application can also be a variable:
// read query
def output[y] = age["Smith"][y]
What happened here? The variable y
matched the first element of ("Jane", 20)
, so it was instantiated to "Jane"
and used to produce the value of age["Smith"]["Jane"]
, that is, the relation { (20); (22) }
.
This relation was copied to output
, but each of the tuples in the copy were extended with an additional initial element: the instantiation of y
, that is, "Jane"
.
The variable y
was then instantiated to the other value that occurs in the first elements of the relation age["Smith"]
, which is "John"
.
The value of age["Smith"]["John"]
is the relation { (21) }
.
This was added to output
, extended with the instantiation of y
, that is, "John"
.
In this case the result was as if you had written def output = age["Smith"]
.
But in general you can use variables much more flexibly.
For instance, you can find out the surnames and ages of people whose first name is “Jane”:
// read query
def output[x] = age[x]["Jane"]
You might find it instructive to figure out the details of how the above works. But the intuitive meaning is clear, thanks to the declarative nature of the language.
As another example, you can copy the entire contents of age
, but change the order of names:
// read query
def output[y][x] = age[x][y]
A List of Items in Single Square Brackets
The value of age["Smith"]["Jane"]
is a unary relation, so you can use it in a partial relational application:
// read query
def output = age["Smith"]["Jane"][20]
The first element in the copy of tuple (20)
was removed, leaving the empty tuple ()
.
So the resulting relation is {()}
.
In Rel, the nonempty nullary relation {()}
is equivalent to true
.
The intuitive meaning of the result is “Yes, the relation age["Smith"]["Jane"]
does contain the tuple (20)
.”
You can also interpret this as “The relation age["Smith"]
contains the tuple ("Jane", 20)
,” or “The relation age
contains the tuple ("Smith", "Jane", 20)
.”
Rel allows you to write [x, y]
instead of [x][y]
.
So, depending on the way in which you want to look at your result, you can choose one of the following equivalent definitions:
def output = age["Smith"]["Jane"][20]
def output = age["Smith", "Jane"][20]
def output = age["Smith"]["Jane", 20]
def output = age["Smith", "Jane", 20]
The form with only one set of square brackets is the most frequently used in practice. You can also use this notation on the left-hand side of a definition.
Parentheses
In the last example of a partial relational application the number of arguments was equal to the arity of the relation.
Such an application is no longer really a “partial” one.
Moreover, as you saw, its value can only be {}
or {()}
, that is, either false
or true
.
Rel provides special syntax for this important case. Instead of square brackets you can use parentheses. Such a construct is called a relational application:
// read query
def output = age("Smith", "Jane", 20)
In the context of Rel, an expression whose value must be either false
or true
is called a formula.
So a relational application is an example of a formula.
If the right-hand side of a definition is a formula, you can also use parentheses on the left-hand side of a definition. In fact, this is the recommended style:
// read query
def output(y, x, z) = age(x, y, z)
The difference between parentheses and square brackets is more than just a notational convention.
If the number of arguments in a relational application is smaller than the number of elements in any tuple, then the relational application is always evaluated to false
, and the compiler warns you that there is a mismatch in arity.
// read query
def output = age("Smith", "Jane")

If the relation has a mixed arity, that is, contains tuples of different lengths, then a relational application picks up only those tuples whose length is equal to the number of arguments. If the number of arguments does not match the size of any tuples, no error is reported. Compare the following four examples:
// read query
def mixed {("Wilson", "Adam", 20); ("Willis", "Joseph", 50, "boss") }
def output[x, y] = mixed[x, y]
// read query
def mixed {("Wilson", "Adam", 20); ("Willis", "Joseph", 50, "boss") }
def output(x, y) = mixed(x, y)
// read query
def mixed {("Wilson", "Adam", 20); ("Willis", "Joseph", 50, "boss") }
def output(x, y, z) = mixed(x, y, z)
// read query
def mixed {("Wilson", "Adam", 20); ("Willis", "Joseph", 50, "boss") }
def output(x, y, z, v) = mixed(x, y, z, v)
Note that you can obtain a formula even with a partial relational application.
For instance, the right-hand side of the following definition is a formula, and the variable x
ranges over the unary relation age["Smith", "Jane']
:
// read query
def output(x) { x = age["Smith", "Jane"] }
You can combine this with a variable that ranges over the first names:
// read query
def output(firstName, x) { age["Smith", firstName] = x }
A more conventional way of writing this would be:
// read query
def output(firstName, x) { age("Smith", firstName, x) }
Varargs allow you to express it more succinctly:
// read query
def output(x...) { age("Smith", x...) }
By using varargs you can allow your relational application to pick up tuples of any length:
// read query
def mixed {("Wilson", "Adam", 20); ("Willis", "Joseph", 50, "boss"); ("what?"); () }
def output("LHS", x...) = mixed(x...)
As you can see, with varargs you can use a relational application to simulate a partial relational application in its full generality. To give another example:
// read query
def output(x...) = age("Smith", x...)
This, however, is not the recommended style. Varargs are best reserved for situations in which the arity of the applied relation is not fixed or unknown.
Partial Applications Are Not Functions
In many settings, []
looks like a function call — as in add[1, 2]
or cos[2 * pi_float64]
— but you should remember that the value of such an expression is a relation.
The relation might contain zero elements or more than one element.
In the example below, neighbor[x]
can have zero, one, or two elements for each x
:
// read query
def neighbor(x, y) {
(y = x + 1 or y = x - 1) and x > 0 and y > 0
}
def output["zero"] = neighbor[0]
def output["one" ] = neighbor[1]
def output["two" ] = neighbor[2]
The relation neighbor
— just like add
, multiply
, etc. — is infinite.
In this example you could use it by restricting the domain of x
, which made the partial application neighbour[x]
evaluate to a finite relation.
Similarly, the evaluation of 3 + 4
— which is an alternative form of the partial application add[3, 4]
— yields the finite relation {(7)}
.
You should be aware that add[x, y]
will not work if the domains of x
or y
are not finite.
Operator Distribution
If S
is a unary relation, Rel can also evaluate R[S]
,
which will be the union of R[x]
over all the tuples x
in S
.
For example:
def output = neighbor[{1; 3}]
The result will be {2; 4}
, since neighbor[1]
is {2}
and neighbor[3]
is {2; 4}
.
Other operations distribute similarly. For example:
// read query
def output = {1; 2} + {3; 4; 5}
The expression {1; 2} + {3; 4; 5}
is equivalent to add[{1;2}, {3;4;5}]
.
Note: In the expression R[S]
, the relation S
must have arity 1, unless R
is a higher order definition.
See @inline
Definitions in the Rel Primer: Advanced Syntax guide.
Base and Derived Relations
In the RelationalAI documentation, small relations with sample data are often built into the Rel code, as in the def players = {...}
definition above.
Typically, though, data will be stored on disk, as relations, often representing “raw data” from outside sources, such as JSON or CSV files.
Once written to disk, no definitions are necessary for these relations, called base relations. See Working With Base Relations.
By contrast, relations defined by rules are derived relations, also known in the database world as views.
Derived relations are transient, local only to a query, unless:
- You write their results back to disk, creating new base relations.
- You install their definitions — hence the term “installed view.”
Database logic is defined by a set of rules. Each derived relation can refer to other derived relations, relations defined in libraries such as the Standard Library, and base relations.
Some quick things to know about rules, also known as definitions:
- Their order does not matter.
- They add up because a relation in Rel is the union of its definitions. Suppose you have:
// read query
def myrel = {}
def myrel = 1; 2
def myrel = 2; 3
myrel
will be {1; 2; 3}
.
-
Definitions can be recursive — see Recursion.
-
Relations can be overloaded by arity and type — see Advanced Syntax.
-
Related definitions can be grouped and parameterized together into modules — see Modules.
Optional Schema Declaration
Rel does not require predefined schemas. When you create a base relation or a derived relation in Rel, the system automatically tracks the arity and type of the tuples in the relation. Unlike traditional database systems, you do not have to specify this information beforehand as a “database schema.”
You can, however, enforce schemas if you want. You can introduce integrity constraints that restrict the arity and types of relations, for instance. You can also use bound declarations.
Uses of ,
and ;
Both ,
and ;
are operators in their own right, and it is helpful to be familiar with the multiple ways they can be used.
Tupling, Filtering, Conjunction, and Products
The ,
operator can:
- Make tuples.
- Serve as a boolean filter, analogous to
and
. - Denote cross products, also known as Cartesian products.
In the following example, ,
gives the cross product of relations {1; 2; 3}
and {4; 5}
,
where 4
or 5
is appended to each of {1; 2; 3}
:
// read query
def output = {1; 2; 3}, {4; 5}
The cross product of {1}
and {2}
is {(1, 2)}
, so you can see tupling as a special case of cross product.
Taking advantage of true
and false
being relations, ,
can also be used as a boolean filter,
analogous to an and
operation:
// read query
def myelements = 1; 2; 3; 4; 5; 6; 7; 8; 9
def output(x) = myelements(x), x > 3, x < 7
This works because true
is the relation {()}
(arity 0 and cardinality 1),
and false
is the relation {}
(arity 0 and cardinality 0).
Any cross product with {}
will be {}
, so R, false
is always false
.
And the cross product of any relation R
with {()}
is R
, hence R, true
is always R
.
You can also use the usual connectives and
, or
, implies
, and not
— but, unlike the comma operator, they always expect arity 0 (true
or false
).
This will be further discussed later.
Union and Disjunction
While ,
builds tuples and can concatenate them, the ;
operator builds relations and can combine their definitions in a union:
// read query
def output = 2; {2; 2; 3} ; {3; 3; 4}
// read query
def rel1 = (1, 2); (4, 8); (3, 6)
def rel2 = (3, 6); (2, 4)
def output = rel1; rel2
The ;
operator can be thought of as a disjunction (“or”), and it behaves exactly as or
for arity 0 (boolean) expressions:
true or false
is true
, false or false
is false
, and so on. While or
works only for arity 0 expressions,
the ;
operator also works with
higher arities and still represents a choice.
For example, {1; 2}(x)
serves as 1 = x or 2 = x
here:
// read query
def output(x, y) {
{1; 2}(x)
and {4; 5; 6}(y)
and x + y < 7
}
Definitions
You have already seen a few examples of relation definitions (def
s) in Rel.
A Rel model is a collection of such definitions, called “Rel sources”,
which can be defined in terms of each other, even recursively.
The RAI query engine answers queries by combining these definitions with known data to compute a requested result.
The query comes in the form of a relation, called output
, that you want to compute.
The order in which the definitions are written down is not relevant. For example:
// read query
def output(x) = odddata(x) and x > 5
def mydata = {1; 2; 3; 4; 5; 7; 8}
def odddata(x) = mydata(x) and x % 2 = 1
def mydata = {9; 10; 11}
Here, mydata
is the union of its two definitions,
and it is not a problem that output
goes first,
or that odddata
is introduced before mydata
, on which it depends.
One very important property of definitions is the following:
- If a relation Q is defined in terms of relation R, then subsequent changes to R will be reflected in Q.
The following example defines three relations.
R
is defined in terms of P
and Q
.
// model
def P = (1, "a", "A"); (2, "b", "B")
def Q = 2; 3; 4
def R["letter"] = P[Q]
The contents of R
are as expected:
// read query
def output = R
If you now add new tuples to P
and Q
, relation R
will be automatically updated:
// read query
def P = (3, "c", "C")
def Q = 1
def output = R
Relational Abstraction
Recall that a relation is a set of tuples. One way to describe a relation is to write down a formula that is true
exactly for those tuples in the relation
and false
for all others.
You can see this most clearly in definitions
that introduce a variable for each column in the left-hand side of the definition, as in
def myrel(x1, x2, ..., xn) = ...
and then give a boolean expression on the right-hand side that constrains those variables:
// read query
def mydomain(x) = range(1, 7, 1, x) // x will range over 1; 2; 3; 4; 5; 6; 7
def myrel(x, y) {
mydomain(x)
and mydomain(y)
and x + y = 9
}
def output = myrel
Note: The Rel Standard Library has many utilities including the one used here:
range(start, stop, step, x)
constrains x
to range over all the elements between start
and stop
,
inclusive, skipping by step
each time.
This style is preferred, since it leads to more readable definitions.
However, relations can also be specified using relational abstraction, indicated by :
in Rel.
The definitions above are equivalent to:
// read query
def mydomain = x : range(1, 7, 1, x)
def myrel {
x, y : mydomain(x)
and mydomain(y)
and x + y = 9
}
def output = myrel
This use of :
is similar to the vertical bar used for describing mathematical sets (opens in a new tab) that separates the variables from the conditions (for example: ).
There are variables on the left of the :
, a boolean condition over those variables on the right,
and the set contains all the combinations of elements for those variables (tuples) that make the condition true.
But in Rel, :
can do much more.
The expression Expr
in <vars> : Expr
does not have to be a boolean formula, that is, it can have arity greater than 0.
If Expr
has arity , the corresponding elements from Expr
are appended to <vars>
, and the
total arity will be the number of variables in vars
plus . For example:
// read query
def mydomain = x : range(1, 4, 1, x)
def myrel = x : x * 5, mydomain(x)
def output = myrel
This is similar to how ,
works. For both :
and ,
,
when the right-hand side is a boolean expression (has arity 0), it works as a filter.
If the right-hand side has arity greater than 0,
then the expression is appended to the tuple.
This offers a way to do group-by aggregations, as you will see later in the Group-By section.
The arity of bindings : expression
is the number of variables in bindings
plus the arity of expression
.
Style Note:
When you define a relation using the def myrel = x1, ..., xn : Expr
style,
the arity of myrel
depends on the arity of the expression on the right (Expr
),
which might not be clear at first sight. You only know that myrel
will have arity of at least .
By contrast, the def myrel(x1, ..., xn) = ...
definition explicitly calls out the arity of the relation — exactly — which is why it is preferred.
Binding Shortcuts
It is often useful to put filters in the left-hand side of :
— called bindings.
For this, you can use in
and where
, which can be used directly wherever variables are introduced,
to restrict the domain of the variables. For example:
// read query
def mydomain(x in range[1, 7, 1]) = true // same as: def mydomain = range[1, 7, 1]
def myrel = x in mydomain, y in mydomain where x + y = 9 : x * y
def output = myrel
Note that while in
restricts the domain of a single variable, where
can restrict combinations of variables.
Note: in
— and its equivalent symbol ∈
— is not a stand-alone boolean predicate and can be used only in bindings. If you want to express x in R
in a boolean formula, just write R(x)
.
You can define many different kinds of relations using the machinery you have seen. Check out this next feature that is widely used in Rel to make the code more concise.
Arity
Parentheses Versus Square Brackets
The Rel compiler checks that all the arities in the code match up and reports an error if they do not.
When you write, say, myrel(x, y, z)
, you are indicating that myrel
must have arity 3.
The expression myrel(x, y, z)
will evaluate to either true
or false
, which are represented in Rel as relations of arity 0.
By contrast, writing myrel[x, y, z]
implies that the arity of myrel
is at least 3, and the statement evaluates to a relation with an arity that is 3 less than the arity of myrel
.
For more details on how partial relational application works, see Relational Application.
In the case that myrel
has arity 3, the partial relational application myrel[1, 2, 3]
is equivalent to a “complete” relational application myrel(1, 2, 3)
.
Both expressions evaluate to relations of arity 0 — which are either true
or false
.
Boolean Formulas
Writing, say, {1} and p(x)
or sin[x] or q(x)
, gives an arity error.
The boolean connectives require arity 0 for both arguments, and
their result has arity 0.
When you write myrel[1, 2, 3]
, you expect myrel
to have arity of at least 3.
If you write myrel(1, 2, 3)
, you require myrel
to have arity of exactly 3.
Arity and Defs
When you write def myrel(a, b, c) = E
, the arity of E
must be 0 because it should have a boolean value, and the arity of myrel
is 3.
When you write def myrel[a,b,c] = E
, then E
can have arbitrary arity, and the arity of myrel
will be 3 plus the arity of E
.
Format
In Rel neither indentation nor line breaks matter. The style conventions adopted on the website are for readability purposes.
Consider the following code:
// read query
def players {
("Neymar", "PSG");
("Messi", "BFC");
("Werner", "Chelsea");
("Pulisic", "Chelsea")
}
def output = players
ic {count[players] = 4}
It could also be written like this:
// read query
def players {
("Neymar", "PSG");
("Messi", "BFC");
("Werner", "Chelsea"); ("Pulisic", "Chelsea") }
def output = players ic {count[players] = 4}
Both yield the same result. Indeed, the two code blocks are not just correct in Rel, they are also syntactically identical and render the same output.
Even though the second code block is syntactically identical to the first, it is highly recommended to use spaces and line breaks to make your code readable and maintainable.
Standard Library
Rel includes a Standard Library with many functions and utilities; for example, range
, maximum
,
minimum, sin, cos,
and many other mathematical operations.
The Standard Library also includes higher order definitions that take relations as arguments,
such as argmax
, argmin
, and first
.
Help
The query help[:name]
— or, in module syntax, just help:name
— will display a brief docstring for each relation.
For example, help:argmax
, help:range
, or help:min
.
These relations are currently imported into the main Rel namespace.
Therefore, unexpected outcomes may occur when users define their own relations with the same names.
Some names to avoid are: domain
, function
, total
, first
, last
, and equal
.
A quick way to check whether a name is reserved is to try help:name
.
Summary
This article has covered the basics of Rel syntax. The next in the Rel Primer series focuses on aggregations, group-by, and joins, followed by more advanced features of Rel.