すべてのプロダクト
Search
ドキュメントセンター

Lindorm:ウェアハウスモードのカラムナテーブル

最終更新日:May 23, 2026

ウェアハウスモードカラムナテーブルは、OLAP リソースグループの組み込み分析テーブルフォーマットです。カラムナストレージと MPP 実行エンジンを使用して、バルク分析とリアルタイム分析の両方に対応します。データは OLAP リソースグループ内に完全に格納され、レイクハウスカラムナデータ (lindorm_columnar) やワイドテーブルエンジンデータ (lindorm_table) とは分離されています。また、完全な DDL/DML サーフェスと高スループットな OLAP 実行をサポートします。

OLAP リソースグループに接続し、ウェアハウスモードのカタログに切り替えます:

SET CATALOG default_catalog;

テーブルタイプ

データが格納された後に更新が必要かどうかに基づいてテーブルタイプを選択します。2 つのタイプは、書き込みがどのように処理され、格納されるかという点で異なります。テーブルタイプは作成時に固定され、後から変更することはできません。エンジンは、テーブルタイプに従って受信データをソート、処理、および格納します。

Duplicate Key テーブル

Duplicate Key テーブルは、デフォルトのテーブルタイプです。これは アペンドオンリー の書き込みモデルを使用し、すべての行が書き込まれたとおりに正確に保持されます。エンジンは重複排除や集約を実行しないため、同じソートキーを持つ複数の行が共存できます。

使用するケース

  • 更新が不要なアペンドオンリーの詳細データ — 生ログ、行動イベント、インストルメンテーショントレース。

  • 固定された事前集約パターンがなく、多くの非定型なディメンションに沿って集約する必要がある単一のデータセット。

  • 任意の述語で行単位のクエリを実行する必要がある、完全な履歴スナップショット。

使用しないケース:ワークロードに頻繁な更新や削除 (注文ステータスの変更やユーザープロファイルの編集など) が含まれる場合は、代わりに Primary Key テーブルを使用します。

DDL

-- 例:ユーザー行動イベントログ
-- (event_day, event_type) でソート — 日付とイベントタイプによるフィルタリングを高速化します。
-- user_id でハッシュバケット化 — ユーザーの行を 1 つのバケットにまとめて配置し、ローカルでの結合を可能にします。
CREATE TABLE user_event_log (
    event_day   DATE        NOT NULL COMMENT 'イベントの日付',
    event_type  TINYINT     NOT NULL COMMENT 'イベントタイプ (1=クリック、2=インプレッション、3=コンバージョン)',
    user_id     BIGINT      NOT NULL COMMENT 'ユーザー ID',
    item_id     BIGINT               COMMENT 'コンテンツ/アイテム ID',
    session_id  VARCHAR(64)          COMMENT 'セッション ID',
    duration_ms INT                  COMMENT '滞在時間 (ミリ秒)',
    province    VARCHAR(32)          COMMENT '省/都道府県',
    channel     VARCHAR(32)          COMMENT 'ソースチャネル'
)
DUPLICATE KEY(event_day, event_type)
PARTITION BY date_trunc('day', event_day)
DISTRIBUTED BY HASH(user_id);

  • ORDER BY は、ソートキーを宣言するための DUPLICATE KEY の同等の代替手段です。両方が存在する場合、DUPLICATE KEY は無視されます。ORDER BY を使用する場合、DISTRIBUTED BYに配置します。

  • ソートキーもバケットキーも指定されていない場合、エンジンはデフォルトで最初の 3 列をソートキーとして使用します。

  • ビットマップインデックスとブルームフィルターインデックスは、任意の列に作成できます。キー列とバリュー列の区別はありません。

  • Duplicate Key テーブルは UPDATE または DELETE をサポートしません (これらが必要な場合は Primary Key テーブルを使用します)。

  • 2 つの同一の行は 2 つの独立した行として格納されます。自動的な重複排除はありません。

ストレージモデル

