← R2 SQL / r2-sql
справочник по SQL
R2 SQL представляет собой бессерверный распределённый аналитический движок запросов Cloudflare, предназначенный для запросов к Apache Iceberg ↗ таблиц, хранящихся в R2 Data Catalog. На этой странице описан поддерживаемый синтаксис SQL.
Синтаксис запроса
SELECT [DISTINCT] column_list | expression | aggregate_function | window_function
FROM namespace_name.table_name
[JOIN namespace_name.table_name ON condition]
[WHERE conditions]
[GROUP BY column_list]
[HAVING conditions]
[QUALIFY window_condition]
[ORDER BY expression [ASC | DESC]]
[LIMIT number]Два или более запросов можно объединить с помощью операции над множествами (UNION, UNION ALL, INTERSECT, EXCEPT).
Команды обнаружения схемы
SHOW DATABASES
Выводит список всех доступных пространств имён.
SHOW DATABASES;SHOW NAMESPACES
Алиас для SHOW DATABASES. Выводит список всех доступных пространств имён.
SHOW NAMESPACES;SHOW TABLES
Выводит список всех таблиц в указанном пространстве имён.
SHOW TABLES IN namespace_name;DESCRIBE
Описывает структуру таблицы: имена столбцов и типы данных.
DESCRIBE namespace_name.table_name;Секция SELECT
Синтаксис
SELECT [DISTINCT] column_specification [, column_specification, ...]Спецификация столбца
- Имя столбца:
column_name - Все столбцы:
* - Qualified wildcard:
table_name.* - Псевдоним столбца:
column_name AS alias - Выражения: арифметические операции, вызовы функций, выражения CASE и приведения типов
Примеры
SELECT * FROM my_namespace.sales_data LIMIT 10
SELECT customer_id, region, total_amount FROM my_namespace.sales_data LIMIT 10
SELECT region, total_amount * 1.1 AS total_with_tax FROM my_namespace.sales_data LIMIT 10DISTINCT
SELECT DISTINCT возвращает уникальные строки. DISTINCT ON (...) возвращает первую строку для каждой комбинации перечисленных выражений, используя ORDER BY предложение, чтобы определить, какая строка сохраняется.
-- Unique combinations
SELECT DISTINCT region, department FROM my_namespace.sales_data
-- First row per region by amount
SELECT DISTINCT ON (region) region, customer_id, total_amount
FROM my_namespace.sales_data
ORDER BY region, total_amount DESCДля подсчёта уникальных значений в больших наборах данных, approx_distinct() является более быстрой альтернативой.
Обобщённые табличные выражения (CTE)
CTE позволяют определять именованные временные наборы результатов с помощью WITH на которое можно ссылаться в основном запросе. CTE могут ссылаться на разные таблицы и включать JOIN. CTE также можно объединить с другими CTE или обычными таблицами в основном запросе.
Синтаксис
WITH cte_name AS (
SELECT ...
FROM namespace_name.table_name
[WHERE ...]
)
SELECT ... FROM cte_nameЦепочки CTE
CTE может ссылаться на ранее определённый CTE.
WITH filtered AS (
SELECT customer_id, department, total_amount
FROM my_namespace.sales_data
WHERE total_amount > 0
),
summary AS (
SELECT department,
COUNT(*) AS order_count,
round(AVG(total_amount), 2) AS avg_amount
FROM filtered
GROUP BY department
)
SELECT *
FROM summary
WHERE order_count > 100
ORDER BY avg_amount DESCCTE, соединённый с другой таблицей
WITH enterprise_zones AS (
SELECT zone_id, domain, plan
FROM my_namespace.zones
WHERE plan = 'enterprise'
)
SELECT ez.domain, f.action, COUNT(*) AS cnt
FROM enterprise_zones ez
INNER JOIN my_namespace.firewall_events f ON ez.zone_id = f.zone_id
GROUP BY ez.domain, f.action
ORDER BY cnt DESC
LIMIT 20Два соединённых CTE
WITH top_zones AS (
SELECT zone_id, COUNT(*) AS req_count
FROM my_namespace.http_requests
GROUP BY zone_id
ORDER BY req_count DESC
LIMIT 50
),
zone_threats AS (
SELECT zone_id, COUNT(*) AS threat_count
FROM my_namespace.firewall_events
WHERE risk_score > 0.5
GROUP BY zone_id
)
SELECT tz.zone_id, tz.req_count, COALESCE(zt.threat_count, 0) AS threat_count
FROM top_zones tz
LEFT JOIN zone_threats zt ON tz.zone_id = zt.zone_id
ORDER BY tz.req_count DESC
LIMIT 20Секция FROM
Синтаксис
SELECT * FROM namespace_name.table_nameЗапросы R2 SQL могут ссылаться на одну или несколько таблиц. Таблицы указываются в виде namespace_name.table_name. Несколько таблиц можно объединить с помощью JOIN или синтаксиса через запятую. См. Предложение JOIN раздел для получения подробной информации.
Предложение JOIN
R2 SQL поддерживает объединение нескольких таблиц Iceberg в одном запросе. Все типы соединений используют стандартный синтаксис SQL.
Поддерживаемые типы соединений
| Тип соединения | Синтаксис | Описание |
|---|---|---|
| Внутреннее соединение | INNER JOIN ... ON |
Возвращает строки, совпадающие в обеих таблицах |
| Левое внешнее соединение | LEFT JOIN ... ON |
Возвращает все строки из левой таблицы, подставляя NULL для несовпадающих строк правой |
| Правое внешнее соединение | RIGHT JOIN ... ON |
Возвращает все строки из правой таблицы, подставляя NULL для несовпадающих строк левой |
| Полное внешнее соединение | FULL OUTER JOIN ... ON |
Возвращает все строки из обеих таблиц, подставляя NULL при отсутствии совпадений |
| Перекрёстное соединение | CROSS JOIN |
Декартово произведение двух таблиц |
| Неявное соединение | FROM t1, t2 WHERE t1.id = t2.id |
Таблицы, перечисленные через запятую, с условием соединения в WHERE |
Синтаксис
-- Explicit JOIN
SELECT columns
FROM namespace.table1 alias1
[INNER | LEFT | RIGHT | FULL OUTER | CROSS] JOIN namespace.table2 alias2
ON alias1.column = alias2.column
[WHERE conditions]
-- Implicit join
SELECT columns
FROM namespace.table1 alias1, namespace.table2 alias2
WHERE alias1.column = alias2.columnМноготабличные соединения
В одном запросе можно объединить три и более таблиц:
SELECT z.domain, h.method, f.action, COUNT(*) AS cnt
FROM my_namespace.zones z
INNER JOIN my_namespace.http_requests h ON z.zone_id = h.zone_id
INNER JOIN my_namespace.firewall_events f ON z.zone_id = f.zone_id
WHERE h.status_code >= 400
GROUP BY z.domain, h.method, f.action
ORDER BY cnt DESC
LIMIT 20Самосоединения
Таблицу можно соединить саму с собой, используя разные алиасы:
SELECT f1.source_ip, f1.zone_id AS zone1, f2.zone_id AS zone2
FROM my_namespace.firewall_events f1
INNER JOIN my_namespace.firewall_events f2
ON f1.source_ip = f2.source_ip
AND f1.zone_id < f2.zone_id
WHERE f1.action = 'block'
LIMIT 20Условия соединения
- Условия соединения используют
ONпредложение с условием равенства (=) или предикатов на основе выражений. - Функции поддерживаются в условиях соединения (например,
ON LOWER(a.col) = LOWER(b.col)). - Несколько условий можно объединить с помощью
AND.
Рекомендации по использованию соединений
- Подключите
WHEREфильтры, чтобы уменьшить размер промежуточных результатов, особенно при многосторонних объединениях. - Соединяйте большие таблицы фактов через общую таблицу измерений вместо прямого CROSS JOIN двух больших таблиц.
- Используйте
LIMITчтобы ограничить размер результата.
Подзапросы
R2 SQL поддерживает подзапросы в нескольких позициях внутри запроса.
Подзапросы в FROM (производные таблицы)
Подзапрос в FROM предложение создает производную таблицу, на которую можно ссылаться во внешнем запросе:
SELECT sub.domain, sub.total_requests
FROM (
SELECT z.domain, COUNT(*) AS total_requests
FROM my_namespace.zones z
INNER JOIN my_namespace.http_requests h ON z.zone_id = h.zone_id
GROUP BY z.domain
) sub
WHERE sub.total_requests > 1000
ORDER BY sub.total_requests DESC
LIMIT 20Производные таблицы можно соединять с другими производными таблицами или обычными таблицами:
SELECT req.domain, req.total_reqs, fw.total_events
FROM (
SELECT zone_id, domain, COUNT(*) AS total_reqs
FROM my_namespace.zones z
INNER JOIN my_namespace.http_requests h ON z.zone_id = h.zone_id
GROUP BY zone_id, domain
) req
INNER JOIN (
SELECT zone_id, COUNT(*) AS total_events
FROM my_namespace.firewall_events
GROUP BY zone_id
) fw ON req.zone_id = fw.zone_id
ORDER BY fw.total_events DESC
LIMIT 20IN / NOT IN подзапросы
Фильтрация строк по наличию значения в результате подзапроса:
-- Find requests from enterprise zones
SELECT method, status_code, COUNT(*) AS cnt
FROM my_namespace.http_requests
WHERE zone_id IN (
SELECT zone_id FROM my_namespace.zones WHERE plan = 'enterprise'
)
GROUP BY method, status_code
ORDER BY cnt DESC
LIMIT 20-- NOT IN example
SELECT zone_id, COUNT(*) AS cnt
FROM my_namespace.http_requests
WHERE zone_id NOT IN (
SELECT zone_id FROM my_namespace.firewall_events WHERE action = 'block'
)
GROUP BY zone_id
LIMIT 10EXISTS / NOT EXISTS подзапросы
Проверка наличия строк, соответствующих коррелированному условию:
-- Find zones with blocked firewall events (semi-join)
SELECT z.domain, z.plan
FROM my_namespace.zones z
WHERE EXISTS (
SELECT 1 FROM my_namespace.firewall_events f
WHERE f.zone_id = z.zone_id AND f.action = 'block'
)
ORDER BY z.domain
LIMIT 20-- Find zones with NO firewall events (anti-join)
SELECT z.domain, z.plan
FROM my_namespace.zones z
WHERE NOT EXISTS (
SELECT 1 FROM my_namespace.firewall_events f
WHERE f.zone_id = z.zone_id
)
ORDER BY z.domain
LIMIT 20Скалярные подзапросы
Подзапрос, возвращающий одно значение, можно использовать в SELECT, WHERE, или HAVING:
-- In SELECT (constant value per row)
SELECT z.domain, z.plan,
(SELECT COUNT(*) FROM my_namespace.zones) AS total_zones
FROM my_namespace.zones z
WHERE z.plan = 'enterprise'
LIMIT 10-- In WHERE (comparison)
SELECT z.domain, z.plan, z.requests_30d
FROM my_namespace.zones z
WHERE z.requests_30d > (
SELECT AVG(requests_30d) FROM my_namespace.zones
)
ORDER BY z.requests_30d DESC
LIMIT 20Секция WHERE
Синтаксис
SELECT * FROM namespace_name.table_name WHERE condition [AND | OR condition ...]Условия
Операторы сравнения
=, !=, <>, <, >, <=, >=
Проверки на NULL
column_name IS NULLcolumn_name IS NOT NULL
Логические проверки
IS TRUE,IS FALSE,IS NOT TRUE,IS NOT FALSEIS UNKNOWN,IS NOT UNKNOWN
Диапазон
column_name BETWEEN value1 AND value2column_name NOT BETWEEN value1 AND value2
Проверка вхождения в список
column_name IN ('value1', 'value2')column_name NOT IN ('value1', 'value2')
Сопоставление с шаблоном
column_name LIKE 'pattern'column_name NOT LIKE 'pattern'column_name ILIKE 'pattern'(без учета регистра)column_name NOT ILIKE 'pattern'column_name SIMILAR TO 'regex_pattern'
Логические операторы
ANDORNOT
Примеры
SELECT * FROM my_namespace.sales_data
WHERE timestamp BETWEEN '2025-09-24T01:00:00Z' AND '2025-09-25T01:00:00Z'
SELECT * FROM my_namespace.sales_data
WHERE status = 200 AND response_time > 1000
SELECT * FROM my_namespace.sales_data
WHERE (region = 'North' OR region = 'South')
AND total_amount IS NOT NULL
SELECT * FROM my_namespace.sales_data
WHERE department ILIKE '%eng%'Секция GROUP BY
Синтаксис
SELECT column_list, aggregation_function(column)
FROM namespace_name.table_name
[WHERE conditions]
GROUP BY column_listПримеры
SELECT department, COUNT(*) AS dept_count
FROM my_namespace.sales_data
GROUP BY department
SELECT department, category, SUM(total_amount) AS total
FROM my_namespace.sales_data
GROUP BY department, categoryGROUPING SETS, ROLLUP и CUBE
Эти расширения вычисляют несколько группировок, включая промежуточные и общие итоги, в рамках одного запроса.
GROUPING SETS: вычисляет только указанные вами группировки.()выдает общий итог.ROLLUP: Вычисляет иерархические промежуточные итоги слева направо.ROLLUP(a, b)группирует по(a, b),(a), а также().CUBE: вычисляет все комбинации перечисленных столбцов.CUBE(a, b)группирует по(a, b),(a),(b), а также().
-- Subtotals per department plus a grand total
SELECT department, SUM(total_amount) AS total
FROM my_namespace.sales_data
GROUP BY ROLLUP(department)
-- Every combination of department and category
SELECT department, category, SUM(total_amount) AS total
FROM my_namespace.sales_data
GROUP BY CUBE(department, category)
-- Explicit groupings
SELECT department, category, SUM(total_amount) AS total
FROM my_namespace.sales_data
GROUP BY GROUPING SETS ((department, category), (department), ())Предложение HAVING
Синтаксис
SELECT column_list, aggregation_function(column) AS alias
FROM namespace_name.table_name
GROUP BY column_list
HAVING aggregation_function(column) comparison_operator valueПримеры
SELECT department, COUNT(*) AS dept_count
FROM my_namespace.sales_data
GROUP BY department
HAVING COUNT(*) > 1000
SELECT region, SUM(total_amount) AS total
FROM my_namespace.sales_data
GROUP BY region
HAVING SUM(total_amount) > 1000000Секция ORDER BY
Синтаксис
ORDER BY expression [ASC | DESC] [, expression [ASC | DESC], ...]- ASC: по возрастанию (по умолчанию)
- DESC: По убыванию
- Поддерживается сортировка по нескольким столбцам
Примеры
SELECT customer_id, total_amount
FROM my_namespace.sales_data
WHERE total_amount IS NOT NULL
ORDER BY total_amount DESC
LIMIT 50
SELECT department, COUNT(*) AS dept_count
FROM my_namespace.sales_data
GROUP BY department
ORDER BY dept_count DESC, department ASCУсловие LIMIT
Синтаксис
LIMIT number- Тип: Только целые числа
- По умолчанию: 500
Примеры
SELECT * FROM my_namespace.sales_data LIMIT 100Оконные функции
Оконные функции вычисляют значение по набору строк, связанных с текущей строкой, не сворачивая их в одну итоговую строку. Окно задаётся непосредственно с помощью OVER (...) предложение, содержащее необязательный PARTITION BY, ORDER BY, и спецификации фрейма.
Синтаксис
function(args) OVER (
[PARTITION BY expression [, ...]]
[ORDER BY expression [ASC | DESC] [, ...]]
[frame_specification]
)Поддерживаемые функции
| Категория | Функции |
|---|---|
| Рейтинг | ROW_NUMBER, RANK, DENSE_RANK, PERCENT_RANK, CUME_DIST, NTILE |
| Смещение | LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE |
| Агрегация | SUM, AVG, COUNT, MIN, MAX, и другие агрегатные функции, используемые с OVER |
Примеры
-- Rank rows within each partition
SELECT customer_id, region,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY total_amount DESC) AS rank_in_region,
LAG(total_amount) OVER (PARTITION BY region ORDER BY total_amount DESC) AS prev_amount
FROM my_namespace.sales_data
-- Running total with an explicit frame
SELECT customer_id, total_amount,
SUM(total_amount) OVER (ORDER BY total_amount ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS running_total
FROM my_namespace.sales_dataQUALIFY
QUALIFY фильтрует строки на основе результата оконной функции, аналогично тому, как HAVING фильтрует сгруппированные строки.
-- Keep only the top 3 customers by amount in each region
SELECT customer_id, region, total_amount
FROM my_namespace.sales_data
QUALIFY ROW_NUMBER() OVER (PARTITION BY region ORDER BY total_amount DESC) <= 3Операции над множествами
Операции над множествами объединяют результаты двух или более SELECT инструкции.
Синтаксис
SELECT ... FROM table1
UNION | UNION ALL | INTERSECT | EXCEPT
SELECT ... FROM table2Поддерживаемые операции
| Операция | Описание |
|---|---|
UNION |
Возвращает все строки из обоих запросов, удаляя дубликаты |
UNION ALL |
Возвращает все строки из обоих запросов, включая дубликаты |
INTERSECT |
Возвращает только строки, присутствующие в результатах обоих запросов |
EXCEPT |
Возвращает строки из первого запроса, отсутствующие во втором |
Примеры
Объединение
-- Find zones that had either firewall blocks OR high-risk requests
SELECT zone_id FROM my_namespace.firewall_events WHERE action = 'block'
UNION
SELECT zone_id FROM my_namespace.http_requests WHERE risk_score > 0.8Intersect
-- Find zones with both firewall blocks AND entries in the zones table
SELECT zone_id FROM my_namespace.firewall_events WHERE action = 'block'
INTERSECT
SELECT zone_id FROM my_namespace.zones WHERE plan = 'enterprise'Except
-- Find enterprise zones that have no firewall events
SELECT zone_id FROM my_namespace.zones WHERE plan = 'enterprise'
EXCEPT
SELECT zone_id FROM my_namespace.firewall_eventsТребования
- Все запросы в операции над множествами должны возвращать одинаковое количество столбцов.
- Соответствующие столбцы должны иметь совместимые типы данных.
- Имена столбцов в результате берутся из первого запроса.
EXPLAIN
Возвращает план выполнения запроса без его фактического выполнения.
EXPLAIN SELECT department, COUNT(*) AS dept_count
FROM my_namespace.sales_data
WHERE total_amount IS NOT NULL
GROUP BY department;EXPLAIN FORMAT JSON
Возвращает план выполнения в виде структурированного JSON для программного анализа.
EXPLAIN FORMAT JSON SELECT * FROM my_namespace.sales_data LIMIT 10;Выражения
Выражения можно использовать в SELECT, WHERE, GROUP BY, HAVING, а также ORDER BY предложения.
Литералы
SELECT 42 AS int_val, 3.14 AS float_val, 'hello' AS str_val, TRUE AS bool_val, NULL AS null_val
FROM my_namespace.sales_data LIMIT 1Арифметические операторы
+, -, *, /, %
SELECT customer_id, total_amount * 1.1 AS total_with_tax, total_amount % 10 AS remainder
FROM my_namespace.sales_data
WHERE total_amount IS NOT NULL
LIMIT 5Конкатенация строк
SELECT customer_id || ' - ' || region AS label
FROM my_namespace.sales_data
LIMIT 5Выражения CASE
Форма с условием:
SELECT customer_id,
CASE
WHEN total_amount > 1000 THEN 'high'
WHEN total_amount > 100 THEN 'medium'
ELSE 'low'
END AS tier
FROM my_namespace.sales_data
LIMIT 10Простая форма:
SELECT customer_id,
CASE region
WHEN 'North' THEN 'N'
WHEN 'South' THEN 'S'
ELSE 'Other'
END AS region_code
FROM my_namespace.sales_data
LIMIT 10Приведение типов
-- CAST
SELECT CAST(total_amount AS INT) AS amount_int FROM my_namespace.sales_data LIMIT 5
-- TRY_CAST (returns NULL on failure instead of error)
SELECT TRY_CAST(customer_id AS INT) AS id_int FROM my_namespace.sales_data LIMIT 5
-- Shorthand (::)
SELECT total_amount::INT AS amount_int FROM my_namespace.sales_data LIMIT 5EXTRACT
SELECT EXTRACT(YEAR FROM timestamp) AS yr,
EXTRACT(MONTH FROM timestamp) AS mo,
EXTRACT(DAY FROM timestamp) AS dy
FROM my_namespace.sales_data
LIMIT 1Справочник по типам данных
| Тип | Описание | Примеры значений |
|---|---|---|
integer |
Целые числа | 1, 42, -10, 0 |
float |
Десятичные числа | 1.5, 3.14, -2.7, 0.0 |
string |
Текстовые значения | 'hello', 'GET', '2024-01-01' |
boolean |
Логические значения | true, false |
timestamp |
RFC3339 | '2025-09-24T01:00:00Z' |
date |
Значения дат | '2025-09-24' |
struct |
Именованные поля | struct_col['field_name'] |
array |
Упорядоченный список | array_col[1] (индексация с 1) |
map |
Пары ключ-значение | map_keys(map_col) |
Приоритет операторов
- Операторы сравнения:
=,!=,<,<=,>,>=,LIKE,BETWEEN,IS NULL,IS NOT NULL - AND (более высокий приоритет)
- OR (более низкий приоритет)
Используйте скобки, чтобы переопределить порядок вычислений по умолчанию:
SELECT * FROM my_namespace.sales_data WHERE (status = 404 OR status = 500) AND region = 'North'Полные примеры запросов
Базовый запрос
SELECT *
FROM my_namespace.sales_data
WHERE timestamp BETWEEN '2025-09-24T01:00:00Z' AND '2025-09-25T01:00:00Z'
LIMIT 100Отфильтрованный запрос с сортировкой
SELECT customer_id, timestamp, status, total_amount
FROM my_namespace.sales_data
WHERE status >= 400 AND total_amount > 5000
ORDER BY total_amount DESC
LIMIT 50Агрегация с HAVING
SELECT region, COUNT(*) AS region_count, AVG(total_amount) AS avg_amount
FROM my_namespace.sales_data
WHERE status = 'completed'
GROUP BY region
HAVING COUNT(*) > 1000
ORDER BY avg_amount DESC
LIMIT 20Условная категоризация
SELECT customer_id,
CASE
WHEN total_amount >= 1000 THEN 'Premium'
WHEN total_amount >= 100 THEN 'Standard'
ELSE 'Basic'
END AS tier,
total_amount
FROM my_namespace.sales_data
WHERE total_amount IS NOT NULL
ORDER BY total_amount DESC
LIMIT 20