PostgreSQL Protocol: Querying with SQL
-
Clients that connect to RavenDB over the PostgreSQL protocol can query the database using plain SQL.
The RavenDB server translates the SQL queries to RQL, runs the translated queries on the database, and returns the matching results to the client.
Only queries that retrieve data (SELECTqueries) are supported. -
SQL is one of two query languages that RavenDB accepts over the PostgreSQL protocol: the RavenDB server detects and runs RQL queries as they are, and clients like Power BI can send queries in either of the two languages.
This page covers querying with SQL. -
A limited SQL subset is supported.
Using an unsupported SQL feature will fail the query, with an error message that names the feature and, where possible, suggests a workaround. -
In this article:
A quick example
The following SQL query retrieves up to 20 companies, ordered by name, excluding one company.
SELECT *
FROM "public"."Companies"
WHERE "Name" <> 'Ernst Handel'
ORDER BY "Name"
LIMIT 20
- Since the SQL query selects
*, the results include all the fields of eachCompaniesdocument, plus the two columns RavenDB adds:id, holding the document's ID, andjson, holding document properties that do not fit the regular columns. - A nested object, like a company's
Address, is returned as JSON text in its column. - The SQL identifiers are double-quoted to keep their case.
Learn more in SELECT and FROM.
RavenDB will run the following RQL query on the database:
from 'Companies' where Name != 'Ernst Handel' order by Name limit 0, 20
Supported SQL clauses and parameters
A SQL query addresses the database in SQL terms: each document collection is a table, and the fields of its documents are the table's columns.
SELECT and FROM
-
SELECT takes a list of the table's columns, or
*for all of them.
A column can be aliased usingAS, like"Freight" AS "shipping_cost". -
SELECT DISTINCTretrieves each distinct value once.
e.g.,SELECT DISTINCT "ShipVia" FROM "public"."Orders"will retrieve the three shippers once each. -
FROM takes a table name, plain or qualified with the
publicschema:FROM "Orders"andFROM "public"."Orders"are equivalent. -
To query an index instead of a collection, qualify the index name with the
indexesschema.
An index query returns the documents that the index covers.The following query retrieves the
Ordersdocuments through the sample database'sOrders/Totalsindex.- SQL
- RQL
The SQL query you send:
SELECT *FROM "indexes"."Orders/Totals"The RQL query RavenDB will run on the database:
from index 'Orders/Totals' -
Double-quote identifiers to keep the exact casing of your collection and field names.
An unquoted identifier folds to lowercase (standard PostgreSQL behavior): an unquotedFROM Ordersis read asFROM "orders".
WHERE
-
WHERE filters the retrieved rows using a condition.
-
A condition can be:
Condition element Example The comparison operators =,<>,<,<=,>,>="Freight" > 100BETWEEN"Freight" BETWEEN 10 AND 20INandNOT IN"ShipVia" IN ('shippers/1-A', 'shippers/2-A')IS NULLandIS NOT NULL"ShippedAt" IS NULLAND,OR,NOT, and parenthesesNOT ("Freight" > 100 OR "ShipVia" = 'shippers/1-A') -
A column reference reaches a nested field by quoting each segment of the path.
e.g.,"ShipTo"."Country"The following query retrieves the orders shipped to France with a freight charge above 100.
- SQL
- RQL
The SQL query you send:
SELECT *FROM "public"."Orders"WHERE "ShipTo"."Country" = 'France' AND "Freight" > 100The RQL query RavenDB will run on the database:
from 'Orders' where ShipTo.Country = 'France' and Freight > 100
ORDER BY
- ORDER BY sorts the results by one or more of the table's columns, in ascending
(
ASC, the default) or descending (DESC) order. - A sort key must be a plain table column: an expression or a function as a sort key,
like
ORDER BY upper("Name"), will fail the query. - Number fields are sorted numerically: RavenDB selects each sort key's ordering type,
numeric or alphabetic, by sampling the value of the column's underlying document field
in one of the collection's documents.
If the sampled document does not hold the field, the results will be sorted alphabetically.
The following query retrieves the orders, sorted from the most recent to the oldest.
- SQL
- RQL
The SQL query you send:
SELECT *
FROM "public"."Orders"
ORDER BY "OrderedAt" DESC
The RQL query RavenDB will run on the database:
from 'Orders' order by OrderedAt desc
LIMIT and OFFSET
- LIMIT caps the number of retrieved rows, and OFFSET skips a given number of
rows before the retrieval starts.
Use the two clauses together to page through a large result set. - Both clauses take an integer literal.
Any other value, like a parameter or an expression, will fail the query.
The following query retrieves the second page of ten companies, ordered by name.
- SQL
- RQL
The SQL query you send:
SELECT *
FROM "public"."Companies"
WHERE "Name" <> 'Ernst Handel'
ORDER BY "Name"
LIMIT 10 OFFSET 10
The RQL query RavenDB will run on the database:
from 'Companies' where Name != 'Ernst Handel' order by Name limit 10, 10
GROUP BY
- GROUP BY groups the retrieved rows by one or more of the table's columns, and
aggregates each group using the
sumandcountfunctions. - A grouped query can order its results by a projected column or aggregate.
- A
WHEREcondition in a grouped query can filter only by the grouped columns. - Aggregate functions other than
sumandcount, and theHAVINGclause, are not supported.
Learn more in Unsupported SQL features.
The following query counts each shipper's orders.
- SQL
- RQL
The SQL query you send:
SELECT "ShipVia", COUNT(*)
FROM "public"."Orders"
GROUP BY "ShipVia"
The RQL query RavenDB will run on the database:
from 'Orders' group by ShipVia select ShipVia, count()
Query parameters
-
A
WHEREcondition can use numbered placeholders like$1and$2instead of literal values, and the client can provide the values separately (using PostgreSQL's extended query protocol):- The client sends the query text to RavenDB once, under a name of its choice, and RavenDB registers the parsed query under this name.
- On each subsequent run, the client sends only the query's name and the values, as an
ordered list that fills the placeholders by position: the first value fills
$1, the second fills$2, and so on. - RavenDB runs the named query with the received values, and returns the results to the client.
- When the connection between the client and RavenDB is terminated, both discard the query.
e.g., a sales dashboard that refreshes every morning can define its query once, and with each subsequent morning run send only a fresh pair of dates and receive the day's orders in return.
-
Parameters serve clients that send queries programmatically, like applications using Npgsql.
Some of the queries that Power BI generates, like incremental refresh date windows, use parameters.
Power BI's SQL statement box, on the other hand, takes plain text: to set values in the box, write the values into the query text.
In the following example, the client sends the query's text to RavenDB once.
On each subsequent run, the client sends to RavenDB only two values indicating
a time window, and RavenDB replies with the orders placed during this time window.
- SQL
- RQL
The SQL query you send:
SELECT *
FROM "Orders"
WHERE "OrderedAt" >= $1 AND "OrderedAt" < $2
The RQL query RavenDB will run on the database:
from 'Orders' where OrderedAt >= $1 and OrderedAt < $2
Unsupported SQL features
The following SQL features are not supported.
Using them will fail the query, with an error message that names the feature and, where
possible, suggests a workaround.
| Feature | Workaround |
|---|---|
JOIN | A relational JOIN that combines columns from two tables into one flat row is not supported, because RavenDB relates documents through document IDs rather than relational joins. To combine related documents, query with RQL, relating them using load or include. e.g., from "Orders" as o load o.Company as c select c.Name, o.Freight retrieves each order's company by the ID stored in the order's Company field. |
avg | Retrieve sum and count, and divide the sum by the count on the client side. e.g., to average each company's freight charge, retrieve SELECT "Company", SUM("Freight"), COUNT(*) FROM "Orders" GROUP BY "Company", and divide each company's sum by its count. |
min, max | Sort by the column and take the first row. e.g., ORDER BY "Freight" ASC LIMIT 1 retrieves the row with the lowest freight charge, and ORDER BY "Freight" DESC LIMIT 1 the row with the highest. |
An aggregate without GROUP BY, like SELECT COUNT(*) FROM "Orders" | Group the query, and combine the groups' results on the client side. e.g., retrieve SELECT "ShipVia", COUNT(*) FROM "Orders" GROUP BY "ShipVia", and sum the counts. |
HAVING | Retrieve the grouped rows without HAVING, and filter the groups on the client side. e.g., to keep only companies with a freight-charge total above 100, retrieve SELECT "Company", SUM("Freight") FROM "Orders" GROUP BY "Company", and drop the lower groups after retrieval. |
INTERSECT, EXCEPT | Run the two SELECT queries separately, and combine the two result sets on the client side: keep the rows that appear in both (INTERSECT), or only the rows of the first set that are absent from the second (EXCEPT). |
Any other unrecognized SQL will fail the query with a generic Unhandled query error.