---
title: "ClickHouse 26.8 のパイプライン化された SQL"
date: "2026-09-18T07:00:55.731Z"
author: "Mark Needham"
category: "Engineering"
excerpt: "ClickHouse 26.8 のパイプライン化された SQL 構文によって、複数ステージのクエリを可読性の高い一連の変換処理として構築する方法を解説します。"
---

# ClickHouse 26.8 のパイプライン化された SQL

SQL クエリは、私たちが頭の中で考える順番通りに書かれるとは限りません。通常はテーブルを選択し、行をフィルタリングし、集約し、最後に結果をソートするという順序で考えますが、従来の SQL ではクエリが返すカラムの定義から書き始めます。

ClickHouse 22.12 以降、FROM 句を SELECT の前に配置できるようになりました。ClickHouse 26.8 では、クエリを一連の変換処理として記述できる新しい `|>` 演算子を用いたパイプライン SQL(pipelined SQL)が導入され、このアプローチがさらに進化しました。

本記事では、[英国の不動産価格データセット](https://clickhouse.com/docs/ja/get-started/sample-datasets/uk-price-paid)を使って、従来のクエリ、`FROM` 先頭のクエリ、そしてパイプラインクエリを比較します。さらに、パイプラインを活用して、通常であればネストしたサブクエリや CTE が必要になるようなクエリを組み立てていきます。

<iframe width="768" height="432" src="https://www.youtube.com/embed/KcwVSzE1TtA?si=GbfIob7hMSecDuS-" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 従来の SQL クエリ

まずは、2024年以降における不動産価格の中央値が高いロンドンの地区(district)を取得するクエリから始めましょう。

```sql
SELECT district, count() AS sales, round(median(price)) AS median_price
FROM uk_price_paid
WHERE (town = 'LONDON') AND (date >= '2024-01-01')
GROUP BY district
ORDER BY median_price DESC
LIMIT 10;
```

```shell
┌─district───────────────┬─sales─┬─median_price─┐
│ KENSINGTON AND CHELSEA │  2694 │      1125000 │
│ RICHMOND UPON THAMES   │   708 │       960000 │
│ CITY OF WESTMINSTER    │  3830 │       900000 │
│ CITY OF LONDON         │   367 │       842000 │
│ HARROW                 │     2 │       806750 │
│ HOUNSLOW               │   660 │       780000 │
│ CAMDEN                 │  3200 │       760000 │
│ HAMMERSMITH AND FULHAM │  3312 │       730000 │
│ ISLINGTON              │  3316 │       650000 │
│ WANDSWORTH             │  7155 │       625000 │
└────────────────────────┴───────┴──────────────┘
```

## `FROM` を先頭に配置する

ClickHouse SQL のあまり知られていない機能として、`FROM` 句を `SELECT` の前に配置できます。そのため、次のようなクエリも有効です。

```sql
FROM uk_price_paid
SELECT district, count() AS sales, round(median(price)) AS median_price
WHERE (town = 'LONDON') AND (date >= '2024-01-01')
GROUP BY district
ORDER BY median_price DESC
LIMIT 10;
```

データソースを先頭に置くことで、クエリの流れを視覚的に把握しやすくなります。操作対象のデータをまず指定し、その後にカラム、フィルタリング、集約、並べ替え、そして制限を記述します。`FROM` の位置が変わった点を除けば、これは依然として従来の SQL クエリです。

## パイプラインを構築する

ClickHouse 26.8 は、このトップダウンの記述スタイルをパイプライン SQL によってさらに推し進めています。`|>` 演算子は、あるステージの結果を次のステージへと引き渡し、テーブルの読み取り、行のフィルタリング、集約、結果のソート、そして最後にリミットの適用という一連の変換手順を明示的に表現します。

同じクエリをパイプラインとして記述した例がこちらです。

```sql
FROM uk_price_paid
|> WHERE (town = 'LONDON') AND (date >= '2024-01-01')
|> AGGREGATE count() AS sales, round(median(price)) AS median_price
   GROUP BY district
|> ORDER BY median_price DESC
|> LIMIT 10;
```

従来のクエリ、`FROM` 先頭のクエリ、そしてパイプライン版のいずれも、まったく同じ結果を返します。

通常の SQL でも段階的にクエリをシンプルにすることは可能ですが、パイプラインでは各 `|>` が明確なチェックポイントとなり、そこまでの処理だけで完結したクエリとして機能します。これにより、変換処理を 1 ステージずつ構築しながら動作を確認するのが非常に容易になります。

## ClickHouse はパイプラインをどのように変換するのか？

パイプライン化されたクエリは、実行前に標準的な SQL へと変換されます。
クエリの先頭に [`EXPLAIN SYNTAX`](https://clickhouse.com/docs/ja/reference/statements/explain#explain-syntax) を付加する(筆者自身、この記事を書いている最中に知りました！)と、パイプラインから生成された従来の SQL を確認できます。

```sql
EXPLAIN SYNTAX
FROM uk_price_paid
|> WHERE (town = 'LONDON') AND (date >= '2024-01-01')
|> AGGREGATE count() AS sales, round(median(price)) AS median_price
   GROUP BY district
|> ORDER BY median_price DESC
|> LIMIT 10
FORMAT LineAsString;
```

```sql
SELECT * FROM (
    SELECT * FROM (
        SELECT district, count() AS sales, round(median(price)) AS median_price
        FROM (
            SELECT * FROM (
                SELECT *
                FROM uk_price_paid
            )
            WHERE and(equals(town, 'LONDON'), greaterOrEquals(date, '2024-01-01'))
        )
        GROUP BY district
    )
    ORDER BY median_price DESC
)
LIMIT 10;
```

変換後のクエリにはネストした `SELECT` 文が複数含まれていますが、ClickHouse は中間結果を個別に実体化(マテリアライズ)するわけではなく、実行前にクエリ全体を最適化します。

## EXTEND によるカラムの追加

`EXTEND` を使用すると、パイプライン内にすでに存在するすべてのカラムを保持したまま、計算カラムを追加できます。

```sql
FROM uk_price_paid
|> WHERE town = 'LONDON' AND date >= '2024-01-01'
|> EXTEND round(price / 1000000, 2) AS price_millions
|> SELECT date, district, price, price_millions
|> ORDER BY price DESC
|> LIMIT 3;
```

```shell
┌───────date─┬─district──────┬─────price─┬─price_millions─┐
│ 2024-03-20 │ TOWER HAMLETS │ 164300000 │          164.3 │
│ 2024-03-20 │ TOWER HAMLETS │ 164300000 │          164.3 │
│ 2024-03-20 │ TOWER HAMLETS │ 161890000 │         161.89 │
└────────────┴───────────────┴───────────┴────────────────┘
```

この例における `EXTEND` の使用は、`SELECT *, round(price / 1000000, 2) AS price_millions` と同等です。このクエリに対する `EXPLAIN SYNTAX` の出力は以下のようになります。

```sql
SELECT * FROM (
      SELECT date, district, price, price_millions
      FROM (
          SELECT *, round(divide(price, 1000000), 2) AS price_millions
          FROM (
              SELECT *
              FROM (
                  SELECT *
                  FROM uk_price_paid
              )
              WHERE and(equals(town, 'LONDON'), greaterOrEquals(date, '2024-01-01'))
          )
      )
      ORDER BY price DESC
  )
  LIMIT 3;
```


## 中間結果の再利用

パイプライン構文を使用すると、既存の集約処理を拡張することもできます。例えば、次のクエリは郡(county)ごとにグループ化し、各地区の不動産価格の中央値を算出します。

```sql
SELECT county, district, median(price) AS district_median
FROM uk_price_paid
WHERE date >= '2024-01-01'
GROUP BY county, district
```

`district_median` というエイリアスは、後続のパイプラインステージで利用可能なカラムになります。これにより、ネストしたサブクエリや CTE を自分で書くことなく、このカラムを対象に再度集約を行えます。

したがって、このクエリを拡張して、各郡における地区レベルの中央値の平均を算出し、その値が最も高い上位 10 の郡を返すようにできます。

```sql
SELECT county, district, median(price) AS district_median
FROM uk_price_paid
WHERE date >= '2024-01-01'
GROUP BY county, district
|> AGGREGATE round(avg(district_median)) AS average_district_median
   GROUP BY county
|> ORDER BY average_district_median DESC
|> LIMIT 10;
```

```shell
┌─county─────────────────┬─average_district_median─┐
│ GREATER LONDON         │                  558476 │
│ WINDSOR AND MAIDENHEAD │                  520000 │
│ SURREY                 │                  497455 │
│ WOKINGHAM              │                  478000 │
│ HERTFORDSHIRE          │                  452250 │
│ BUCKINGHAMSHIRE        │                  443870 │
│ ISLES OF SCILLY        │                  430000 │
│ BRIGHTON AND HOVE      │                  405000 │
│ OXFORDSHIRE            │                  400451 │
│ BRACKNELL FOREST       │                  400000 │
└────────────────────────┴─────────────────────────┘
```

## ステージの順序に関する注意点

注意すべき点として、パイプラインステージの順序が実行結果を左右することが挙げられます。もし `LIMIT 10` を 2 つ目の集約処理の前に移動すると、中間結果が 10 件の地区中央値だけに制限されます。その結果、郡の平均値はすべての地区からではなく、その 10 行のみから算出されることになります。

```sql
SELECT county, district, median(price) AS district_median
FROM uk_price_paid
WHERE date >= '2024-01-01'
GROUP BY county, district
|> LIMIT 10
|> AGGREGATE round(avg(district_median)) AS average_district_median
   GROUP BY county
|> ORDER BY average_district_median DESC;
```

```shell
┌─county──────────────────────────────┬─average_district_median─┐
│ BUCKINGHAMSHIRE                     │                  443000 │
│ BRIGHTON AND HOVE                   │                  405000 │
│ BRACKNELL FOREST                    │                  400000 │
│ BATH AND NORTH EAST SOMERSET        │                  390000 │
│ BEDFORD                             │                  330000 │
│ BOURNEMOUTH, CHRISTCHURCH AND POOLE │                  325000 │
│ BRIDGEND                            │                  205000 │
│ BLACKBURN WITH DARWEN               │                  150000 │
│ BLAENAU GWENT                       │                  126250 │
│ BLACKPOOL                           │                  125000 │
└─────────────────────────────────────┴─────────────────────────┘
```

> **Note**
>
> 最初の `LIMIT` の前に `ORDER BY` がないため、選択される 10 件の地区行は決定的ではなく、正確な結果は実行ごとに異なる可能性があります。

## 他のステートメントでパイプラインを使用する

パイプラインは `SELECT` で始まるクエリに限定されず、サブクエリ、`INSERT ... SELECT` 文、ビューなど、ClickHouse が `SELECT` クエリを期待するあらゆる場所で使用できます。

次の例では、ビューの基盤となるクエリとしてパイプラインを使用しています。

```sql
CREATE VIEW million_pound_london_sales AS
FROM uk_price_paid
|> WHERE town = 'LONDON' AND price >= 1000000
|> SELECT date, price, district, postcode1, postcode2;
```

その後、従来の SQL 構文を使用してそのビューを照会できます。

```sql
SELECT *
FROM million_pound_london_sales
ORDER BY price DESC
LIMIT 3;
```

```shell
┌───────date─┬─────price─┬─district────────────┬─postcode1─┬─postcode2─┐
│ 2017-07-31 │ 594300000 │ CITY OF WESTMINSTER │ W1U       │ 8EW       │
│ 2018-02-08 │ 569200000 │ CITY OF WESTMINSTER │ W1J       │ 7BT       │
│ 2019-11-20 │ 542540820 │ CAMDEN              │ NW5       │ 2HB       │
└────────────┴───────────┴─────────────────────┴───────────┴───────────┘
```

また、`INSERT` のソースとしてパイプラインを使用することも可能です。例えば、高額なロンドンの不動産を保存する次のようなテーブルがあるとします。

```sql
CREATE TABLE expensive_london_sales
  (
      date Date,
      price UInt32,
      district LowCardinality(String)
  )
  ENGINE = MergeTree
  ORDER BY (district, date);
```

従来の `INSERT ... SELECT` クエリでは、ソーステーブルの前に選択するカラムを指定します。

```sql
INSERT INTO expensive_london_sales (date, price, district)
SELECT date, price, district
FROM uk_price_paid
WHERE town = 'LONDON' AND price >= 1000000;
```

パイプライン構文を使用すると、挿入先の選択、ソースの読み取り、行のフィルタリング、挿入するカラムの選択という、実際の処理順序に沿って同じ操作を記述できます。

```sql
INSERT INTO expensive_london_sales (date, price, district)
FROM uk_price_paid
|> WHERE town = 'LONDON' AND price >= 1000000
|> SELECT date, price, district;
```

## まとめ

パイプライン SQL は、ClickHouse のクエリを表現する新たな手段を提供します。従来の SQL を完全に置き換えるものではありませんが、クエリを一連の変換処理として記述することで、複数ステージにわたるクエリの構築や処理の流れの把握が容易になります。

ぜひ使ってみた感想や、ご自身のクエリをシンプルにするために役立ったかどうかをお聞かせください。

---

## 今すぐ始める

実際のデータで ClickHouse を試してみませんか？ClickHouse Cloud は数分で使い始めることができ、300 ドル分の無料クレジットも提供されます。

[サインアップ](https://console.clickhouse.cloud/signUp?loc=blog-cta-2211&utm_blogctaid=2211)

---