R2 SQL は、R2 Data Catalog に保存した Apache Iceberg ↗ テーブルを照会する、Cloudflare のサーバーレス分散分析クエリエンジンです。このページでは対応する 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]2 つ以上のクエリは、集合演算(UNION、UNION ALL、INTERSECT、EXCEPT)で結合できます。
利用可能な名前空間をすべて一覧表示します。
SHOW DATABASES;SHOW DATABASES の別名です。利用可能な名前空間をすべて一覧表示します。
SHOW NAMESPACES;特定の名前空間内のテーブルをすべて一覧表示します。
SHOW TABLES IN namespace_name;テーブルの構造を説明し、列名とデータ型を表示します。
DESCRIBE namespace_name.table_name;SELECT [DISTINCT] column_specification [, column_specification, ...]- 列名:
column_name - すべての列:
* - 修飾ワイルドカード:
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 10SELECT 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 は、WITH で名前付きの一時結果セットを定義し、メインクエリから参照できます。CTE は別のテーブルを参照でき、JOIN を含められます。メインクエリでは、CTE をほかの CTE や通常のテーブルと結合することもできます。
WITH cte_name AS (
SELECT ...
FROM namespace_name.table_name
[WHERE ...]
)
SELECT ... FROM cte_nameCTE は、先に定義した 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 DESCWITH 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 20WITH 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 20SELECT * FROM namespace_name.table_nameR2 SQL のクエリは、1 つ以上のテーブルを参照できます。テーブルは namespace_name.table_name で指定します。複数テーブルは JOIN またはカンマ区切り構文で結合できます。詳細は JOIN 句 を参照してください。
R2 SQL は、1 つのクエリで複数の 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.column1 つのクエリで 3 つ以上のテーブルを結合できます。
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フィルタを入れてください。 - 大きなファクトテーブル同士を直接クロス結合せず、共有ディメンションテーブル経由で結合してください。
- 結果サイズを抑えるため
LIMITを使ってください。
R2 SQL は、クエリ内の複数の位置でサブクエリに対応しています。
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 20値がサブクエリの結果に存在するかどうかで行を絞り込みます。
-- 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 10相関条件に一致する行があるかを調べます。
-- 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 20SELECT * FROM namespace_name.table_name WHERE condition [AND | OR condition ...]=、!=、<>、<、>、<=、>=
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%'SELECT column_list, aggregation_function(column)
FROM namespace_name.table_name
[WHERE conditions]
GROUP BY column_listSELECT 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, categoryこれらの拡張は、1 つのクエリで小計や総計を含む複数のグループ化を計算します。
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), ())SELECT column_list, aggregation_function(column) AS alias
FROM namespace_name.table_name
GROUP BY column_list
HAVING aggregation_function(column) comparison_operator valueSELECT 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) > 1000000ORDER 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 ASCLIMIT number- 型: 整数のみ
- デフォルト: 500
SELECT * FROM my_namespace.sales_data LIMIT 100ウィンドウ関数は、現在行に関連する行集合に対して値を計算し、1 行にまとめません。ウィンドウは、省略可能な PARTITION BY、ORDER BY、フレーム指定を含む OVER (...) 句でインライン定義します。
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 は、ウィンドウ関数の結果で行を絞り込みます。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集合演算は、2 つ以上の SELECT 文の結果を結合します。
SELECT ... FROM table1
UNION | UNION ALL | INTERSECT | EXCEPT
SELECT ... FROM table2| 演算 | 説明 |
|---|---|
UNION |
両方のクエリの全行を返し、重複を除きます |
UNION ALL |
両方のクエリの全行を返し、重複も含めます |
INTERSECT |
両方のクエリ結果に現れる行だけを返します |
EXCEPT |
最初のクエリにあり、2 番目にはない行を返します |
-- 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.8-- 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'-- 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 SELECT department, COUNT(*) AS dept_count
FROM my_namespace.sales_data
WHERE total_amount IS NOT NULL
GROUP BY department;プログラム解析向けに、実行計画を構造化 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 5SELECT customer_id || ' - ' || region AS label
FROM my_namespace.sales_data
LIMIT 5検索形式:
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 5SELECT 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 100SELECT customer_id, timestamp, status, total_amount
FROM my_namespace.sales_data
WHERE status >= 400 AND total_amount > 5000
ORDER BY total_amount DESC
LIMIT 50SELECT 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 20SELECT 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