メインコンテンツへスキップ
メインコンテンツへスキップ

Variant(T1, T2, ...)

この型は、他のデータ型のユニオンを表します。型 Variant(T1, T2, ..., TN) は、この型の各行が T1T2、...、TN のいずれかの型の値、またはそのどれにも該当しない値 (NULL 値) を持つことを意味します。

ネストされた型の順序は重要ではありません。Variant(T1, T2) = Variant(T2, T1) です。 ネストされた型には、Nullable(...)、LowCardinality(Nullable(...))、Variant(...) 型を除き、任意の型を指定できます。

注記

類似した型をバリアントとして使用することは推奨されません (たとえば、Variant(UInt32, Int64) のような異なる数値型や、Variant(Date, DateTime) のような異なる日付型) 。 このような型の値を扱うと、あいまいさが生じる可能性があるためです。デフォルトでは、このような Variant 型を作成すると例外が発生しますが、設定 allow_suspicious_variant_types を使用すると有効にできます。

Variantの作成

テーブルのカラム定義で Variant 型を使用するには:

CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT v FROM test;
┌─v─────────────┐
│ ᴺᵁᴸᴸ          │
│ 42            │
│ Hello, World! │
│ [1,2,3]       │
└───────────────┘

通常のカラムからのCASTの使用:

SELECT toTypeName(variant) AS type_name, 'Hello, World!'::Variant(UInt64, String, Array(UInt64)) as variant;
┌─type_name──────────────────────────────┬─variant───────┐
│ Variant(Array(UInt64), String, UInt64) │ Hello, World! │
└────────────────────────────────────────┴───────────────┘

引数に共通の型がない場合の関数 if/multiIf の使用 (この機能を使うには設定 use_variant_as_common_type を有効にする必要があります) :

SET use_variant_as_common_type = 1;
SELECT if(number % 2, number, range(number)) as variant FROM numbers(5);
┌─variant───┐
│ []        │
│ 1         │
│ [0,1]     │
│ 3         │
│ [0,1,2,3] │
└───────────┘
SET use_variant_as_common_type = 1;
SELECT multiIf((number % 4) = 0, 42, (number % 4) = 1, [1, 2, 3], (number % 4) = 2, 'Hello, World!', NULL) AS variant FROM numbers(4);
┌─variant───────┐
│ 42            │
│ [1,2,3]       │
│ Hello, World! │
│ ᴺᵁᴸᴸ          │
└───────────────┘

配列要素やmapの値に共通の型がない場合の関数'array/map'の使用 (この場合は設定 use_variant_as_common_type を有効にする必要があります) :

SET use_variant_as_common_type = 1;
SELECT array(range(number), number, 'str_' || toString(number)) as array_of_variants FROM numbers(3);
┌─array_of_variants─┐
│ [[],0,'str_0']    │
│ [[0],1,'str_1']   │
│ [[0,1],2,'str_2'] │
└───────────────────┘
SET use_variant_as_common_type = 1;
SELECT map('a', range(number), 'b', number, 'c', 'str_' || toString(number)) as map_of_variants FROM numbers(3);
┌─map_of_variants───────────────┐
│ {'a':[],'b':0,'c':'str_0'}    │
│ {'a':[0],'b':1,'c':'str_1'}   │
│ {'a':[0,1],'b':2,'c':'str_2'} │
└───────────────────────────────┘

サブカラムとしてのVariantネスト型の読み込み

Variant型では、型名をサブカラムとして使用して、Variantカラムから単一のネスト型を読み込むことができます。 たとえば、variant Variant(T1, T2, T3) というカラムがある場合、構文 variant.T2 を使用して型 T2 のサブカラムを読み込めます。 このサブカラムの型は、T2Nullable の内側に含められる場合は Nullable(T2)、それ以外の場合は T2 になります。このサブカラムは 元の Variant カラムと同じサイズになり、元の Variant カラムの型が T2 ではないすべての行では、NULL 値 (または T2Nullable の内側に含められない場合は空の値) を含みます。

Variantのサブカラムは、関数 variantElement(variant_column, type_name) を使用して読み込むこともできます。

例:

CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT v, v.String, v.UInt64, v.`Array(UInt64)` FROM test;
┌─v─────────────┬─v.String──────┬─v.UInt64─┬─v.Array(UInt64)─┐
│ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ          │     ᴺᵁᴸᴸ │ []              │
│ 42            │ ᴺᵁᴸᴸ          │       42 │ []              │
│ Hello, World! │ Hello, World! │     ᴺᵁᴸᴸ │ []              │
│ [1,2,3]       │ ᴺᵁᴸᴸ          │     ᴺᵁᴸᴸ │ [1,2,3]         │
└───────────────┴───────────────┴──────────┴─────────────────┘
SELECT toTypeName(v.String), toTypeName(v.UInt64), toTypeName(v.`Array(UInt64)`) FROM test LIMIT 1;
┌─toTypeName(v.String)─┬─toTypeName(v.UInt64)─┬─toTypeName(v.Array(UInt64))─┐
│ Nullable(String)     │ Nullable(UInt64)     │ Array(UInt64)               │
└──────────────────────┴──────────────────────┴─────────────────────────────┘
SELECT v, variantElement(v, 'String'), variantElement(v, 'UInt64'), variantElement(v, 'Array(UInt64)') FROM test;
┌─v─────────────┬─variantElement(v, 'String')─┬─variantElement(v, 'UInt64')─┬─variantElement(v, 'Array(UInt64)')─┐
│ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ                        │                        ᴺᵁᴸᴸ │ []                                 │
│ 42            │ ᴺᵁᴸᴸ                        │                          42 │ []                                 │
│ Hello, World! │ Hello, World!               │                        ᴺᵁᴸᴸ │ []                                 │
│ [1,2,3]       │ ᴺᵁᴸᴸ                        │                        ᴺᵁᴸᴸ │ [1,2,3]                            │
└───────────────┴─────────────────────────────┴─────────────────────────────┴────────────────────────────────────┘

各行に格納されている Variant を確認するには、関数 variantType(variant_column) を使用できます。この関数は、各行に対応する Variant の型名を持つ Enum を返します (行が NULL の場合は 'None') 。

例:

CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT variantType(v) FROM test;
┌─variantType(v)─┐
│ None           │
│ UInt64         │
│ String         │
│ Array(UInt64)  │
└────────────────┘
SELECT toTypeName(variantType(v)) FROM test LIMIT 1;
┌─toTypeName(variantType(v))──────────────────────────────────────────┐
│ Enum8('None' = -1, 'Array(UInt64)' = 0, 'String' = 1, 'UInt64' = 2) │
└─────────────────────────────────────────────────────────────────────┘

Variant カラムと他のカラムの相互変換

Variant 型のカラムでは、4 種類の変換を実行できます。

StringカラムをVariantカラムに変換する

StringからVariantへの変換は、文字列値をパースしてVariant型の値を生成することで行われます。

