このトピックでは、JSON データ型に対して検索インデックスを作成する方法について説明します。
前提条件
LindormTable エンジンはバージョン 2.8.5 以降である必要があります。現在のバージョンを確認またはアップグレードするには、「マイナーバージョンの更新」をご参照ください。
Lindorm LTS エンジンはバージョン 3.8.13.3 以降である必要があります。現在のバージョンを確認またはアップグレードするには、「マイナーバージョンの更新」をご参照ください。
注意事項
検索インデックスを作成する際には、JSON データのパターンに基づいてインデックスフィールドの型を指定する必要があります。主なパターンは次の 3 つです:
基本要素の配列
トップレベル要素が配列であり、配列内の各要素が基本要素であるパターンです。例として、
[1, 2, 3]のような数値配列や、["1", "2", "3"]のような文字列配列があります。JSON オブジェクト
トップレベル要素が JSON オブジェクトであるパターンです。例:
{"id": "GTY102289", "scores": [1.1, 2.9], "extra": {"A": "daew", "B": 88.99}}。オブジェクトの配列
トップレベル要素が配列であり、配列内の各要素が JSON オブジェクトであるパターンです。例:
[{"name": "Alice", "address": "XX Street"},{"name": "Bob"}]。
これら 3 つのパターンを混在させないでください。無効なデータはスキップされ、インデックスが作成されない場合があります。
使用方法
以降のセクションでは、基本要素の配列、JSON オブジェクト、オブジェクトの配列という 3 つのパターンについて、インデックスの作成方法と SQL クエリの実行方法を例とともに説明します。
基本要素の配列
ワイドテーブルの作成
CREATE TABLE test_json_array(id VARCHAR, c1 JSON, c2 JSON, PRIMARY KEY (id));データの書き込み
UPSERT INTO test_json_array(id, c1, c2) VALUES ('1001', '["101", "102", "109"]', '[1, 2, 3]');
UPSERT INTO test_json_array(id, c1, c2) VALUES ('1002', '["999", "888", "777"]', '[1, 2, 3, 4, 5]');検索インデックスの作成
JSON 列の c1 は文字列配列です。この列に検索インデックスを作成する際には、mapping='{"type": "keyword"}' を指定する必要があります。
JSON 列の c2 は整数配列です。この列に検索インデックスを作成する際には、mapping='{"type": "integer"}' を指定する必要があります。
CREATE INDEX idx USING SEARCH ON test_json_array(
c1(mapping='{
"type": "keyword"
}'),
c2(mapping='{
"type": "integer"
}')
);クエリメソッド
基本要素の配列に対して、SQL レイヤーは次の 2 つのクエリメソッドをサポートしています:
JSON_CONTAINS:すべての候補要素が指定された条件を満たす必要があります。JSON_CONTAINS_ANY:いずれかの候補要素が指定された条件を満たした場合に結果を返します。
JSON_CONTAINS クエリ
-- c1 列には "101" と "999" の両方は含まれていません。
Lindorm> SELECT * FROM test_json_array WHERE JSON_CONTAINS(c1, '["101", "999"]');
Empty set (0.11 sec)
-- c1 列には "101" と "102" の両方が含まれています。
Lindorm> SELECT * FROM test_json_array WHERE JSON_CONTAINS(c1, '["101", "102"]');
+------+-----------------------+-----------+
| id | c1 | c2 |
+------+-----------------------+-----------+
| 1001 | ["101", "102", "109"] | [1, 2, 3] |
+------+-----------------------+-----------+
1 row in set (0.01 sec)JSON_CONTAINS_ANY クエリ
-- json_contains_any は、いずれかの要素がヒットした場合に結果を返します。id=1001 の c1 列には "101" が、id=1002 の c1 列には "999" が含まれているため、両方の行が返されます。
Lindorm> SELECT * FROM test_json_array WHERE JSON_CONTAINS_ANY(c1, '["101", "999"]');
+------+-----------------------+---------------------+
| id | c1 | c2 |
+------+-----------------------+---------------------+
| 1002 | ["999", "888", "777"] | [1, 2, 3, 5, 9, 10] |
| 1001 | ["101", "102", "109"] | [1, 2, 3] |
+------+-----------------------+---------------------+
2 rows in set (0.01 sec)JSON オブジェクト
ワイドテーブルの作成
CREATE TABLE test_json_object(id VARCHAR, user_info JSON, PRIMARY KEY (id));データの書き込み
UPSERT INTO test_json_object(id, user_info) VALUES ('1001', '{"name": "Alice", "age": 13, "address": "Hangzhou, Zhejiang Province", "hobbies": ["play games", "read", "badminton"]}');
UPSERT INTO test_json_object(id, user_info) VALUES ('1002', '{"name": "Bob", "age": 9, "address": "Ningbo, Zhejiang Province", "hobbies": ["play games"]}');
UPSERT INTO test_json_object(id, user_info) VALUES ('1003', '{"name": "John", "age": 21, "address": "Shenzhen, Guangdong Province", "hobbies": ["read", "badminton", "food"]}');検索インデックスの作成
内部オブジェクトの型の依存関係を自動的に推論するか、事前に定義されたサブオブジェクト構造を使用することができます。
内部オブジェクトの自動的な型推論
JSON 列の user_info は JSON オブジェクトです。検索インデックスを作成する際に type=jsonobject を指定できます。これにより、システムは最初に書き込まれた値に基づいてフィールドの型を自動的に推論します。例えば、user_info.name は文字列型、user_info.age は数値型として推論されます。後続のデータが推論された型と一致しない場合、システムはインデックスの一貫性を保つために無効なデータをスキップします。
CREATE INDEX idx USING SEARCH ON test_json_object(user_info(type=jsonobject));サブオブジェクト構造の事前定義 (推奨)
自動的な型推論に依存すると、予期しない型になる可能性があります:
内部フィールドに対してトークン化されたクエリを実行するには、トークン化のためのフィールド型を明示的に指定する必要があります。
複雑な JSON オブジェクトにネストされたオブジェクトの配列が含まれている場合、フィールド型を nested として明示的に指定する必要があります。
また、mapping を使用して内部構造を指定し、必要なフィールド型を事前に定義することもできます。
mapping を使用して内部構造を指定する場合、内部フィールドの型は Elasticsearch の構文と互換性があります。
-- サブフィールドの型を事前定義
CREATE INDEX idx USING SEARCH ON test_json_object(
user_info(mapping='{
"type": "object",
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"address": {
"type":"text",
"analyzer": "ik_max_word"
},
"hobbies": {
"type": "keyword"
}
}
}')
);JSON オブジェクト内の一部のフィールドのみにインデックスを作成したい場合は、"dynamic": "false" パラメーターを追加して他のフィールドを無視することができます。
CREATE INDEX idx USING SEARCH ON test_json_object(
user_info(mapping='{
"type": "object",
"dynamic": "false",
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"address": {
"type": "text",
"analyzer": "ik_max_word"
},
"hobbies": {
"type": "keyword"
}
}
}')
);クエリメソッド
システムは JSON オブジェクトに対して、以下のクエリメソッドをサポートしています:
JSON_EXTRACT関数ファミリーを使用したクエリ。JSON オブジェクト内の基本要素配列の要素に対する
JSON_CONTAINSおよびJSON_CONTAINS_ANYを使用したクエリ。JSON_EXTRACTとMATCHを組み合わせたクエリ。
JSON_EXTRACT や JSON_CONTAINS を使用した完全なオブジェクトに対するマッチクエリはサポートされていないことに注意してください。例えば、WHERE JSON_EXTRACT(json_col, '$.user') = '{"name": "Alice", "age": 12}' のようなクエリはサポートされていません。現在、検索インデックスにおける JSON_EXTRACT 関数は、単一要素のマッチングのみをサポートしています。
JSON_EXTRACT クエリ
Lindorm> SELECT * FROM test_json_object WHERE JSON_EXTRACT_STRING(user_info, '$.name')='Alice';
+------+------------------------------------------------------------------------------------------------+
| id | user_info |
+------+------------------------------------------------------------------------------------------------+
| 1001 | {"name": "Alice", "age": 13, "address": "Hangzhou, Zhejiang Province", "hobbies": ["play games", "read", "badminton"]} |
+------+------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)
Lindorm> SELECT * FROM test_json_object WHERE JSON_EXTRACT(user_info, '$.age')=21;
+------+--------------------------------------------------------------------------------------------------------+
| id | user_info |
+------+--------------------------------------------------------------------------------------------------------+
| 1003 | {"name": "John", "age": 21, "address": "Shenzhen, Guangdong Province", "hobbies": ["read", "badminton", "food"]} |
+------+--------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)JSON_EXTRACT と MATCH の組み合わせクエリ
JSON サブフィールドの型を text として事前に定義する必要があります。 デフォルトで推論される型は keyword であり、トークン化されたクエリをサポートしていません。
Lindorm> SELECT * FROM test_json_object WHERE MATCH(JSON_EXTRACT(user_info, '$.address')) AGAINST ('Zhejiang');
+------+---------------------------------------------------------------+
| id | user_info |
+------+---------------------------------------------------------------+
| 1001 | {"name": "Alice", "age": 13, "address": "Hangzhou, Zhejiang", "hobbies": ["play games", "read", "badminton"]} |
| 1002 | {"name": "Bob", "age": 9, "address": "Ningbo, Zhejiang", "hobbies": ["play games"]} |
+------+---------------------------------------------------------------+
2 rows in set (0.03 sec)
Lindorm> SELECT * FROM test_json_object WHERE MATCH(JSON_EXTRACT(user_info, '$.address')) AGAINST ('Hangzhou');
+------+---------------------------------------------------------------+
| id | user_info |
+------+---------------------------------------------------------------+
| 1001 | {"name": "Alice", "age": 13, "address": "Hangzhou, Zhejiang", "hobbies": ["play games", "read", "badminton"]} |
+------+---------------------------------------------------------------+
1 row in set (0.01 sec)指定パスでの JSON_CONTAINS クエリの実行
Lindorm> SELECT * FROM test_json_object WHERE JSON_CONTAINS(user_info, '["read"]', '$.hobbies');
+------+---------------------------------------------------------------------------------------------------------------+
| id | user_info |
+------+---------------------------------------------------------------------------------------------------------------+
| 1003 | {"name": "John", "age": 21, "address": "Shenzhen, Guangdong Province", "hobbies": ["read", "badminton", "food"]} |
| 1001 | {"name": "Alice", "age": 13, "address": "Hangzhou, Zhejiang Province", "hobbies": ["play games", "read", "badminton"]} |
+------+---------------------------------------------------------------------------------------------------------------+
2 rows in set (0.04 sec)オブジェクトの配列
ワイドテーブルの作成
CREATE TABLE test_json_object_array(id VARCHAR, user JSON, primary key(id));データの書き込み
UPSERT INTO test_json_object_array(id, user) VALUES ('1001', '[{"name": "Alice", "age": 12}]');
UPSERT INTO test_json_object_array(id, user) VALUES ('1002', '[{"name": "Alice", "age": 9},{"name": "Bob", "age": 20}]');
UPSERT INTO test_json_object_array(id, user) VALUES ('1003', '[{"name": "Alice", "age": 12},{"name": "Bob", "age": 20}]');検索インデックスの作成
内部オブジェクトの型の依存関係を自動的に推論するか、事前に定義されたサブオブジェクト構造を使用する 2 つの方法があります。
内部オブジェクトの自動的な型推論
JSON 列の user はオブジェクトの配列です。検索インデックスを作成する際に type=jsonarray を指定できます。これにより、システムは配列内の各オブジェクトについて、最初に書き込まれた値に基づいてフィールドの型を自動的に推論します。例えば、user.name は文字列型、user.age は数値型として推論されます。後続のデータが推論された型と一致しない場合、システムはインデックスの一貫性を保つために無効なデータをスキップします。
CREATE INDEX idx USING SEARCH ON test_json_object_array(user(type=jsonarray));サブオブジェクト構造の事前定義 (推奨)
自動的な型推論に依存すると、予期しない型になる可能性があります:
内部フィールドに対してトークン化されたクエリを実行するには、トークン化のためのフィールド型を明示的に指定する必要があります。
複雑な JSON オブジェクトにネストされたオブジェクトの配列が含まれている場合、フィールド型を nested として明示的に指定する必要があります。
mapping を使用して内部構造を指定し、必要なフィールド型を事前に定義することができます。
mapping を使用して内部構造を指定する場合、内部フィールドの型は Elasticsearch の構文と互換性があります。
CREATE INDEX idx USING SEARCH ON test_json_object_array(
user(mapping='{
"type": "nested",
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
}
}
}')
);JSON オブジェクト内の一部のフィールドのみにインデックスを作成したい場合は、"dynamic": "false" パラメーターを追加して他のフィールドを無視することができます。
CREATE INDEX idx USING SEARCH ON test_json_object_array(
user(mapping='{
"type": "nested",
"dynamic": "false",
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
}
}
}')
);クエリメソッド
SEARCH_QUERY 関数を使用してのみ、オブジェクトの配列をクエリできます。次の例に示すように、SQL クエリ内に Elasticsearch DSL 構文を埋め込む必要があります。
-- user.name が Alice で、かつ user.age が 10 以上の行をクエリします。
Lindorm> SELECT * FROM test_json_object_array WHERE SEARCH_QUERY('
{
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{ "match": { "user.name": "Alice" } },
{ "range": { "user.age": {"gte": 10} } }
]
}
}
}
}
');
+------+-----------------------------------------------------------+
| id | user |
+------+-----------------------------------------------------------+
| 1003 | [{"name": "Alice", "age": 12},{"name": "Bob", "age": 20}] |
| 1001 | [{"name": "Alice", "age": 12}] |
+------+-----------------------------------------------------------+