書き込まれたすべての行は、ソートキーで物理的にソートされ、列指向ファイルに格納されます。各レコードは独立しており、その場では変更されません。バックグラウンドコンパクションは、小さなファイルをマージして領域を再利用しますが、行の内容を重複排除したり変更したりすることはありません。

このモデルの強みは、高い書き込みスループットとクエリの柔軟性です。書き込みにはマージや重複排除のオーバーヘッドがないため、このテーブルは高頻度のアペンドワークロードに最適です。クエリは、事前定義された集約によってスキーマがロックされることなく、任意のディメンションで自由に集計できます。

ソートキーの設計

ソートキーは DUPLICATE KEY または ORDER BY で宣言されます。これは、物理的な行の順序と shortkey インデックスの基礎の両方を定義します。

設計原則:

  • 最も頻繁にフィルタリングされる列を最初に配置します。shortkey インデックスはソートキーの連続したプレフィックスにのみ一致するため、先頭の列は WHERE 句で最も頻繁に使用される列である必要があります。

  • プレフィックスには低カーディナリティの列を優先します。低カーディナリティの列 (日付、イベントタイプ、都市など) は、同一の値をまとめてクラスタ化するため、ZoneMap インデックスと組み合わせることで大量の I/O をスキップできます。

  • プレフィックスに高カーディナリティの列を避けます。user_iddevice_id のような列は、分散しすぎているため shortkey インデックスが有効に機能しません。代わりにブルームフィルター列またはバケットキーとして使用します。

  • ソートキーは 3 列以下に保ちます。shortkey インデックスは 36 バイトを超えて作成されないため、列を追加しても効果は減少します。


Primary Key テーブル

Primary Key テーブルは、主キー列に一意かつ NULL でない制約を適用します。これは、リアルタイム更新高スループットの非定型クエリを組み合わせたワークロード向けに専用設計されています。

使用するケース

  • Flink CDC や同様のツールを介して、MySQL や PostgreSQL などのトランザクションデータベースから完全な CDC ストリーム (挿入/更新/削除) を同期する。

  • 高頻度の UPSERT ワークロード — 注文ステータスの追跡、リアルタイムのユーザープロファイル更新、物流送り状の状態同期。

  • 複数のデータストリームが 1 つのワイドテーブルに書き込み、それぞれが独自の列のサブセットを更新する (部分的な列更新)。

  • 最新のスナップショットを参照する必要がある、同時実行される複雑な分析クエリ。

DDL

-- 例:eコマース注文のリアルタイム分析テーブル
-- 主キー列 (order_id, dt) は列リストの先頭に配置されます。
-- パーティション列 dt とバケット列 order_id は両方とも主キーに含まれます。
-- ORDER BY (dt, merchant_id) は、日付とマーチャントでの範囲スキャンを高速化します。
CREATE TABLE orders (
    order_id       BIGINT         NOT NULL COMMENT '注文 ID',
    dt             DATE           NOT NULL COMMENT '注文日',
    merchant_id    INT            NOT NULL COMMENT 'マーチャント ID',
    user_id        BIGINT         NOT NULL COMMENT 'ユーザー ID',
    good_id        INT            NOT NULL COMMENT '商品 ID',
    good_name      VARCHAR(128)   NOT NULL COMMENT '商品名',
    total_amount   DECIMAL(18, 2) NOT NULL COMMENT '注文金額',
    payment_amount DECIMAL(18, 2)          COMMENT '支払済金額',
    status         TINYINT        NOT NULL COMMENT '注文ステータス',
    create_time    DATETIME       NOT NULL COMMENT '作成日時',
    update_time    DATETIME                COMMENT '最終更新日時'
)
PRIMARY KEY (order_id, dt)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(order_id)
ORDER BY (dt, merchant_id);

  • 主キー列は一意であり、NULL 値は許可されません。主キーに DECIMAL は使用できません。主キーの合計長は 128 バイトを超えてはなりません。

  • 主キー列は、他の列を挟まずに列リストの先頭に配置する必要があります。すべての主キー列を CREATE TABLE の列定義の先頭に配置してください。間に主キー以外の列を混在させると、DDL は失敗します。

  • パーティショニングとハッシュバケットを使用する場合、エンジンが更新する正確なパーティションとバケットを特定できるように、主キーにパーティション列とバケット列を含める必要があります

  • 主キーの値は変更不可能です。UPDATE は主キー以外の列のみを書き換えます。主キーの値を変更するには、行を DELETE し、新しい行を INSERT します。

  • ソートキー (ORDER BY) は主キーから完全に分離されており、任意の列を組み合わせて指定できます。ソートキーはテーブル作成後に変更できます (ALTER TABLE tbl ORDER BY ... を使用)。ただし、ソートキーを削除したり、ソート列のデータ型を変更したりすることはできません。

  • Primary Key テーブルは、INSERTUPDATE (キー列またはバリュー列のいずれかでフィルタリング)、および DELETE (キー列またはバリュー列のいずれかでフィルタリング) という完全な DML 操作をサポートします。

  • Primary Key テーブルはハッシュバケット化のみをサポートします。ランダムバケットはサポートされていません。

