Search Records - SurrealTools/Documentation GitHub Wiki

Get data from array

  • array.* to get all fields,
  • array.{foo, bar} to get foo, bar
  • array.id to get the id
  • array.nested.thing

I always forget that this SurrealQL magic exists:

WHERE [first, last, birthday] = [$first, $last, $birthday];

The whole query language is designed to operate around embedded fields, and nested documents...

-- objects
UPDATE person:tobie SET my.embedded.object.field = "Test";
SELECT my.embedded.object.field AS my_field FROM person:tobie; -- returns "Test"
-- or arrays
UPDATE person:tobie SET my.embedded.tags = [], my.embedded.tags += "Golang", my.embedded.tags += "Rust";
SELECT my.embedded.tags[1] FROM person:tobie; -- returns "Rust";

Querying for a field subset requires Surreal to load full row first internally before plucking out the requested fields.

If you want to pull out a record itself, make sure you are doing... as that will not scan a whole table...

SELECT * FROM person:1;
-- and not
SELECT * FROM person WHERE id = person:1;

select a subset of fields To select a subset of fields using dot notation (or any notation) from related records.

SELECT  name, (SELECT id, name FROM $parent.friends) AS friends FROM person;

= Operator

The = operator works sort of like the == operator in JS and the == operator in surreal works like the === in JS i.e. it does exact comparisons.

SELECT * FROM test WHERE active == false; // returns all records with active = false

Casting NULL value

SELECT <option<datetime>> date FROM appointments;

Cursor based pagination

# That will only use a single extra read, which will have negligible impact on performance!
SELECT * FROM users:983fjka98434.. LIMIT 10 START 1;

Ensure value is always an array

to ensure that "sessions" is always an array, no matter the existance of any sessions:
SELECT ->attends[where status = "confirmed"]->session ?? [] as sessions FROM $client;

Select Nested Object Based on a Condition

SELECT address.active = true FROM person;

Path based querying

-- An array filter, followed by an array index operation
SELECT tags[WHERE value CONTAINS 'gmail'][0] FROM user;

-- Selecting an object value or array index using a string as a key
SELECT language['en-gb'] AS content FROM documentation;
-- Updating an object value or array index using a string as a key
UPDATE documentation:test SET language['en-gb'] = 'my english text';

LET $lang = 'en-gb';
-- Selecting an object value or array index using a parameter as a key
SELECT language[$lang] AS content FROM documentation;
-- Updating an object value or array index using a parameter as a key
UPDATE documentation:test SET language[$lang] = 'my english text';

-- Selecting an object or array index value using the value of another document field as a key
UPDATE documentation:test SET primarylang = 'en-gb';
SELECT language[primarylang] AS content FROM documentation;

Exclude a field

SELECT *, NONE AS field FROM posts

Flattened query results

SELECT VALUE name FROM user

Combining nested field values into a unique array

-- Person table
[
    {
        email: '[email protected]',
        firstname: 'Jan',
        id: person:0b3b952c565155fcb0ae9acfedf99315,
        lastname: 'Morkel',
        program: [
            'flights',
            'apps'
        ]
    },
    {
        email: '[email protected]',
        firstname: 'Sam',
        id: person:8bc35665018b595b95bd9b2e909ed651,
        lastname: 'pedlar',
        program: [
            'entertainment',
            'flights'
        ]
    }
]
SELECT array::group(program) FROM person GROUP ALL

Sub-Queries

  • Select more than 1 field!
  • Ensure you alias the age > 18 to a field name!
SELECT * FROM (SELECT *, age > 18 AS adult FROM user) WHERE adult = true;

Retrieve the last element in an array of each property

--- $ refers to the last element in an array
SELECT *, sports[$] as sports FROM person

Dynamic Table

LET $table = "users"
SELECT * from type::table($table);

Select Id only

SELECT *, meta::id(id) AS id, meta::id(thing) AS thing FROM table;

-- This will return 'person'
SElECT * FROM meta::tb(person:tobie);
-- This will return 'tobie'
SElECT * FROM meta::id(person:tobie);

select fields from a graph part

SELECT
  id, name, slug, description, isPrivate, categories, createdAt, updatedAt, createdBy.id, createdBy.username, <-joined<-user.id AS team.id, <-joined<-user.username AS team.username
