# Querying

# Selecting all columns

All columns from all joined tables can be queried with SELECT * statement.

SELECT *
FROM products;

# Selecting specific columns

SELECT id, customer_name, ip
FROM customers;

# Filtering entries

SELECT id, customer_name, email
FROM customers
WHERE country = 'SE' AND city = 'Arvika';

# Ordering by column value

SELECT id, customer_name
FROM customers
ORDER BY customer_name ASC;

Currently only one column can be used in the ORDER BY clause.

The order may be either ascending (ASC suffix, default) or descending (DESC suffix).

Ordering rows by a value of a column requires a matching index on that column.

# INNER JOIN

immudb supports standard SQL INNER JOIN syntax. The INNER join type is optional.

SELECT *
FROM orders
INNER JOIN customers ON orders.customerid = customers.id;

SELECT *
FROM orders
JOIN customers ON orders.customerid = customers.id
WHERE orders.productid = 2;

SELECT * FROM orders
JOIN customers ON customers.id = orders.customerid
JOIN products ON products.id = orders.productid;

# LIKE operator

immudb supports the LIKE operator. Unlike in other SQL engines though, the pattern use a regexp syntax supported by the regexp library in the go language (opens new window).

A NOT prefix negates the value of the LIKE operator.

SELECT product
FROM products
WHERE product LIKE '(J.*ce|Red)';

SELECT product
FROM products
WHERE product NOT LIKE '(J.*ce|Red)';

SELECT id, product
FROM products
WHERE (id > 0 AND NOT products.id >= 10)
  AND (products.product LIKE 'J');

# IN operator

immudb has a basic supports for the IN operator.

A NOT prefix negates the value of the IN operator.

Note: Currently the list for the IN operator can not be calculated using a sub-query.

SELECT product
FROM products
WHERE product IN ('Milk', 'Grapes - Red');

SELECT product
FROM products
WHERE product NOT IN ('Milk', 'Grapes - Red');

SELECT id, product
FROM products
WHERE (id > 0 AND NOT products.id >= 10)
  AND (product IN ('Milk', 'Grapes - Red'));

# Column and table aliasing

SELECT c.id, c.customer_name AS name, active
FROM customers AS c
WHERE c.id <= 3 AND c.active = true;

SELECT c.id, c.customer_name AS name, active
FROM customers c
WHERE c.id <= 3 AND c.active = true;

Table name aliasing is necessary when using more than one join with the same table.

# Aggregations

Available aggregation functions:

  • COUNT
  • SUM
  • MAX
  • MIN
  • AVG
SELECT
    COUNT(*) AS c,
    SUM(age),
    MIN(age),
    MAX(age),
    AVG(age)
FROM customers;

# Grouping results with GROUP BY

Results can be grouped by a value of a single column. That column must also be used in a matching ORDER BY clause.

SELECT COUNT(*) as customer_count, country
FROM customers
GROUP BY country
ORDER BY country;

# Filtering grouped results with HAVING

SELECT
    active,
    COUNT(*) as c,
    MIN(age),
    MAX(age)
FROM customers
GROUP BY active
HAVING COUNT(*) > 0
ORDER BY active DESC;

# Sub-queries

The table in the SELECT or JOIN clauses can be replaced with a sub-query.

SELECT * FROM (
    SELECT id, customer_name
    FROM customers
    WHERE age < 30
)
INNER JOIN customer_review
    ON customer_review.customerid = customers.id;

SELECT * FROM (
    SELECT id, customer_name
    FROM customers
    WHERE age < 30
) AS c
INNER JOIN (
    SELECT * FROM customer_review
) AS r
    ON r.customerid = c.id;

Note: the context of a sub-query does not propagate outside, e.g. it is not possible to reference a table from a sub-query in the WHERE clause outside of the sub-query.

# Combining query results with UNION

It is possible to combine multiple query results with the UNION operator.

Subqueries must select the same number and type of columns. The final return will assign the same naming as in the first subquery, even if names differ.

SELECT customer_name as name
FROM customers
WHERE age < 30
UNION
SELECT seller_name
FROM sellers
WHERE age < 30

Subqueries are not constrained in any way, they can contain aggregations or joins.

Duplicate rows are excluded by default. Using UNION ALL will leave duplicate rows in place.

SELECT AVG(age) FROM customers
UNION ALL 
SELECT AVG(age) FROM sellers

# Transactions

The ACID (Atomicity, Consistency, Isolation, and Durability) compliance is complete.

Handling read-write conflicts may be necessary when dealing with concurrent transactions. Getting the error ErrTxReadConflict ("tx read conflict") means there was another transaction committed before the current one, and the data it read may have been invalidated. MVCC (opens new window) validations have not yet been implemented, therefore there may be false positives generated. In case of conflict, a new attempt may be required.

BEGIN TRANSACTION;
    UPSERT INTO products (id, price, product)
    VALUES (4, '$5.76', 'Bread');

    INSERT INTO orders(productid, customerid)
    VALUES(4, 1);
COMMIT;

# Time travel

Time travel allows you to read data from SQL as if it were in a previous state or from a specific time range. Initial and final points are optional and can be specified using either a transaction ID or a timestamp.

The temporal range can be used to filter out rows from the specified (physical) table, but it is not supported in subqueries.

The initial point can be inclusive (SINCE) or exclusive (AFTER). The final point can be inclusive (UNTIL) or exclusive (BEFORE).

SELECT id, product, price
FROM products BEFORE TX 13
WHERE id = 2;
SELECT * FROM sales SINCE '2022-01-06 11:38' UNTIL '2022-01-06 12:00'

Temporal ranges can be specified using functions and parameters

SELECT * FROM mytable SINCE TX @initialTx BEFORE now()