書き込みモデル:Delete+Insert

Primary Key テーブルは、従来の Merge-On-Read モデルとは対照的に、Delete+Insert 書き込み戦略を使用します。

  • 書き込み時:エンジンはプライマリキーインデックスを介して既存の行を検索し、それを削除済みとしてマークし、新しい行を書き込み、インデックスを更新します。

  • クエリ時:データはすでに最終状態にあります。オンラインでのマルチバージョンマージは不要で、リーダーは単に有効な行をストリーミングします。

その結果、Primary Key テーブルは、特に読み取り増幅が問題となるような大量の更新トラフィック下において、Merge-On-Read エンジンよりも著しく優れたクエリレイテンシーを提供します。

主キーの設計原則

  • 主キーはビジネスエンティティを一意に識別する必要があります。テーブル全体で単一のレコードを特定する列または列のセットを選択します。たとえば、order_id(user_id, event_id) です。

  • 主キーにはパーティション列とバケット列を含める必要があります。PARTITION BYDISTRIBUTED BY HASH を使用する場合、主キーにこれらの列を含める必要があります。そうしないと、エンジンは更新対象の正確なパーティションとバケットを特定できません。

  • 主キーはコンパクトに保ちます。合計長は最大 128 バイトとし、3 列以下を目指します。VARCHAR よりも INTBIGINT のようなコンパクトな型を優先します。主キー列が多すぎると、インデックスのストレージとメンテナンスのコストが増大します。

  • 主キー列では DECIMALを使用できません。 代わりに BIGINTVARCHAR、またはその他のサポートされている型を使用してください。

  • 主キー列は NOT NULL でなければなりません。すべての主キー列は NOT NULL として宣言する必要があります。

主キーから分離されたソートキー

PRIMARY KEY は一意性を定義し、ORDER BY は物理的な行の順序を定義します。この 2 つは完全に分離されており、独立して設計できます。

  • ソートキーは任意の列を組み合わせて指定でき、主キー列を含む必要はありません。フィルタで最も頻繁に使用される列に基づいて選択します。

  • ORDER BY を省略した場合、デフォルトで主キー列がソートキーとして使用されます。

  • ソートキーはテーブル作成後に変更できます:ALTER TABLE tbl ORDER BY (col1, col2, ...)。ソートキーの削除はサポートされておらず、ソート列のデータ型を変更することもできません。

部分的な列更新

Primary Key テーブルは部分的な列更新をサポートします。各ライターは自身が所有する列のみを送信し、エンジンはそれらを主キーでマージします。これは、複数のデータストリームからリアルタイムでワイドテーブルを構築するための基盤となります。

-- status 列と update_time 列のみを更新します。他の列は現在の値を保持します。
INSERT INTO orders (order_id, status, update_time)
VALUES (10001, 3, '2024-03-01 10:30:00');
-- リストにない列 (total_amount, user_id など) は変更されません。