FROM module

Faster than using WHERE clause

SELECT * FROM person:1, person:2, person:3

Moving averages with view

-- Define a table as a view which aggregates data from the reading table
DEFINE TABLE temperatures_by_month AS
  SELECT
    count() AS total,
    time::month(recorded_at) AS month,
    math::mean(temperature) AS average_temp
  FROM reading
  GROUP BY city
;

-- Add a new temperature reading with some basic attributes
CREATE reading SET
  temperature = 27.4,
  recorded_at = time::now(),
  city = 'London',
  location = (-0.118092, 51.509865)
;

Query Complex Id

Ids are always represented as strings, but on the nightly version you can query the complex parts of an id.

-- With an array
CREATE person:['[email protected]'] SET name = 'Tobie';
-- Or with an object
CREATE person:{ email: '[email protected]' } SET name = 'Tobie';
create user_colour:['Red', user:other] content {user: user:other};
create user_colour:['Blue', user:hugh] content {user: user:hugh};

You can then do...

- With an array, like above
SELECT id[0] FROM person WHERE name = 'Tobie';
-- Or with an object, like above
SELECT id.email FROM person WHERE name = 'Tobie';

select * from user_colour:['Red', NONE]..
[{ id: user_colour:['Red', user:one], user: user:one }, { id: user_colour:['Red', user:other], user: user:other }]

Javascript Function in SELECT

CREATE data:2 CONTENT { pi: 3 };
SELECT * from data WHERE  function() { return this.pi % 2 } > 1;
SELECT *, function() { return this.pi % 2 } as mod from data:1;

Refer Parent in Subquery

-- Use the parent instance's field in a subquery
SELECT *, (SELECT * FROM events WHERE host == $parent.id) AS self_hosted FROM user;

ORDER BY Expressions

In SurrealDB you do need to add the field in the SELECT expression, in order to be able to ORDER BY that field.

CREATE person:1 SET test = [32, 46, 58, 31];
CREATE person:2 SET test = [55, 66];
CREATE person:3 SET test = [55, 66, 44];
SELECT id, count(test) AS total FROM person ORDER BY total;

Select (and try to filter) on multiple items in an array

when you select (and try to filter) on multiple items in an array (tags.*.value), then you are actually comparing an array of values, and must therefore use an operator which searches within an array...

SELECT * FROM article WHERE tags.*.value CONTAINS '';

Filters on an array of objects with a relation member

SELECT friends[WHERE relation = 'brother'].who.name FROM person:tobie;

-- `files` is an array, so you need to use CONTAINS as `disk_id` will be an array of disk_id
SELECT id FROM invoices WHERE files.disk_id CONTAINS 'something';
-- `files` is an array, so you can use an array filter to filter the array, using $this to refer to each item
SELECT id FROM invoices WHERE files[WHERE $this.disk_id = 'something'];
-- you can also just write it with a `?` instead of a `WHERE` keyword
SELECT id FROM invoices WHERE files[? $this.disk_id = 'something'];
-- ----------
-- If you wanted to check whether any `files.disk_id` contains a particular string, and not equals, then you would HAVE to use the array filter..
SELECT id FROM invoices WHERE files[WHERE $this.disk_id CONTAINS 'something'];

Filter on an array of primitives

CREATE temp:dataset SET data = [
    "a",
    "b",
    "c",
    "invalid",
    "invalid"
];
SELECT data[WHERE $this != "invalid"] FROM temp:dataset;

Elvis operator

SELECT "a: " + (a || "nothing") FROM thing

Fetch partial records

FETCH replaces any Record ID at the specified path, with the full contents of that Record ID. If you want to select only parts of that record id, then you can do that with...

SELECT *, crew.id, crew.name, crew.certifications.*.name FROM ships FETCH crew, crew.certifications;
// Without fetch
SELECT friends.*.* from users

FETCH is not required if you are specifying the path.name field to retrieve...

SELECT *, path.name AS path FROM folder;
-- 1. Select more than 1 field!
-- 2. Ensure you alias the `age > 18` to a field name!
SELECT * FROM (SELECT *, age > 18 AS adult FROM user) WHERE adult = true;