SELECT '42'::Variant(String, UInt64) AS variant, variantType(variant) AS variant_type
┌─variant─┬─variant_type─┐
│ 42      │ UInt64       │
└─────────┴──────────────┘
SELECT '[1, 2, 3]'::Variant(String, Array(UInt64)) as variant, variantType(variant) as variant_type
┌─variant─┬─variant_type──┐
│ [1,2,3] │ Array(UInt64) │
└─────────┴───────────────┘
SELECT CAST(map('key1', '42', 'key2', 'true', 'key3', '2020-01-01'), 'Map(String, Variant(UInt64, Bool, Date))') AS map_of_variants, mapApply((k, v) -> (k, variantType(v)), map_of_variants) AS map_of_variant_types```
┌─map_of_variants─────────────────────────────┬─map_of_variant_types──────────────────────────┐
│ {'key1':42,'key2':true,'key3':'2020-01-01'} │ {'key1':'UInt64','key2':'Bool','key3':'Date'} │
└─────────────────────────────────────────────┴───────────────────────────────────────────────┘

String から Variant への変換時のパースを無効にするには、設定 cast_string_to_dynamic_use_inference を無効にします:

SET cast_string_to_variant_use_inference = 0;
SELECT '[1, 2, 3]'::Variant(String, Array(UInt64)) as variant, variantType(variant) as variant_type
┌─variant───┬─variant_type─┐
│ [1, 2, 3] │ String       │
└───────────┴──────────────┘

通常のカラムを Variant カラムに変換する

T の通常のカラムは、その型を含む Variant カラムに変換できます。

SELECT toTypeName(variant) AS type_name, [1,2,3]::Array(UInt64)::Variant(UInt64, String, Array(UInt64)) as variant, variantType(variant) as variant_name
┌─type_name──────────────────────────────┬─variant─┬─variant_name──┐
│ Variant(Array(UInt64), String, UInt64) │ [1,2,3] │ Array(UInt64) │
└────────────────────────────────────────┴─────────┴───────────────┘

注意: String 型からの変換は常にパースを通じて行われます。パースを行わずに String カラムを VariantString バリアントに変換する必要がある場合は、次のようにします。

SELECT '[1, 2, 3]'::Variant(String)::Variant(String, Array(UInt64), UInt64) as variant, variantType(variant) as variant_type
┌─variant───┬─variant_type─┐
│ [1, 2, 3] │ String       │
└───────────┴──────────────┘

Variantカラムを通常のカラムに変換する

Variantカラムは通常のカラムに変換できます。この場合、ネストされたすべての Variant は宛先の型に変換されます。

CREATE TABLE test (v Variant(UInt64, String)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('42.42');
SELECT v::Nullable(Float64) FROM test;
┌─CAST(v, 'Nullable(Float64)')─┐
│                         ᴺᵁᴸᴸ │
│                           42 │
│                        42.42 │
└──────────────────────────────┘

ある Variant を別の Variant に変換する

Variant カラムは別の Variant カラムに変換できますが、変換先の Variant カラムに変換元の Variant に含まれるすべてのネストされた型が含まれている場合に限られます。

CREATE TABLE test (v Variant(UInt64, String)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('String');
SELECT v::Variant(UInt64, String, Array(UInt64)) FROM test;
┌─CAST(v, 'Variant(UInt64, String, Array(UInt64))')─┐
│ ᴺᵁᴸᴸ                                              │
│ 42                                                │
│ String                                            │
└───────────────────────────────────────────────────┘

データからの Variant 型の読み込み

すべてのテキストフォーマット (TSV、CSV、CustomSeparated、Values、JSONEachRow など) で、Variant 型を読み込めます。データのパース時に、ClickHouse は値を最も適切な Variant 型に挿入しようとします。

例:

SELECT
    v,
    variantElement(v, 'String') AS str,
    variantElement(v, 'UInt64') AS num,
    variantElement(v, 'Float64') AS float,
    variantElement(v, 'DateTime') AS date,
    variantElement(v, 'Array(UInt64)') AS arr
FROM format(JSONEachRow, 'v Variant(String, UInt64, Float64, DateTime, Array(UInt64))', $$
{"v" : "Hello, World!"},
{"v" : 42},
{"v" : 42.42},
{"v" : "2020-01-01 00:00:00"},
{"v" : [1, 2, 3]}
$$)
┌─v───────────────────┬─str───────────┬──num─┬─float─┬────────────────date─┬─arr─────┐
│ Hello, World!       │ Hello, World! │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ []      │
│ 42                  │ ᴺᵁᴸᴸ          │   42 │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ []      │
│ 42.42               │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │ 42.42 │                ᴺᵁᴸᴸ │ []      │
│ 2020-01-01 00:00:00 │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │ 2020-01-01 00:00:00 │ []      │
│ [1,2,3]             │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ [1,2,3] │
└─────────────────────┴───────────────┴──────┴───────┴─────────────────────┴─────────┘

Variant 型の値の比較

Variant 型の値は、同じ Variant 型の値としか比較できません。

デフォルトでは、比較演算子は Variant のデフォルト実装 を使用し、 各バリアント型ごとに個別に比較を行います。これは、以下で説明するネイティブな Variant 比較ルールを使用するために、設定 use_variant_default_implementation_for_comparisons = 0 で無効にできます。: ORDER BY では常にネイティブな比較が使用されます。

ネイティブな Variant 比較ルール:

Variant(..., T1, ... T2, ...) において、内部型 T1 を持つ値 v1 と内部型 T2 を持つ値 v2 に対する演算子 < の結果は、次のように定義されます。

  • T1 = T2 = T の場合、結果は v1.T < v2.T です (内部値が比較されます) 。
  • T1 != T2 の場合、結果は T1 < T2 です (型名が比較されます) 。

例:

SET allow_suspicious_types_in_order_by = 1;
CREATE TABLE test (v1 Variant(String, UInt64, Array(UInt32)), v2 Variant(String, UInt64, Array(UInt32))) ENGINE=Memory;
INSERT INTO test VALUES (42, 42), (42, 43), (42, 'abc'), (42, [1, 2, 3]), (42, []), (42, NULL);
SELECT v2, variantType(v2) AS v2_type FROM test ORDER BY v2;
┌─v2──────┬─v2_type───────┐
│ []      │ Array(UInt32) │
│ [1,2,3] │ Array(UInt32) │
│ abc     │ String        │
│ 42      │ UInt64        │
│ 43      │ UInt64        │
│ ᴺᵁᴸᴸ    │ None          │
└─────────┴───────────────┘
SELECT v1, variantType(v1) AS v1_type, v2, variantType(v2) AS v2_type, v1 = v2, v1 < v2, v1 > v2 FROM test;
┌─v1─┬─v1_type─┬─v2──────┬─v2_type───────┬─equals(v1, v2)─┬─less(v1, v2)─┬─greater(v1, v2)─┐
│ 42 │ UInt64  │ 42      │ UInt64        │              1 │            0 │               0 │
│ 42 │ UInt64  │ 43      │ UInt64        │              0 │            1 │               0 │
│ 42 │ UInt64  │ abc     │ String        │              0 │            0 │               1 │
│ 42 │ UInt64  │ [1,2,3] │ Array(UInt32) │              0 │            0 │               1 │
│ 42 │ UInt64  │ []      │ Array(UInt32) │              0 │            0 │               1 │
│ 42 │ UInt64  │ ᴺᵁᴸᴸ    │ None          │              0 │            1 │               0 │
└────┴─────────┴─────────┴───────────────┴────────────────┴──────────────┴─────────────────┘

特定の Variant 値を持つ行を見つける必要がある場合は、次のいずれかの方法を使用できます。

  • 値を対応する Variant 型にCASTします:
SELECT * FROM test WHERE v2 == [1,2,3]::Array(UInt32)::Variant(String, UInt64, Array(UInt32));
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘
  • Variant のサブカラムを必要な型と比較します:
SELECT * FROM test WHERE v2.`Array(UInt32)` == [1,2,3] -- or using variantElement(v2, 'Array(UInt32)')
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘

場合によっては、バリアント型について追加で確認しておくと便利です。Array/Map/Tuple のような複合型の サブカラム は Nullable の中に含めることができないため、型が異なる行では NULL ではなくデフォルト値が入ります。

SELECT v2, v2.`Array(UInt32)`, variantType(v2) FROM test WHERE v2.`Array(UInt32)` == [];
┌─v2───┬─v2.Array(UInt32)─┬─variantType(v2)─┐
│ 42   │ []               │ UInt64          │
│ 43   │ []               │ UInt64          │
│ abc  │ []               │ String          │
│ []   │ []               │ Array(UInt32)   │
│ ᴺᵁᴸᴸ │ []               │ None            │
└──────┴──────────────────┴─────────────────┘
SELECT v2, v2.`Array(UInt32)`, variantType(v2) FROM test WHERE variantType(v2) == 'Array(UInt32)' AND v2.`Array(UInt32)` == [];
┌─v2─┬─v2.Array(UInt32)─┬─variantType(v2)─┐
│ [] │ []               │ Array(UInt32)   │
└────┴──────────────────┴─────────────────┘

注意: 数値型が異なるバリアントの値は別々のバリアントと見なされるため、相互には比較されず、代わりに型名が比較されます。

例:

SET allow_suspicious_variant_types = 1;
CREATE TABLE test (v Variant(UInt32, Int64)) ENGINE=Memory;
INSERT INTO test VALUES (1::UInt32), (1::Int64), (100::UInt32), (100::Int64);
SELECT v, variantType(v) FROM test ORDER by v;
┌─v───┬─variantType(v)─┐
│ 1   │ Int64          │
│ 100 │ Int64          │
│ 1   │ UInt32         │
│ 100 │ UInt32         │
└─────┴────────────────┘

注記 デフォルトでは、Variant 型は GROUP BY/ORDER BY キーで使用できません。使用する場合は、その特殊な比較ルールを考慮したうえで、allow_suspicious_types_in_group_by/allow_suspicious_types_in_order_by 設定を有効にしてください。

Variant を引数に取る JSONExtract 関数

すべての JSONExtract* 関数は Variant 型をサポートします。

SELECT JSONExtract('{"a" : [1, 2, 3]}', 'a', 'Variant(UInt32, String, Array(UInt32))') AS variant, variantType(variant) AS variant_type;
┌─variant─┬─variant_type──┐
│ [1,2,3] │ Array(UInt32) │
└─────────┴───────────────┘
SELECT JSONExtract('{"obj" : {"a" : 42, "b" : "Hello", "c" : [1,2,3]}}', 'obj', 'Map(String, Variant(UInt32, String, Array(UInt32)))') AS map_of_variants, mapApply((k, v) -> (k, variantType(v)), map_of_variants) AS map_of_variant_types
┌─map_of_variants──────────────────┬─map_of_variant_types────────────────────────────┐
│ {'a':42,'b':'Hello','c':[1,2,3]} │ {'a':'UInt32','b':'String','c':'Array(UInt32)'} │
└──────────────────────────────────┴─────────────────────────────────────────────────┘
SELECT JSONExtractKeysAndValues('{"a" : 42, "b" : "Hello", "c" : [1,2,3]}', 'Variant(UInt32, String, Array(UInt32))') AS variants, arrayMap(x -> (x.1, variantType(x.2)), variants) AS variant_types
┌─variants───────────────────────────────┬─variant_types─────────────────────────────────────────┐
│ [('a',42),('b','Hello'),('c',[1,2,3])] │ [('a','UInt32'),('b','String'),('c','Array(UInt32)')] │
└────────────────────────────────────────┴───────────────────────────────────────────────────────┘

Variant 引数を受け取る関数

ClickHouse のほとんどの関数は、Variant 用のデフォルト実装によって Variant 型の引数を自動的にサポートします。 バージョン 26.1 以降では、Variant 型を明示的に処理しない関数に Variant カラムが渡されると、ClickHouse は次のように動作します。

  1. Variant カラムから各バリアント型を抽出します
  2. 各バリアント型ごとに関数を個別に実行します
  3. 結果の型に応じて結果を適切に結合します

これにより、特別な処理をしなくても、通常の関数を Variant カラムに対して使用できます。

例:

CREATE TABLE test (v Variant(UInt32, String)) ENGINE = Memory;
INSERT INTO test VALUES (42), ('hello'), (NULL);
SELECT *, toTypeName(v) FROM test WHERE v = 42;
   ┌─v──┬─toTypeName(v)───────────┐
1. │ 42 │ Variant(String, UInt32) │
   └────┴─────────────────────────┘

比較演算子は各 Variant 型に個別に自動適用されるため、Variant カラムに対してフィルタリングを行えます。

結果型の挙動:

結果型は、関数が各 Variant に対して返す値によって決まります。

  • 結果型が異なる場合: Variant(T1, T2, ...)

    CREATE TABLE test2 (v Variant(UInt64, Float64)) ENGINE = Memory;
    INSERT INTO test2 VALUES (42::UInt64), (42.42);
    SELECT v + 1 AS result, toTypeName(result) FROM test2;
    
    ┌─result─┬─toTypeName(plus(v, 1))──┐
    │     43 │ Variant(Float64, UInt64) │
    │  43.42 │ Variant(Float64, UInt64) │
    └────────┴─────────────────────────┘
    
  • 型の非互換性: 互換性のない Variant では NULL

    CREATE TABLE test3 (v Variant(Array(UInt32), UInt32)) ENGINE = Memory;
    INSERT INTO test3 VALUES ([1,2,3]), (42);
    SELECT v + 10 AS result, toTypeName(result) FROM test3;
    
    ┌─result─┬─toTypeName(plus(v, 10))─┐
    │   ᴺᵁᴸᴸ │ Nullable(UInt64)        │
    │     52 │ Nullable(UInt64)        │
    └────────┴─────────────────────────┘
    
注記

エラー処理: 関数が Variant 型を処理できない場合、型関連のエラー (ILLEGAL_TYPE_OF_ARGUMENT, TYPE_MISMATCH, CANNOT_CONVERT_TYPE, NO_COMMON_TYPE) のみが捕捉され、該当する行の結果は NULL になります。ゼロ除算やメモリ不足などのその他のエラーは、実際の問題が見えなくなるのを防ぐため、通常どおり送出されます。

型不一致時の動作

設定 variant_throw_on_type_mismatch は、関数を Variant カラムに適用した際、各行に実際に格納されている型がその関数に対応していない場合の動作を制御します。

  • true (デフォルト) — 最初の非互換な行で例外を送出します (ILLEGAL_TYPE_OF_ARGUMENT) 。
  • false — 非互換な行では NULL を返し、互換性のある行の結果はそのまま返します。

例:

CREATE TABLE test (v Variant(String, UInt64)) ENGINE = Memory;
INSERT INTO test VALUES ('hello'), (42), ('foo');

-- Default (throw on mismatch): length() does not accept UInt64, so the query throws.
SELECT length(v) FROM test;  -- throws ILLEGAL_TYPE_OF_ARGUMENT

-- With throw disabled: incompatible rows return NULL.
SET variant_throw_on_type_mismatch = false;
SELECT v, length(v) FROM test ORDER BY v::String NULLS LAST;
┌─v─────┬─length(v)─┐
│ foo   │         3 │
│ hello │         5 │
│ 42    │      ᴺᵁᴸᴸ │
└───────┴───────────┘