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" } } } ]
変更後
以下のようにドロップダウンのアイコンが変更されます。