Pattern Matching

SELECT * FROM person WHERE name CONTAINS "something"; -- contains
SELECT * FROM person WHERE name = /something/; -- matches regex
SELECT * FROM person WHERE email = /gmail.com$/; -- ends with gmail.com
SELECT * FROM person WHERE email ~ "GMail"; -- fuzzy matches
SELECT * FROM person WHERE emails.*.value ?= /gmail.com$/; -- any email value ends with gmail.com
SELECT * FROM person WHERE emails.*.value ?~ "GMail"; -- any email value fuzzy matches
SELECT * FROM person WHERE emails.*.value *= /gmail.com$/; -- all email values end with gmail.com
SELECT * FROM person WHERE emails.*.value *~ "GMail"; -- all email values fuzzy match

CONTAINS Queries

The CONTAINS operator is case sensitive.

CREATE person:test SET tags = ['one', 'two', 'three', 'fourth'];
SELECT * FROM person WHERE tags CONTAINS 'one'; -- true
SELECT * FROM person WHERE tags CONTAINSNOT 'four'; -- true
SELECT * FROM person WHERE tags CONTAINSANY ['one', 'four']; -- true
SELECT * FROM person WHERE tags CONTAINSALL ['one', 'four']; -- false
SELECT * FROM person WHERE tags ?~ 'four' -- true
SELECT * FROM person WHERE tags ?~ 'for' -- true

Fuzzy matches

