S-strings
An s-string inserts SQL directly, as an escape hatch when there’s something that PRQL doesn’t yet implement. For example, there’s a version() function in PostgreSQL that returns the PostgreSQL version, so if we want to use that, we use an s-string:
PRQL
1. from my_table
2. select db_version = s"version()"
SQL
1. SELECT
2. version() AS db_version
3. FROM
4. my_table
Embed a column name in an s-string using braces. For example, PRQL’s standard library defines the average function as:
1. let average = column -> s"AVG({column})"
So this compiles using the function:
PRQL
1. from employees
2. aggregate {average salary}
SQL
1. SELECT
2. AVG(salary)
3. FROM
4. employees
Note
Because S-string contents are SQL, double-quotes (") will denote a column name. To avoid that, use single-quotes (') around the SQL string, and adjust the quotes of the S-string. For example, instead of s'CONCAT("hello", "world")' use s"CONCAT('hello', 'world')"
Here’s an example of a more involved use of an s-string:
PRQL
1. from de=dept_emp
2. join s=salaries side:left (s.emp_no == de.emp_no && s"""
3. ({s.from_date}, {s.to_date})
4. OVERLAPS
5. ({de.from_date}, {de.to_date})
6. """)
SQL
1. SELECT
2. de.*,
3. s.*
4. FROM
5. dept_emp AS de
6. LEFT JOIN salaries AS s ON s.emp_no = de.emp_no
7. AND (s.from_date, s.to_date) OVERLAPS (de.from_date, de.to_date)
For those who have used Python, s-strings are similar to Python’s f-strings, but the result is SQL code, rather than a string literal. For example, a Python f-string of f"average({col})" would produce "average(salary)", with quotes; while in PRQL, s"average({col})" produces average(salary), without quotes.
Note that interpolations can only contain plain variable names and not whole expression like Python.
We can also use s-strings to produce a full table:
PRQL
1. from s"SELECT DISTINCT ON first_name, id, age FROM employees ORDER BY age ASC"
2. join s = s"SELECT * FROM salaries" (==id)
SQL
1. WITH table_0 AS (
2. SELECT
3. DISTINCT ON first_name,
4. id,
5. age
6. FROM
7. employees
8. ORDER BY
9. age ASC
10. ),
11. table_1 AS (
12. SELECT
13. *
14. FROM
15. salaries
16. )
17. SELECT
18. table_0.*,
19. table_1.*
20. FROM
21. table_0
22. JOIN table_1 ON table_0.id = table_1.id
Note
S-strings in user code are intended as an escape-hatch for an unimplemented feature. If we often need s-strings to express something, that’s a sign we should implement it in PRQL or PRQL’s stdlib.
Braces
To output braces from an s-string, use double braces:
PRQL
1. from employees
2. derive {
3. has_valid_title = s"regexp_contains(title, '([a-z0-9]*-){{2,}}')"
4. }
SQL
1. SELECT
2. *,
3. regexp_contains(title, '([a-z0-9]*-){2,}') AS has_valid_title
4. FROM
5. employees
Precedence
Variables in s-strings are inserted into the SQL source as-it, which means we may get surprising behavior when the variable is has multiple terms and the s-string isn’t parenthesized.
In this toy example, the salary + benefits / 365 gets precedence wrong:
PRQL
1. from employees
2. derive {
3. gross_salary = salary + benefits,
4. daily_rate = s"{gross_salary} / 365"
5. }
SQL
1. SELECT
2. *,
3. salary + benefits AS gross_salary,
4. salary + benefits / 365 AS daily_rate
5. FROM
6. employees
Instead, the denominator {gross_salary} must be encased in parentheses:
PRQL
1. from employees
2. derive {
3. gross_salary = salary + benefits,
4. daily_rate = s"({gross_salary}) / 365"
5. }
SQL
1. SELECT
2. *,
3. salary + benefits AS gross_salary,
4. (salary + benefits) / 365 AS daily_rate
5. FROM
6. employees
