Elasticsearch Joinデータ型で親子データの定義とクエリ
Joinデータ型を利用することで、同一インデックス内に親子関係を持ったドキュメントを作成することができます。
ユースケースとしては、1つのエンティティが他のエンティティを大幅に上回っている1対多の関係がデータに含まれている場合と公式ページに記載されています。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/parent-join.html
また、クエリの際は、パフォーマンス的な負担が大きいと記載されているので、注意が必要です。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/parent-join.html#_parent_join_and_performance
実行環境
- Elasticsearch
- 7.4.2
- Kibana
- 7.4.2
定義
フィールド名をjoin_field
として、Joinデータ型を定義してみます。relations
内で指定している、question
とanswer
はそれぞれ親ドキュメント名、子ドキュメント名を任意の名前を指定することができます。
PUT /join_test { "mappings": { "properties": { "join_field": { "type": "join", "relations": { "question": "answer" } } } } }
インデクシング
Joinデータ型を定義したインデックスに親ドキュメント(質問)、子ドキュメント(解答)をインデクシングする。
親ドキュメント
Joinデータ型で指定した、親ドキュメント名(今回の例だとquestion
)を指定してインデクシングする。また、name
の指定は省略することができるので、以下の2パターンは同じ意味となる。
PUT /join_test/_doc/1 { "question_text": "好きな野球チームは?", "join_field": { "name": "question" } } PUT /join_test/_doc/2 { "question_text": "好きなサッカーチームは?", "join_field": "question" }
子ドキュメント
Joinデータ型で指定した、子ドキュメント名(今回の例だとanswer
)を指定してインデクシングする。以下の例だと1つ目の質問の子ドキュメント(解答)を2つインデクシングしている。
PUT /join_test/_doc/3?routing=1 { "answer_text": "日ハム", "join_field": { "name": "answer", "parent": "1" } } PUT /join_test/_doc/4?routing=1 { "answer_text": "巨人", "join_field": { "name": "answer", "parent": "1" } }
join_field
のname
- 定義で指定した子ドキュメント名
answer
- 定義で指定した子ドキュメント名
join_field
のparent
- 親ドキュメントの
ID
- 親ドキュメントの
routing
- 子ドキュメントと親ドキュメントは同じシャードに保存する必要がある
- どこのシャードに保存されるか?は、
routing
で指定することができる routing
を省略した場合は、ID
となる
クエリ
上記でインデクシングしたデータを取得する。親子関係というと、親レコードに子レコードリストがぶら下がっている事を想像してしまいますが、親ドキュメントと子ドキュメントは同じレベルのドキュメントとしてフラットになっていることを意識する必要がある。
✗こっちじゃない - 親1 - 子1 - 子2 - 親2 - 子1 ○こっち - 親1 - 親1の子1 - 親1の子2 - 親2 - 親2子1
親ドキュメントのみ取得
GET /join_test/_search { "query": { "term": { "join_field": "question" } } }
子ドキュメントのみ取得
GET /join_test/_search { "query": { "term": { "join_field": "answer" } } }
子ドキュメントの条件に合致する親ドキュメントを取得
answer_text
が日ハム
の子ドキュメントを持つ親ドキュメントリストを取得する。inner_hits
を付与することで、条件に合致する子ドキュメントもレスポンスとして含まれる。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-has-child-query.html
GET /join_test/_search { "query": { "has_child": { "type": "answer", "query": { "term": { "answer_text.keyword": "日ハム" } }, "inner_hits": {} } } }
親ドキュメントが同じ子ドキュメントを取得
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-parent-id-query.html https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-has-parent-query.html
GET /join_test/_search { "query": { "parent_id": { "type": "answer", "id": "1" } } } GET /join_test/_search { "query": { "has_parent": { "parent_type" : "question", "query": { "term": { "question_text.keyword": "好きな野球チームは?" } } } } }
Typescriptでオブジェクトのnullやundefinedの扱い方
なにも考慮せずに書くと、以下のコメント部分のようにコンパイラに怒られる。
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return { hoge: "hogehoge" }; } const sampleObject = getObject(); console.log(sampleObject.hoge); // Object is possibly 'null' or 'undefined'.
ifでNullチェック
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return { hoge: "hogehoge" }; } const sampleObject = getObject(); if (sampleObject != null) { console.log(sampleObject.hoge); } // "hogehoge"
Optional Chainingの?
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return { hoge: "hogehoge" }; } const sampleObject = getObject(); console.log(sampleObject?.hoge); // "hogehoge"
null または undefinedの場合
null
、undefined
いずれの場合も結果はundefined
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return null; } const sampleObject = getObject(); console.log(sampleObject?.hoge); // undefined
fooを参照する場合
Nullish Coalescing
と組み合わせると設定されていない場合の初期値などを設定できる。
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return { hoge: "hogehoge", foo: 1 }; } const sampleObject = getObject(); console.log(sampleObject?.foo ?? 10); // 1
type nullableObject = null | undefined | { hoge: string, foo?: Number }; const getObject = (): nullableObject => { return null; } const sampleObject = getObject(); console.log(sampleObject?.foo ?? 10); // 10
Elasticsearch 基本的なQuery
KibanaのDev Tools上のconsoleから実行できます。
- Elasticsearch
- 7.4.2
- Kibana
- 7.4.2
Term-level queries
検索キーワードに完全一致するフィールドを検索する際に利用するクエリです。文字列の完全一致、日付の範囲、数値の検索などに利用できそうです。
また、文字列の完全一致に利用する場合は、keyword
型でフィールドを作成します。
text型ではアナライザによって単語の分割が行われて転置インデックスが形成されるが、keyword型では、アナライザによる単語の分割が行われません。
term
指定されたフィールドの値と完全一致するドキュメントを取得する https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-term-query.html
GET /blog_test/_search { "query": { "term": { "id": { "value": "id1" } } } }
terms
term
クエリと同様の検索で、valueを複数指定して完全一致するドキュメントを取得する。SQLのIN句のイメージでしょうか。なお、デフォルトの最大terms数は、65,536
個とのことです。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-terms-query.html
GET /blog_test/_search { "query": { "terms": { "id": ["id1", "id2"] } } }
range
指定された範囲内の値を持つドキュメントを取得する https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-range-query.html
GET /blog_test/_search { "query": { "range": { "age": { "gte": 10, "lte": 20 } } } }
この例だとage
が10以上20以下を取得するクエリとなる。なお、大小関係の表現としては以下を利用することができる。
- gt
- 〜より大きい
- gte
- 〜以上
- lt
- 〜より小さい
- lte
- 〜以下
ids
指定されたドキュメントIDに合致するドキュメントを取得する。 https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-ids-query.html
GET /blog_test/_search { "query": { "ids": { "values": ["1", "3"] } } }
prefix
指定されたプレフィックスを含むドキュメントを取得する
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-prefix-query.html
GET /blog_test/_search { "query": { "prefix": { "id": { "value": "id" } } } }
wildcard
指定されたワイルドカードパターンに一致するドキュメントを取得する。SQLのLike検索のイメージでしょうか。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-wildcard-query.html
GET /blog_test/_search { "query": { "wildcard": { "id": { "value": "id*" } } } }
Full text queries
検索キーワードで全文検索を実行します。転置インデックスに対しての検索となるので、検索対象のフィールドはtext
型で作成します。
なお、指定されたキーワードもインデクシング時同様に分割(Analyzer)されて一致条件に利用されます。
match
指定されたキーワードに一致するドキュメントを取得する。オペレーター(ANDとかOR)を変更したり、 キーワードのAnalyzerをインデクシング時と異なるものを指定することもできる。詳細は以下のドキュメントを参照。 https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-match-query.html
GET /blog_test/_search { "query": { "match": { "name": { "query": "名前 太郎" } } } }
名前
または太郎
がname
フィールドに含まれるドキュメントが取得される。
match phrase
指定されたキーワードが、指定された語順に一致するドキュメントを取得する。
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-match-query-phrase.html
GET /blog_test/_search { "query": { "match_phrase": { "name": { "query": "名前 太郎" } } } }
名前
、太郎
の語順でname
フィールドに一致するドキュメントが取得される。
Elasticsearch 基本的なCRUD操作
KibanaのDev Tools上のconsoleから実行できます。
- Elasticsearch
- 7.4.2
- Kibana
- 7.4.2
登録
ドキュメントIDを指定して登録
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
PUT /blog_test/_doc/1 { "id": "id1", "name": "名前 太郎" }
ドキュメントIDを自動生成して(指定しないで)登録
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
POST /blog_test/_doc { "id": "id2", "name": "名前 花子" }
取得・検索
ドキュメントIDを指定して取得
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html
GET /blog_test/_doc/1
条件を指定して検索
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
GET /blog_test/_search { "query": { "match": { "name": "太郎" } } }
更新
ドキュメント全体を置き換える更新
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
PUT /blog_test/_doc/1 { "id": "id_update", "name": "名前 更新" }
ドキュメントの一部を更新
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
POST /blog_test/_update/1 { "doc": { "name": "部分 更新" } }
削除
ドキュメントの削除
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html
DELETE /blog_test/_doc/1
Javascriptで配列を便利に操作
配列の中がオブジェクトで構成されているような配列を操作したい時のTipsです。
const array = [ {id: "id1", name: "hoge"}, {id: "id2", name: "foo"}, ];
nameだけ取得する
const array = [ {id: "id1", name: "hoge"}, {id: "id2", name: "foo"}, ]; const names = array.map((currentValue, index, array) => { // currentValueは、配列内のアイテム return currentValue.name; }); console.log(names); // ["hoge", "foo"]
nameだけの配列から重複を削除する
const array = ["hoge", "foo", "foo2", "hoge"]; const newArray = Array.from(new Set(array)); console.log(array); // ["hoge", "foo", "foo2", "hoge"] console.log(newArray); // ["hoge", "foo", "foo2"]
idがid1
のアイテムだけ取得する
const array = [ {id: "id1", name: "hoge"}, {id: "id2", name: "foo"}, ]; const id1 = array.find((element, index, array) => { // elementは、配列内のアイテム return element.id === "id1"; }); console.log(id1); // {id: "id1", name: "hoge"}
idがid1
のアイテムだけ配列で取得する
1つの特定のオブジェクトを取得する場合は、上記のfind
のほうが良いかと思います。
const array = [ {id: "id1", name: "hoge"}, {id: "id2", name: "foo"}, ]; const id1 = array.filter((currentValue, index, array) => { // currentValueは、配列内のアイテム return currentValue.id === "id1"; }); console.log(id1); // [{id: "id1", name: "hoge"}]
idがid1
のアイテムが存在するか?
const array = [ {id: "id1", name: "hoge"}, {id: "id2", name: "foo"}, ]; const isExists = array.some((element, index, array) => { // elementは、配列内のアイテム return element.id === "id1"; }); console.log(isExists); // true
Javascriptでオブジェクトを結合・マージする
Javascriptでオブジェクトを結合する方法をいくつかメモっておきます。個人的には、簡潔に書けるのでスプレッド構文がオススメ。
Object.assign
const object1 = { A: "objectA", B: "objectB" }; const object2 = { C: "objectC", D: "objectD" }; console.log(Object.assign(object1, object2)); // → {A: "objectA", B: "objectB", C: "objectC", D: "objectD"}
Object.assign()
でマージされるが、最初に指定されたオブジェクト(例だとobject1
)も変更される
ので注意が必要。
キーが重複している場合は、あとに指定されているオブジェクト(例だとobject2
)が優先される。
上記の破壊的変更を防ぐには、空のオブジェクトを指定することで回避可能。
const object1 = { A: "objectA", B: "objectB" }; const object2 = { C: "objectC", D: "objectD" }; console.log(Object.assign({}, object1, object2)); // 最初の引数に空オブジェクトを指定する // → {A: "objectA", B: "objectB", C: "objectC", D: "objectD"}
スプレッド構文
const object1 = { A: "objectA", B: "objectB" }; const object2 = { C: "objectC", D: "objectD" }; console.log({...object1, ...object2}); // → {A: "objectA", B: "objectB", C: "objectC", D: "objectD"}
指定したオブジェクトの破壊的変更はなし。キーが重複していた場合は、あとに指定されているオブジェクト(例だとobject2
)が優先される。
Vuetifyのv-selectのドロップダウンアイコンを変更する方法
Vuetifyのv-selectコンポーネントを利用する際に、デフォルトだと以下のようなアイコンです。
このアイコンを別のアイコンに変更する方法を2パターン試してみました。
対象のコンポーネントのみ変更する
v-selectコンポーネントのappend-icon
プロパティに変更したいアイコンを指定します。
ちなみにデフォルトはmdi-menu-down
というアイコンが設定されています。
以下の通り実装することで、対象のコンポーネントのアイコンのみ指定されたアイコンに変更されます。
<v-select append-icon="mdi-chevron-down">
v-selectすべてを変更する
一部のコンポーネントのみ変更するよりも、サイト全体として変更することのほうが多いのかもしれません。 そんな時は、v-selectが利用しているのデフォルトのアイコン設定を変更します。 iconsのvaluesに変更したいアイコンを指定することで変更することができます。
// src/plugins/vuetify.js import Vue from 'vue' import Vuetify from 'vuetify/lib' Vue.use(Vuetify) export default new Vuetify({ icons: { iconfont: 'mdi', values: { dropdown: 'mdi-chevron-down' }, }, })
nuxt.jsのbuildModules
を使って指定する場合は、以下のように指定することができます。
// nuxt.config.js [ "@nuxtjs/vuetify", icons: { iconfont: "mdi", // default values: { dropdown: "mdi-chevron-down" } } } ]
変更後
以下のようにドロップダウンのアイコンが変更されます。