データ分散

分散戦略は、クラスター全体でデータを物理的にどのように配置するかを決定します。適切に設計されたパーティションとバケットの構成により、クエリ実行時にエンジンがクエリプルーニングによって不要なデータを除外でき、コンピューティングリソース全体に処理を均等に分散できます。

ウェアハウスモードのカラムナテーブルは、2 階層レイアウトを使用します。

Table
  └── Partition     ← 論理的な分割 — データのライフサイクル管理とクエリプルーニングに使用します。
        └── Bucket  ← 物理的な分割 — 均等な分散と並列スキャンに使用します。

パーティション

パーティションは、パーティションキーに沿ってテーブルを独立した管理単位に分割します。クエリがパーティションキーでフィルターする場合、エンジンは不要なパーティションをプルーニングし、対象のみをスキャンするため、I/O を大幅に削減できます。パーティションはライフサイクル管理の単位でもあります。DROP PARTITION は、DELETE よりもはるかに高速に履歴データを削除します。

式パーティション (推奨)

date_trunc() のような関数式で定義します。エンジンは入力データ内の時刻フィールドに基づいてパーティションをオンザフライで作成するため、事前にパーティションリストを定義する必要はありません。これは時系列ワークロードに推奨される方法です。

-- 日次パーティション (自動作成)
PARTITION BY date_trunc('day', event_time)

-- 月次パーティション (自動作成)
PARTITION BY date_trunc('month', order_date)

レンジパーティション

データを連続した数値または日付の範囲に分割します。日、週、月単位で管理する時系列データに適しています。

PARTITION BY RANGE(event_day) (
    PARTITION p202401 VALUES LESS THAN ("2024-02-01"),
    PARTITION p202402 VALUES LESS THAN ("2024-03-01"),
    PARTITION p202403 VALUES LESS THAN ("2024-04-01")
)

リストパーティション

列挙された列の値に基づいてデータを分割します。地域、ステータス、その他の離散カテゴリでデータを管理する場合に適しています。

PARTITION BY LIST(region) (
    PARTITION p_east   VALUES IN ("shanghai", "jiangsu", "zhejiang"),
    PARTITION p_north  VALUES IN ("beijing", "tianjin", "hebei"),
    PARTITION p_south  VALUES IN ("guangdong", "fujian", "hainan")
)
 パーティションキーは、日付型 (DATEDATETIME) または整数型 (INTBIGINT など) である必要があります。

バケット

各パーティション内で、バケットはデータを物理ストレージユニット (Bucket / Tablet) に均等に分散し、スキャンを並列に実行できるようにします。

ハッシュバケッティング

行はバケットキーのハッシュに基づいてルーティングされるため、同じバケットキーを持つ行は同じバケットに配置されます。これにより、等価述語や同一キー結合が高速化されます (コロケートジョインではネットワークシャッフルが不要です)。スキューを避けるため、高カーディナリティで、かつ等価フィルターまたは結合条件で頻繁に使用される列をバケットキーとして選択してください。

-- エンジンがバケット数を自動的に選択します
DISTRIBUTED BY HASH(user_id)

-- バケット数を手動で指定します
DISTRIBUTED BY HASH(order_id) BUCKETS 32

ランダムバケッティング

バケットキーを指定せずに、行をランダムにバケットに割り当てます。書き込みが均等に分散されるため、固定のクエリパターンがない詳細データの取り込みに適しています。Duplicate Key テーブル はランダムバケッティングがデフォルトですが、Primary Key テーブル ではサポートされません。

DISTRIBUTED BY RANDOM
ベストプラクティス バケットあたりのデータ量は100 MB ~ 1 GB を目安にしてください。バケットが大きすぎると並列性が低下し、バケット数が多すぎるとメタデータとスケジューリングオーバーヘッドが増加します。同様に、細かすぎるパーティション (時間単位のパーティションなど) は避けてください。小さなパーティションが大量にあると、メタデータへの負荷が大きくなります。

