SELECT statement
The most common way to select a record from the database is by using the @
at-operator.
FROM CARDINALITY WHERE WHAT TAIL
entity_name @ { condition } () ;
The operator is separated into five parts: FROM, CARDINALITY, WHERE, WHAT, and TAIL.
- FROM: represents the table from where to make the query. Same as SQL FROM.
- CARDINALITY: specifies the number of
results that are expected from the query. The query will fail if this condition can't be matched.
- @ exactly one result
- @? zero or one result
- @+ more than one result
- @* zero or many results
- WHERE: represents a filter similar to the WHERE part of an SQL query.
- WHAT: The actual values to retrieve from the query. It can be one or several attributes or left empty to get a reference (rowid) of the entity. Here, you can also sort, group, or omit fields.
- TAIL: Additional SQL-related statements such as
limit
oroffset
that can be used when retrieving multiple results.
The at-operator also works on collection types such as list<T>
and set<T>
as a powerful replacement for the for
loop.
Implicit select statements
There are also cases where SELECTs are performed without the reader noticing. This happens when we access an attribute of an entity.
val attribute = entity_instance.attribute;
This will result in the following SQL query:
SELECT "attribute" FROM "entity_name" WHERE "rowid" = ? ORDER BY "rowid"
To make the database roundtrip explicit, we can form the following at-expression:
val attribute = entity_name @ { $ == entity_instance } ( .attribute );
The expression provided above will correspond to an equivalent SQL expression.
You can use the .to_struct()
function to retrieve all attributes from an entity. This will give you an in-memory
representation of the entity. However, it is important to note that this will also create a database roundtrip to
retrieve the values. This means that the following statements are equivalent:
val struct1 = entity_instance.to_struct();
val struct2 = entity_name @ { $ == entity_instance } (struct<entity_name>(attribute = .attribute, ...));
Select statement example
Based on the above, we see that excessive use of attribute access can lead to many SELECTs. Have a look at the following function:
function is_mansion(house) {
if (house.floor_area <= 460) return false;
if (house.number_of_rooms < 10) return false;
return house.number_of_floors > 1 and house.number_of_floors <= 3;
}
This may seem like a reasonable function, but in reality, this will perform four database roundtrips, getting the attributes one by one. Putting this in a loop makes the situation even worse:
val houses = house @* {};
for (house in houses) {
if (is_mansion(house)) print("Found a mansion");
}
The number of database roundtrips will increase linearly with the size of the database table.
This means that passing entity references to functions as arguments is really dangerous as it can lead to a lot of unnecessary database roundtrips.
Consider the difference when we change the signature to function is_mansion(house: struct<house>)
. With this change,
we can retrieve all the data we need in a single database statement:
val houses = house @* {} ($.to_struct());
for (house in houses) {
if (is_mansion(house)) print("Found a mansion");
}
Don't pass entity references in functions, as it might lead to additional database roundtrips that can grow uncontrollably in the worst case.
Practical example
Add the following query to the queries.rell
file stored in the src/main
directory.
This query retrieves all house
records:
query get_all_mansion() = house @* { .floor_area >= 406, .number_of_rooms > 10, .number_of_floors > 1 } ( $.to_struct() );
You can invoke the get_all_mansion
query using the chr
command in the CLI:
chr query get_all_mansion
The result of this query looks as follows:
[
[
"floor_area": 500,
"number": 24,
"number_of_floors": 2,
"number_of_rooms": 12,
"street": 2
],
[
"floor_area": 700,
"number": 30,
"number_of_floors": 3,
"number_of_rooms": 15,
"street": 2
]
]