Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

SQL リファレンス

最終更新 Markdown で表示Agent セットアップ

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 つ以上のクエリは、集合演算UNIONUNION ALLINTERSECTEXCEPT)で結合できます。


スキーマ探索コマンド

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
  • すべての列: *
  • 修飾ワイルドカード: 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 10

DISTINCT

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 DESC

別テーブルと結合する CTE

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

2 つの 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 のクエリは、1 つ以上のテーブルを参照できます。テーブルは namespace_name.table_name で指定します。複数テーブルは JOIN またはカンマ区切り構文で結合できます。詳細は 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.column

3 テーブル以上の結合

1 つのクエリで 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 句のサブクエリ(派生テーブル)

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

IN / 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 10

EXISTS / 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

スカラーサブクエリ

単一の値を返すサブクエリは、SELECTWHEREHAVING で使えます。

-- 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 NULL
  • column_name IS NOT NULL

真偽値チェック

  • IS TRUEIS FALSEIS NOT TRUEIS NOT FALSE
  • IS UNKNOWNIS NOT UNKNOWN

範囲

  • column_name BETWEEN value1 AND value2
  • column_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'

論理演算子

  • AND
  • OR
  • NOT

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, category

GROUPING SETS、ROLLUP、CUBE

これらの拡張は、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), ())

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

ウィンドウ関数

ウィンドウ関数は、現在行に関連する行集合に対して値を計算し、1 行にまとめません。ウィンドウは、省略可能な PARTITION BYORDER BY、フレーム指定を含む OVER (...) 句でインライン定義します。

構文

function(args) OVER (
    [PARTITION BY expression [, ...]]
    [ORDER BY expression [ASC | DESC] [, ...]]
    [frame_specification]
)

対応関数

カテゴリ 関数
順位 ROW_NUMBERRANKDENSE_RANKPERCENT_RANKCUME_DISTNTILE
オフセット LAGLEADFIRST_VALUELAST_VALUENTH_VALUE
集計 SUMAVGCOUNTMINMAX、および 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_data

QUALIFY

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

集合演算

集合演算は、2 つ以上の SELECT 文の結果を結合します。

構文

SELECT ... FROM table1
UNION | UNION ALL | INTERSECT | EXCEPT
SELECT ... FROM table2

対応している演算

演算 説明
UNION 両方のクエリの全行を返し、重複を除きます
UNION ALL 両方のクエリの全行を返し、重複も含めます
INTERSECT 両方のクエリ結果に現れる行だけを返します
EXCEPT 最初のクエリにあり、2 番目にはない行を返します

UNION

-- 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

INTERSECT

-- 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;

式は SELECTWHEREGROUP BYHAVINGORDER 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 5

EXTRACT

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 整数 142-100
float 小数 1.53.14-2.70.0
string 文字列 'hello''GET''2024-01-01'
boolean 真偽値 truefalse
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)

演算子の優先順位

  1. 比較演算子: =!=<<=>>=LIKEBETWEENIS NULLIS NOT NULL
  2. AND(優先度が高い)
  3. 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

役に立ちましたか?