組み合わせの例

-- チャージ明細テーブル: 日次の式パーティション、user_id によるハッシュバケッティング
CREATE TABLE recharge_detail (
    id             BIGINT         NOT NULL COMMENT "連番ID",
    user_id        BIGINT         NOT NULL COMMENT "ユーザーID",
    recharge_money DECIMAL(18, 2) NOT NULL COMMENT "チャージ額",
    city           VARCHAR(32)    NOT NULL COMMENT "都市",
    dt             DATE           NOT NULL COMMENT "チャージ日"
)
DUPLICATE KEY(id)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(user_id);

データ型

数値型

バイト数

範囲

BOOLEAN

1

TRUE / FALSE

TINYINT

1

-128 ~ 127

SMALLINT

2

-32,768 ~ 32,767

INT

4

-2,147,483,648 ~ 2,147,483,647

BIGINT

8

-2⁶³ ~ 2⁶³-1

LARGEINT

16

-2¹²⁷ ~ 2¹²⁷-1。128 ビットのデバイス ID など、非常に大きな整数に使用します。

FLOAT

4

単精度浮動小数点 (近似値)

DOUBLE

8

倍精度浮動小数点 (近似値)

DECIMAL(M, D)

可変

正確な 10 進数です。M は総桁数 (最大 38)、D は小数点以下の桁数です。金融データやトランザクションデータには、この型の使用を推奨します。

注記 数値型は UNSIGNED 修飾子をサポートしていません。

文字列型

最大長

説明

CHAR(N)

255 bytes

長さが N になるよう空白でパディングされる固定長文字列です。

VARCHAR(N)

1,048,576 bytes

実際の長さで格納される可変長文字列です。

STRING

VARCHAR と同じ

大容量の VARCHAR と等価です。非構造化テキストに適しています。

日付と時刻のデータ型

説明

DATE

YYYY-MM-DD 形式の日付です。範囲:0000-01-01 ~ 9999-12-31。

DATETIME

マイクロ秒精度のYYYY-MM-DD HH:MM:SS[.ffffff] 形式の日付時刻です。

OLAP 固有のデータ型

説明

BITMAP

ビットマップ型です。BITMAP_UNION と組み合わせ、UV 計算などの正確な個別カウントを実行します。

HLL

HyperLogLog 型です。HLL_UNION と組み合わせ、約1%の誤差と非常に低いメモリコストで、近似的な個別カウントを実行します。

BITMAP 列と HLL 列は、ソートキー列またはプライマリキー列として使用できません。

半構造化データ型

説明

JSON

柔軟な JSON ドキュメントです。JSON_QUERY-> 演算子によるパスクエリをサポートします。時間の経過とともにスキーマが変化する場合に適しています。

ARRAY<T>

同種要素の配列です。array_aggarray_containsarray_length などの配列関数をサポートします。

MAP<K, V>

キーバリューマップです。動的な属性、タグなど、スキーマが固定されていないデータの格納に適しています。

STRUCT<field: type>

フィールド名でアクセスするネストされた構造体です。深くネストされたデータのモデリングに適しています。


コンピューティング機能

SQL 分析オペレーター

ウェアハウスモードの列指向テーブルは、標準的な SQL 分析構文をサポートし、OLAP のあらゆるクエリパターンに対応します。

射影とフィルター

SELECT は、列の任意のサブセットを射影します。列は AS でエイリアスを付けることができ、算術式や関数呼び出しで使用できます。WHERE は、標準的な述語の組み合わせを使用できます:

述語

等価性

status = 1

範囲

amount BETWEEN 100 AND 1000dt >= '2024-01-01'

セットメンバーシップ

city IN ('beijing', 'shanghai')type NOT IN (3, 5)

パターンマッチ

name LIKE 'A%'

NULL チェック

pay_time IS NULLremark IS NOT NULL

正規表現

email REGEXP '^[a-zA-Z0-9]+'

