Execute SQL queries in Deephaven

Structured Query Language (SQL) is one of the most popular languages for database management and access. You can use SQL with Deephaven in two ways. Use the deephaven.dbc Python package to run SQL against an external database and read the result into a Deephaven table. Use the experimental deephaven.experimental.sql module to run SQL against Deephaven tables in your session.

This guide shows you how to do both. SQL queries against Deephaven tables work on ticking tables too, and their results update as the source tables change.

Query an external database

deephaven.dbc provides three functions:

Choose the right function

Consider the following factors when you choose among these three functions.

High-level vs low-level

read_sql is a high-level function for executing a query against a SQL database. Pass the SQL query as query. Pass a connection string or an existing turbodbc or ADBC Connection object as conn. It returns a static table that holds the query's result.

odbc.read_cursor and adbc.read_cursor are lower-level functions because they read from a cursor that has already executed a query. You open the cursor and execute the query yourself, then pass the cursor to the function. Use one of these when you need control over the cursor itself, for example to execute a parameterized query.

ODBC vs ADBC

The two lower-level options can both achieve the same results. When choosing one or the other, consider:

  • ODBC (odbc.read_cursor) is a well-known specification that works with relational databases that have an ODBC driver.
  • ADBC (adbc.read_cursor) fetches data in Arrow format from databases that support Arrow natively, such as databases that serve the Arrow Flight SQL protocol. It also works with other databases that have an ADBC driver, such as PostgreSQL and SQLite.

Supported drivers and required packages

read_sql can use one of three supported drivers, which you choose with its driver argument. To use a driver, install the corresponding packages:

If you pass a turbodbc or ADBC Connection object instead of a connection string, read_sql ignores driver.

odbc.read_cursor and adbc.read_cursor need the same packages as the "odbc" and "adbc" drivers, respectively.

For ODBC, you need a turbodbc build with Apache Arrow support, an ODBC driver manager, and your database's ODBC driver.

For ADBC, you always need adbc-driver-manager, along with the driver package for your database. With a connection string, read_sql(driver="adbc") supports only two databases:

  • PostgreSQL, when the connection string is a postgresql:// URI.
  • SQLite, when the connection string is empty.

For other ADBC drivers, such as Flight SQL, pass an ADBC Connection object as conn, or use adbc.read_cursor.

Execute a query

With the necessary packages installed, you can create a table from a SQL query with read_sql, odbc.read_cursor, or adbc.read_cursor. Each example below pulls from a PostgreSQL database.

Tip

It's best practice not to store credentials in plaintext. The following examples read the username, password, host, and port of the PostgreSQL database from environment variables.

read_sql

odbc.read_cursor

Before you run this example, install the PostgreSQL ODBC driver and register it with the ODBC driver manager. The Driver value in the connection string must match the registered driver name (PostgreSQL Unicode in this example). turbodbc.connect takes an ODBC connection string rather than a PostgreSQL URI.

adbc.read_cursor

All three examples read the same rows into crypto_trades, although column types can differ between drivers. PostgreSQL converts unquoted identifiers such as Timestamp to lowercase, so the column names in the result are lowercase:

The crypto_trades table, with lowercase columns timestamp, id, instrument, exchange, price, and size

Query Deephaven tables with SQL

The deephaven.experimental.sql module runs SQL queries against Deephaven tables instead of an external database. Its evaluate function runs a query that refers to tables by their Python variable names in the calling scope and returns a new Deephaven table. If the source tables are ticking, so is the result.

Note

SQL support for Deephaven tables is experimental. The deephaven.experimental.sql API and the set of supported SQL features are subject to change.

Query a ticking table

Let's run SQL against a ticking table. The example below creates a time table that adds a row every second, then counts its rows by the value of X. The result updates each time a new row arrives:

Validate a query without executing it

To parse and validate a query without executing it, pass dry_run=True to evaluate. Instead of a table, evaluate then returns a Java TableSpec object that describes the query plan. It also writes the plan to the server log in Graphviz format. The example below prints that plan to the console with Graphviz.toDot:

Supported SQL features

Table and column names are case-sensitive and must match the Deephaven names exactly, so SELECT x FROM source doesn't find a column named X. Use double quotes to reference a name that isn't a valid SQL identifier.

Deephaven's SQL implementation supports:

  • SELECT statements with column selection and aliasing.
  • SELECT DISTINCT for removing duplicate rows.
  • WHERE clauses for filtering.
  • Inner joins with JOIN or INNER JOIN.
  • GROUP BY with aggregate functions: SUM, AVG, MIN, MAX, STDDEV, VARIANCE, ANY_VALUE, COUNT(*), COUNT(DISTINCT col), and APPROX_COUNT_DISTINCT.
    • DISTINCT works only inside COUNT.
    • Unlike standard SQL, COUNT(DISTINCT col) counts null as a distinct value.
    • APPROX_COUNT_DISTINCT returns the same exact count as COUNT(DISTINCT col).
  • ORDER BY for sorting.
  • LIMIT for restricting the number of rows.
  • UNION and UNION ALL for combining the rows of queries with matching columns.
  • Arithmetic operators (+, -, *, /).
    • Unlike standard SQL, / on two integers returns a floating-point result.
  • Comparison and logical operators (=, <>, <, <=, >, >=, AND, OR, NOT, IS NULL, IS NOT NULL).
    • Unlike standard SQL, = and <> treat null as an ordinary value, so two nulls are equal and X <> 1 also matches rows where X is null. Use IS NULL and IS NOT NULL to test for null values.
  • CAST to numeric types (TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, and DOUBLE).
  • Common math functions such as ABS, SQRT, POWER, ROUND (one-argument form only), FLOOR, and CEIL.

To see how integer division differs from standard SQL, divide an int column by 2. The Half column in the result is a double, so 3 / 2 is 1.5 rather than 1:

Deephaven doesn't support every SQL feature. For example, it doesn't support OFFSET, the remainder operator %, COUNT(col), CAST to any other type (such as DECIMAL or VARCHAR), string functions and operators such as LIKE, or ORDER BY with ASC NULLS LAST or DESC NULLS FIRST. If a query uses one of these, evaluate raises a DHError.

Warning

Deephaven runs outer joins (LEFT, RIGHT, and FULL) as inner joins and does not raise an error, so the result drops unmatched rows.