The contains operator matches case sensitively for what it’s operating on.You could do something like this (it's not the same because it fuzzy matches)...

SELECT * FROM ["one", "two", "three"] ?~ "THree"; -- true
-- But it wil also match
SELECT * FROM ["one", "two", "three"] ?~ "THr"; -- true
SELECT * FROM ["one", "two", "three"] ?= /^(?i)Three$/; -- true

SELECT * FROM user WHERE email = /(.*)@gmail.com/;
-- Using fuzzy matching
SELECT * FROM user WHERE email ~ 'gmail.com';
  • ?= operator check that ANY item in the array is equal to...
  • ^ part of the regex matches the start of the string
  • (?i) part of the regex matches insentively
  • $ part of the regex matches the end of the string

Array Types

SELECT friends[*].who.name FROM person:tobie;
-- or
SELECT friends.*.who.name FROM person:tobie;
SELECT friends[WHERE relation = 'brother'].who.name FROM person:tobie;

SELECT email, todos[0].content, todos[0].tag.name FROM user:1;

Id with a number

SELECT * FROM `98d4057080a33f103b26`;
-- or
SELECT * FROM type::table('98d4057080a33f103b26');

Time-series records

-- Create a record with a complex ID using an array
CREATE temperature:['London', '2022-08-29T08:03:39'] SET
    location = 'London',
    date = '2022-08-29T08:03:39',
    temperature = 23.7
;
-- Create a record with a complex ID using an object
CREATE temperature:{ location: 'London', date: '2022-08-29T08:03:39' } SET
    location = 'London',
    date = '2022-08-29T08:03:39',
    temperature = 23.7
;
-- Select a specific record using a complex ID
SELECT * FROM temperature:['London', '2022-08-29T08:03:39'];

Then you can also use record ranges to query specific ranges of records...
-- Select all person records with IDs between the given range
SELECT * FROM person:1..1000;
-- Select all temperature records with IDs between the given range
SELECT * FROM temperature:['London', '2022-08-29T08:03:39']..['London', '2022-08-29T08:09:31'];

Hide a field

SELECT *, NONE AS email FROM user:1;

Live Queries on Aggregate

You won't ever be able to run aggregate (GROUP BY clauses) on LIVE queries, but you can do it a different/better way... What you will be able to do is create a computed view...

DEFINE TABLE person_by_age AS
  SELECT math::mean(age), country FROM person GROUP BY country
;

and then create a live query which watches that table/view...

LIVE SELECT * FROM person_by_age;

This would then be more efficient, as the live query is only being 'activated' for single record changes, and not potentially large whole tables.

Single Array Item

LIMIT on the subquery will return single item. SurrealDB knows that you just want 1 record, so makes it a record link, not an array of record links... This query only return single user from this sub-query.

INSERT INTO post {
  author: (SELECT id FROM user WHERE name = 'Anna' LIMIT 1)
};

Fetch a nested record link

I have 3 tables: country, state and city. I want to 'select * from city fetch state', but also want to fetch the country from the state.

-- fetching multiple fields
SELECT * FROM city FETCH state, country;
-- or fetching further, from the fetched field
SELECT * FROM city FETCH state, state.districts
-- or using the select clause instead
SELECT *, state.*, country.* FROM city;
-- or fetching further in more nested records
SELECT *, state, state.districts AS districts FROM city;
-- You can specify a field on an array of objects like so
SELECT * FROM person FETCH links.*.inner_id;
-- This can also be re-written by dropping the '*', and will by default go into each item in the array
SELECT * FROM person FETCH links.inner_id;

Check if a field exists

SELECT * FROM person WHERE email !== NONE;

select an element from array by index

You can't do array indexing (yet) on an array itself, but you can on fields, and params...

LET $value = [1,2,3];
SELECT * FROM $value[1];

Separate Parameters from query with Javascript client

We can use params...

LET $name = "Zafar"
LET $other = "sdfsdf"
UPDATE type::thing('user', $id) SET name = $name, something.else = $other;

You can then submit data to the query in the client libraries...

let people = await surreal.query("SELECT * FROM article WHERE status INSIDE $status", {
  status: ["live", "draft"],
});

Sending a query with variable in HTTP request

You send them as query parameters in the URL.

Fetch

FETCH only fetches full records - you can't select specific fields. You would need to use subqueries if you want to select specific fields...

SELECT *, (SELECT id, username FROM $parent.createdBy) AS createdBy FROM gigs;

Select without From

RETURN users:983fjka98434.*
-- Return something explicitly
RETURN person:jaime.brother.name;
RETURN person:tobie.*;
-- Return something more complex
RETURN {
    tobie: person:tobie.name,
    jaime: person:jaime.name,
};

Q and A

Question How do you get items for today i.e i want to get products created on 20-04-2024 which is today?

time::round(created_at, 1d) = time::round(time::now(), 1d)

Question: How can I see how many cars does the person have without adding a vehicleType to Car and Bike tables? is something like this possible? select vehicle from person where (type vehicle == car) ??

Answer: we could improve this so you can detect the record type, but in the meantime ...

DEFINE FIELD vehicles ON TABLE person TYPE array;
DEFINE FIELD vehicles.* ON TABLE person record (car, bike);
DEFINE FIELD type ON TABLE car VALUE 'car';
DEFINE FIELD type ON TABLE bike VALUE 'bike';
SELECT vehicles[WHERE type = 'car'] FROM person;

Question: How do you select an item that contains an object in an array that contains a value that equals something.

SELECT * FROM word
  WHERE
    history.*.name -- this is an array so we need to use an operator which works for arrays of values...
    CONTAINS 'Wordie'; -- where the name field is exactly 'Wordie' case sensitive
  
  OR
  
  SELECT * FROM word
  WERE
    history.*.name -- this is an array so we need to use an operator which works for arrays of values...
    ?~ 'wordie'; -- where any name field fuzzy matches 'wordie'
  
  OR
  SELECT * FROM word
  WERE
    history.*.name -- this is an array so we need to use an operator which works for arrays of values...
    ?= /^(?i)wordie$/; -- where any name field equals a regular expression (case insensitive)
  
  SELECT *, history[WHERE name = 'Wordie'] FROM word; -- This will retrieve all words, but will filter the 'history' to only matching objects...

Question: When making queries like SELECT * FROM table; is there an easy way to remove the table name from the id table:id -> id?

Answer: A new function coming in v1.0.0-beta.8

-- This will return 'person'
SElECT * FROM meta::tb(person:tobie);
-- This will return 'tobie'
SElECT * FROM meta::id(person:tobie);

Question: How could I return the string '2022-10-03' from a date (2022-10-13T00:00:00Z)?

SELECT *, string::slice(date, 1, 10) AS date FROM schedule

Question: How do you limit results returned from a FETCH?

Currently you could do something like the following...

SELECT *, (SELECT ->posts.* FROM $parent LIMIT 3) FROM user:1;
⚠️ **GitHub.com Fallback** ⚠️