述語は可能な限りストレージレイヤーにプッシュダウンされ、ディスクから読み取られる行数を減らします。

JOIN

サポートされている結合タイプ:

結合タイプ

説明

INNER JOIN

両方のテーブルで一致する行を返します。

LEFT OUTER JOIN

左テーブルのすべての行を保持し、右テーブルに一致がない場合は NULL が設定されます。

RIGHT OUTER JOIN

右テーブルのすべての行を保持し、左テーブルに一致がない場合は NULL が設定されます。

FULL OUTER JOIN

両方のテーブルのすべての行を保持します。

CROSS JOIN

デカルト積。

LEFT SEMI JOIN

右テーブルに一致する行がある左テーブルの行を返します。右テーブルの列は返されません。

LEFT ANTI JOIN

右テーブルに一致する行がない左テーブルの行を返します。

オプティマイザ は、テーブルサイズとデータ分布に基づいて結合戦略を自動的に選択します。これには、小さなテーブルをすべてのノードにブロードキャストする (ブロードキャスト結合)、結合キーでデータを再シャッフルする (シャッフル結合)、またはバケットキーが結合キーと一致する場合にネットワークシャッフルなしでコロケート結合を実行する方法などがあります。

-- 例:注文詳細とユーザープロファイルを結合
SELECT
    o.order_id,
    o.total_amount,
    u.user_level,
    u.city
FROM orders o
INNER JOIN user_profile u ON o.user_id = u.user_id
WHERE o.dt >= '2024-01-01'
  AND u.user_level IN ('gold', 'platinum');

集合演算

演算子

説明

UNION ALL

重複を保持したまま、複数のクエリの結果を結合します。UNION よりも高速です。

UNION

複数のクエリの結果を結合し、重複を除外します。

INTERSECT

2つの結果セットの共通部分を返します。

EXCEPT / MINUS

最初の結果セットにあり、2番目の結果セットにはない行を返します。

集計 (GROUP BY)

標準的な集計関数 (COUNTSUMAVGMINMAXCOUNT DISTINCT) がサポートされています。集計結果のフィルタリングには HAVING を使用します。

以下の拡張グルーピング構文もサポートされています。これらは単一のスキャンで複数の集計を計算するため、クエリを個別に実行するよりもパフォーマンスが向上します:

-- GROUPING SETS:ディメンションの組み合わせを明示的に列挙します
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY GROUPING SETS ((city, dt), (city), ());

-- ROLLUP:階層的な合計 (詳細レベルから総計まで)
-- GROUPING SETS ((city, dt), (city), ()) と同等
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY ROLLUP (city, dt);

-- CUBE:ディメンションのすべての組み合わせ
-- GROUPING SETS ((city, dt), (city), (dt), ()) と同等
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY CUBE (city, dt);

ソートとページネーション

ORDER BY は、複数列 (ASC または DESC) でソートします。LIMIT および OFFSET を組み合わせることで、ページ分割読み取りが可能です。

SELECT order_id, total_amount
FROM orders
WHERE dt = '2024-03-01'
ORDER BY total_amount DESC, order_id ASC
LIMIT 20 OFFSET 40;

サブクエリ

オプティマイザは、変換可能なサブクエリを同等の JOIN に書き換えます。サポートされているサブクエリの形式は次のとおりです:

サブクエリのタイプ

スカラーサブクエリ

WHERE amount > (SELECT AVG(amount) FROM orders)

IN / NOT IN サブクエリ

WHERE user_id IN (SELECT user_id FROM vip_users)

EXISTS / NOT EXISTS サブクエリ

WHERE EXISTS (SELECT 1 FROM blacklist WHERE ...)

相関サブクエリ

外部クエリの列を参照し、行ごとに評価されるサブクエリです。

ウィンドウ関数

ウィンドウ関数は、元の行を保持したまま行のグループに対して集計を計算する、OLAP ワークロードの中核機能です。ウィンドウは OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...) 句で定義します。

カテゴリ

一般的な関数

ランキング

