We recently took a look at ten ways to improve your database schema, followed by ten more! Schema is all about setting definitions and expectations for your database, after which the operations are entirely passive. This blog post takes a look at the other part of working with your data: how to get the most out of active queries.
Did you know that you can...
1. Use the ?? operator to specify a default value for a field
The following person records are pretty similar, except that one doesn't have a value for the age field.
A query on both name and age works fine, with age showing up as the value NONE for one of them.
But you might need to pass this information into an app that expects a value, such as either an integer or the value "unknown". In that case, you can change age to age ?? "unknown". An easy mnemonic to remember this syntax:
"Is there a value for age?? If not, return "unknown""
After making that change, be sure to add AS age so that the field name stays as age instead of "age ?? 'unknown'", the expression used to calculate it.
2. Use the ? operator to skip out ahead of time and avoid errors
Another questionable SurrealQL operator is ?, which lets you avoid errors by skipping out ahead of time and returning NONE.
Let's add a value for friends for the above queries, which contains an array of record IDs. Here as well, only one person record has a value for this field.
This time we get an error, because .len() (the function array::len()) can't be called on a NONE.
A dot and a question mark is enough to fix this. If the query finds no value for the field, it will give up at that point and the .len() method will not be called.
The ?? can be used here too, because friends? may end up returning a NONE value, which is what the ?? operator looks for.
3. Use a SELECT expression at any point inside a graph query
Take the following example that uses graph relations to connect a recipe with its ingredients.
A query on the ingredients can be done by using .{} on the contains edge and selecting which fields you want to display.
Now what if you wanted to only show the main ingredients, those with the largest quantity? No problem here either, because SELECT can be used at any point inside a graph query. Just wrap it in parentheses and use SELECT as you normally would. The only difference is that SELECT at this point will be done on only the output from the previous step (the -> arrow), instead of the whole contains table.
4. Use the SPLIT clause instead of [0]
This example shows two companies, one the parent of the other.
To see each company's parent company, you can use a graph query. Since a company can only have a single parent company, parentheses are used to grab the expression and then [0] to access the first record.
Using SPLIT is another option here, which will return one object for each result at the <-owns<-company path. This will remove company:one from the results because there is nothing for it to split, so in this case SPLIT also acts as a filter.
In addition, using SPLIT shows that we mistakenly used a RELATE statement twice for the same two companies!
(Did you catch that?)
By the way, you can ensure that the two RELATE statements couldn't happen in the first place by creating a UNIQUE index. While this is a query tips and not schema tips blog, we would be remiss to not mention this fact!
5. Graph edges can have non-standard IDs too
The usual way to add metadata to a graph edge is by using RELATE and then SET to add the fields.
The liked_at edge will have a random ID like likes:geci4id19wk3k5g45iqb.
You can then use a filter query of this sort to see a user's posts over the last five days. This approach is great if you are doing queries from the user's side.
However, if you are using a lot of queries from the likes table itself, you might want to opt for a complex array ID such as this one that includes a record and a datetime.
The relation can be created in the same way, except that this time the ID of the edge is specified.
If the RELATE statement can't parse the ID you are attempting to pass in, you can use INSERT RELATION instead.
With this complex ID, you can use the former query in almost the same way (changing liked_at to id.liked_at) while also being able to use a record range query on the likes table itself, thereby avoiding a full table scan.
6. You can use fields for polymorphic relationships in graph queries
A graph query can be done on multiple types of graph edges. The example below shows how you can combine multiple edges into a single field.
If you feel like you are using too many types of edge relations, you can combine them into a single relation and differentiate by field values instead.
Now there is only one edge connecting person records, but you can use the [WHERE] syntax at any point to filter by relationship type.
7. Use SELECT to get around current closure limitations
A current limitation of SurrealQL is that closures are not aware of variables. In the query below, this means that the $now parameter will show up as NONE instead of a datetime when attempting to map some record IDs into an array of objects.
However, since .map() returns an array of objects with field names, these field names can be selected in the usual way - along with the $now parameter as the created_at field.
8. Use .reduce() for the time being
Parameters in SurrealDB are currently immutable, meaning that this sort of pattern to increment the value of a parameter will not work.
Functions like array::reduce(), array::fold(), and .chain() can be used in these cases instead.
The array::reduce() function performs an operation on each item in an array, array::fold() does the same but with an extra initial value, and .chain() allows you to take any value and perform one operation on it.
9. Use statements inside another statement
This example shows a laptop that is created and assigned to an new employee.
However, when creating records that depend on each other it can be preferable to do the entire creation inside a single query. This will allow all of the statements to fail at once if something has gone wrong, and you won't have to manually undo any previous statements.
This time we'll have the dob field as a datetime.
Oops! The value '1985-09-01T00:00:00Z' that we passed in is a string and not a datetime.
Fortunately, both create statements for employee and computer failed so we still have a clean slate to work with. All we need to do is add a d in front of the date to have the input recognised as a datetime and not a string.
10. Add metadata with RETURN
This last tip is about a functionality that is pretty basic but easy to forget about.
There are quite a few options available to you when using RETURN after a query. You can RETURN BEFORE, RETURN AFTER, RETURN DIFF, RETURN VALUE field_name, or just about anything else.
Returning a variable output makes it possible to return some content once without touching the actual saved data. For example, you could return a message for a developer that you know is monitoring the database activity and needs to be alerted to something.
But the original user record will stay untouched.