ROW_NUMBER()RANK()DENSE_RANK()NTILE(n)

集計

SUM()AVG()MIN()MAX() (スライディングウィンドウをサポート)

オフセットアクセス

LAG(col, n, default)LEAD(col, n, default)

先頭/末尾

FIRST_VALUE(col)LAST_VALUE(col)

-- 例:ユーザーごとの累積支出と日次ランキング
SELECT
    user_id,
    order_id,
    dt,
    total_amount,
    SUM(total_amount) OVER (
        PARTITION BY user_id
        ORDER BY dt
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS cumulative_amount,
    RANK() OVER (
        PARTITION BY dt
        ORDER BY total_amount DESC
    ) AS daily_rank
FROM orders;

-- 例:各ユーザーの連続購入間の日数
SELECT
    user_id,
    order_id,
    dt,
    DATEDIFF(dt, LAG(dt, 1) OVER (PARTITION BY user_id ORDER BY dt)) AS days_since_last_order
FROM orders;

CASE WHEN

検索CASE式と単純CASE式の両方がサポートされています。CASE WHENSELECTWHEREORDER BY、集計関数の引数などで使用できます。

SELECT
    order_id,
    total_amount,
    CASE
        WHEN total_amount >= 10000 THEN 'large'
        WHEN total_amount >= 1000  THEN 'medium'
        WHEN total_amount >= 100   THEN 'small'
        ELSE 'micro'
    END AS amount_tier,
    SUM(CASE WHEN status = 1 THEN total_amount ELSE 0 END) AS paid_amount
FROM orders
GROUP BY order_id, total_amount;

インデックス

ウェアハウスモードの列指向テーブルは、複数層のインデックスシステムを備えています。各インデックスは異なるクエリパターンを対象とし、連携して動作することでクエリに不要なデータをスキップします。

ショートキーインデックス

自動的に構築されます。エンジンは、ソートキーのプレフィックス (最大 36 バイト、最大 3 列) を 1024 行ごとに取得し、スパースインデックスのエントリとして格納します。クエリがソートキーのプレフィックスでフィルタリングされると、二分探索で対象のデータブロックが特定されます。手動でのインデックス作成は不要です。

発動条件: フィルターがソートキーの プレフィックス である必要があります。

ソートキー: (event_day, site_id, city_code)

✅ WHERE event_day = '2024-01-01'                       -- ショートキーインデックスにヒットします
✅ WHERE event_day = '2024-01-01' AND site_id = 10      -- ショートキーインデックスにヒットします
❌ WHERE city_code = 'BJ'                               -- プレフィックスではないため、インデックスは使用できません
ソートキーの先頭に、最も頻繁にフィルタリングされる列を配置することで、ショートキーインデックスで優先的にカバーされるようにします。

ZoneMap インデックス

自動的に構築されます。すべてのデータブロック (デフォルトで 64 KB の Page) のすべての列に対して、エンジンは最小値、最大値、および NULL 値の有無に関する統計情報を保持します。範囲クエリの実行中、エンジンは述語を各 Page の最小/最大値と比較し、一致する可能性のない Page をスキップします。これにより、データブロックレベルでのプルーニングが行われます。

効果的なケース: 数値や日付などの順序付けられた型に対する範囲フィルター (BETWEEN><>=<=)。

ブルームフィルターインデックス

ブルームフィルター をベースとしたスキップインデックスであり、データブロック (Page) ごとに構築されます。書き込み時、エンジンは各 Page の列値をハッシュ化してフィルターに格納します。クエリ時、エンジンは述語値をハッシュ化し、フィルターにその値が含まれていない Page をスキップすることで I/O を削減します。

使用ケース: ユーザーID、注文ID、デバイスID などの高カーディナリティ列に対する 等価述語 (=IN)。これらの列は個別値の種類が多すぎるため、ビットマップインデックスではコスト効率が良くありません。

インデックスは PROPERTIESbloom_filter_columns で指定します。テーブル作成後、ALTER TABLE を使用して追加または変更できます。

-- テーブル作成時に定義
CREATE TABLE orders (
    order_id    BIGINT         NOT NULL,
    user_id     BIGINT         NOT NULL,
    merchant_id INT            NOT NULL,
    dt          DATE           NOT NULL,
    amount      DECIMAL(18, 2)
)
PRIMARY KEY(order_id)
DISTRIBUTED BY HASH(order_id)
PROPERTIES (
    "bloom_filter_columns" = "user_id, merchant_id"
);

-- 作成後に追加または変更
ALTER TABLE orders SET ("bloom_filter_columns" = "user_id, merchant_id, dt");

-- ブルームフィルターインデックスを削除
ALTER TABLE orders SET ("bloom_filter_columns" = "");

注意:

  • TINYINTFLOATDOUBLE、および DECIMAL 列はサポートされていません。

  • 等価述語 でのみ効果があります。範囲述語 (><BETWEEN) は使用できません。

  • 低カーディナリティの列には効果がありません。そのような列にはビットマップインデックスを使用してください。

ビットマップインデックス

個別値ごとにビットマップを構築します (各ビットは行に対応し、1 はその行がその値を持つことを示します)。複数条件のクエリは、ビットマップに対する AND/OR/NOT 演算で評価され、非常に効率的に結果セットを生成します。内部ストレージは Roaring Bitmap であり、従来のビットマップと比較して最大 90% のストレージ領域を節約できます。

使用ケース: 中カーディナリティの列 (個別値が 10,000~100,000 個のケースが最適) に対する等価述語、および 複数列を組み合わせたフィルター。例えば、単一のクエリで年齢層、顧客ティア、都市によってユーザープロファイルテーブルをセグメント化する場合などです。

ビットマップインデックスは、テーブル作成時に INDEX 句内で定義するか、後から CREATE INDEX (非同期のスキーマ変更操作) を使用して追加します。

-- テーブル作成時に作成
CREATE TABLE user_profile (
    user_id      BIGINT      NOT NULL COMMENT "ユーザー ID",
    age_range    TINYINT              COMMENT "年齢層 (1-5)",
    gender       TINYINT              COMMENT "性別 (0=女性, 1=男性)",
    city_code    VARCHAR(20)          COMMENT "都市コード",
    user_level   TINYINT              COMMENT "顧客ティア (1-5)",
    register_day DATE                 COMMENT "登録日",
    INDEX idx_age_range  (age_range)  USING BITMAP COMMENT "年齢層インデックス",
    INDEX idx_city_code  (city_code)  USING BITMAP COMMENT "都市インデックス",
    INDEX idx_user_level (user_level) USING BITMAP COMMENT "ティアインデックス"
)
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id);

-- 作成後に追加
CREATE INDEX idx_gender ON user_profile (gender) USING BITMAP;

-- インデックスの確認
SHOW INDEX FROM user_profile;

-- インデックスの削除
DROP INDEX idx_gender ON user_profile;

注意:

  • FLOATDOUBLEBOOLEAN、および DECIMAL 列はサポートされていません。

  • カーディナリティが非常に高い (個別値が 100 万を超える) 場合、ビットマップのストレージ消費が著しく増加するため、ブルームフィルターインデックスに切り替えてください。

  • インデックスによってフィルタリングされる行がごく一部の場合 (例えば、50% 以上の行が条件に一致するなど)、インデックスは効果がないばかりか、かえってパフォーマンスを低下させる可能性があります。その場合はインデックスを作成しないでください。

インデックスの選択

クエリパターン

推奨インデックス

ソートキーのプレフィックスに対する等価述語または範囲述語

ショートキーインデックス (自動。作成不要)

数値または日付列に対する範囲述語

ZoneMap インデックス (自動。作成不要)

中カーディナリティの列に対する等価述語、複数列を組み合わせたフィルター

ビットマップインデックス

高カーディナリティ列 (IDなど) に対する等価述語

ブルームフィルターインデックス