ElasticSearchジョイン: Has_Child、Has_parentクエリ

ElasticSearchジョイン: Has_Child、Has_parentクエリ

  • オーケストレーション
5分で読めます 更新日 Dec 6, 2018

ポイント: ElasticSearchは、リレーショナルデータベースの結合に似たドキュメントの関連付けを管理するために親子関係を利用します。このガイドでは、これらの関係のためのマッピングとクエリの作成方法を説明し、従来の結合を制限するElasticSearchの速度重視のアーキテクチャを強調しています。主な例として、大学と学生のデータを作成し、親ドキュメントと子ドキュメントのセットアップを示し、親IDによって子レコードをクエリする方法を示しています。

  • ElasticSearchはリレーショナルデータベースではなく、従来の結合を避けます。
  • 親子関係は特定のマッピングとクエリを通じて確立されます。

再び、ElasticSearchの複雑さと時には矛盾するドキュメントを扱い、それを理解しやすくすることを目指しています。ここでは、ドキュメント間の親子関係について見ていきます。

概念 親、子、およびジョイン

リレーショナルデータベースでは、親子関係はジョインと呼ばれます。数学者はそれを2つの集合の交差点と呼ぶでしょう。例えば、奇数と素数の交差点を見つけることができます。つまり、素数でない奇数の集合を作成します。

データベースの観点からは、それが親子関係で表現されます。そのためには、いくつかの共通要素が必要です。

例を示すために、いくつかの奇数といくつかの素数の集合があるとしましょう。次に、2つの集合の交差点、つまり両方に存在する要素の集合を作成します。データベースのジョインは同じことです。

親(いくつかの奇数)
{9,21}
子供(いくつかの素数)
{7,11, 21}
交差点(奇数∩素数)
{21}

この場合、どの集合を親または子と見なすかは関係ありません。これは単なる数字だからです。

大学と学生を使用して、ElasticSearchでこれを示します。大学と学生のセットを作成し、各大学に学生を割り当てます。

したがって、次のようになります:

親(大学)
{ハーバード、クレムソン}
子供(学生)
{{"学生:" ウォーカー",
"大学": "クレムソン},
{学生: ステファン",
"大学": "ハーバード"}}
交差点
(クレムソンに通う学生)
{"学生:" ウォーカー",
"大学": "クレムソン}

子供は親を指しています。なぜなら、大学名が子レコードに含まれているからです。それが共通要素です。ElasticSearchでは、インデックスマッピングでこれを次のように示します:

    "universities_students" : {
          "type" : "join",
            "eager_global_ordinals" : true,
            "relations" : {
              "universities" : "students"
          }
     }

これにより、universities_studentsという名前の新しいフィールドが作成され、そのタイプはjoinです。親はuniversitiesで、子はstudentsです。

学生(子)レコードを書き込むときは、親の_idを指定し、すなわち'parent': 'e2cfb3b015d4be50a466aa593ab4d9f490982ede'で親ドキュメントのタイプをstudentsと指定します。

    'universities_students': {
'name': 'students',
 'parent': 'e2cfb3b015d4be50a466aa593ab4d9f490982ede'
}

大学(親)レコードを書き込むときは、それが大学のタイプのドキュメントであることを示します。そのため、_idは指定しません。なぜなら、それは論理的ではないからです。クエリはその関係を把握します。

    'universities_students': {
'name': 'universities'
}

ElasticSearchはデータベースではない

ElasticSearchは、それがリレーショナルデータベースではないと指摘しています。したがって、従来のジョインを行うことはできません。なぜなら、それは非常に遅くなるからです。ESは速度を重視しています。そのため、両方のドキュメントタイプは同じインデックスの同じシャードに存在する必要があります。これにより、同じディスクドライブ上で近くに配置され、シーク時間が短縮されます。また、このルールを追加しても、ドキュメントを追加するとクエリが遅くなることに注意してください。

最初にマッピングタイプを作成

これを例で示します。最初にマッピングタイプを作成します。このマッピングタイプは、大学と学生の両方を含むことができます。これは、フィールドのリストが両方の一般的なフィールドのスーパーセットだからです。つまり、学生にはクラスと成績がありますが、大学にはありません。同様に、学校にはAdminEmail、学校名などがあります。

最後に、フィールドuniversities_studentsをリストして、タイプjoinのフィールドを持つことに注意してください。これはドキュメント間の関係を表現します。

(大学のレコードには学校名のみを入力します。他のフィールド、電話番号などは無視できます。これらはこの以前の例でネストされたクエリを行う方法に関するもので、関連するものの、異なるトピックです。)

    curl -XPUT --header 'Content-Type: application/json' http://parisx:9200/universities -d '{
"settings" : {
        "index" : {
            "number_of_shards" : 1, 
            "number_of_replicas" : 1 
        }
    },    "mappings" : {
      "doc" : {
        "properties" : {
          "Address" : { "type" : "text"},
          "AdminEmail" : { "type" : "text"},
          "AdminName" : { "type" : "text" },
          "AdminPhone" : { "type" : "text" },
          "DapipId" : { "type" : "text" },
          "Fax" : { "type" : "text" },
          "GeneralPhone" : { "type" : "text" },
          "LocationName" : { "type" : "text" },
          "LocationType" : { "type" : "text" },
          "OpeId" : { "type" : "text" },
          "ParentDapipId" : { "type" : "text" },
          "ParentName" :  { "type" : "text" },
          "UpdateDate" : { "type" : "text" },
          "classes" : {
                "type" : "nested",
                "properties" : {
                   "name" : { "type" : "text"},
                   "grades" : { "type" : "integer" }
              }
            },
        "firstName" : { "type" : "text" },
        "lastName" : { "type" : "text" },
        "school" : { "type" : "text" },
          "universities_students" : {
          "type" : "join",
            "eager_global_ordinals" : true,
            "relations" : {
              "universities" : "students"
          }
          }
        }
        }
}
}'

データを作成

このコードをダウンロードして実行します。簡単にするために、1つの学校と1人の学生のみを作成します。これを試してみたい場合は、range(1,2)をrange(1,400)のように変更します。

curlを使用してデータを作成できますが、ESのドキュメントにある例のように、Pythonを使用する方がはるかに簡単です。なぜなら、Pythonの辞書で作業する方が、特にESドキュメントが長く、オブジェクト内に多くのオブジェクトを持つ場合、JSONオブジェクトよりも簡単だからです。

以下の大学と学生のレコードを見てください。実行すると、ランダムに学校を選択するため、異なるデータが生成されます。次のクエリで子のレコードを見つけるために、大学レコードの_idをメモする必要があります。

親(大学)

    {'school': 'University of South Carolina - Columbia', 'universities_students': {'name': 'universities'}}
{'_version': 1, '_type': 'doc', '_id': 'e2cfb3b015d4be50a466aa593ab4d9f490982ede', '_primary_term': 1, 'result': 'created', '_index': 'universities', '_seq_no': 0, '_shards': {'failed': 0, 'total': 2, 'successful': 2}}

子(学生)

    {'school': 'University of North Carolina at Chapel Hill', 'universities_students': {'name': 'students', 'parent': 'e2cfb3b015d4be50a466aa593ab4d9f490982ede'}, 'lastName': 'Mann', 'classes': [{'name': 'French', 'grades': 6}, {'name': 'French', 'grades': 3}, {'name': 'French', 'grades': 2}, {'name': 'math', 'grades': 4}], 'firstName': 'Walker'}

ここに完全なコードがあります。その下に、自己明白でない部分を説明します。これは、ランダムな数に基づいて3つの学校のうちの1つを選択するだけです。名前とクラスも同じアプローチを使用して選択します。そして、上記で説明したように大学と学生のレコードを追加します。

基本的なアプローチは、辞書{}を作成し、そのフィールドを追加することです。辞書にフィールドを追加して、ネストされたJSONオブジェクトを作成できます。したがって、長いJSON文字列フィールドで作業するよりも簡単です。

    import requests
import random
import hashlib
import json

url="http://parisx:9200/universities/doc/" 

def assignClasses():
    classes=[]
    for x in range(random.randrange(1,6)):
      classes.append(genclass())
    return classes 
      
schools = ("Arizona State University", "University of South Carolina - Columbia", "University of North Carolina at Chapel Hill") 
firstNames = ("Walker", "Stephen", "Julie", "George")
lastNames = ("Rowe", "Shakespeare", "Mann", "Sarte")
courses= ("math", "physics", "French", "logic")

def genclass():
    classes={}
    classes["name"]=courses[random.randrange(0,3)]
    classes["grades"] = random.randrange(1,7)
    return classes

def genParent(id):
    child={}
    child['name'] = 'students'
    child['parent'] = id
    return child

def genSchool():
    school={}
    school["universities_students"] = {'name': 'universities'}
    school["school"]=schools[random.randrange(0,3)]
    m = hashlib.sha1()
    m.update(bytes(json.dumps(school), 'utf-8'))
    id = m.hexdigest()
    print(school)
    response = requests.post(url + id, json=school)
    print (response.json())
    return id

def genStudent():
    id = genSchool()
    students={}
    students["universities_students"] = genParent(id)
    students["school"]=schools[random.randrange(0,3)]
    students["firstName"] = firstNames[random.randrange(0,3)]
    students["lastName"] = lastNames[random.randrange(0,3)]
    students["classes"] = assignClasses()
    m = hashlib.sha1()
    m.update(bytes(json.dumps(students), 'utf-8'))
    sid = m.hexdigest()

    print(students)
    response = requests.post(url + sid + "?routing=1&refresh", json=students)
    print (response.json())

for r in range(1,2):
    genStudent()
url="http://parisx:9200
/universities/doc/"
インデックス/タイプはElasticSearchの中で最も混乱を招く部分の1つです。彼らはドキュメントタイプフィールドを削除したと言っていますが、データを書き込むときにはインデックス/タイプで使用するように求められます。これは矛盾です。この場合、それはdocであり、universities/docです。
m = hashlib.sha1()
m.update(bytes(json.
dumps
(school),'utf-8'))
id = m.hexdigest()
すべてのElasticSearchドキュメントには一意のIDが必要です。同じドキュメントIDを2回書き込むと、1つがもう1つを消去します。一般的なアプローチは、全体のJSONドキュメントに対してSHA1ダイジェストを計算することです。もちろん、2つのドキュメントが同じIDを持つこともあります。uuid.UUIDのようなものを使用することもできます。
response = requests.post
(url + sid
+ "?routing=1&refresh",
json=students)
子ドキュメントを追加する際には、親ドキュメントと同じシャードに配置する必要があるため、どのシャードを使用するかを指定する必要があります。(シャードはインデックスの分割です。クラスタ内のノードにデータを分散させるために設計されています。これは制限ではありません。1つのシャードよりも多くのシャードを持つことができます。その場合は、親にもルーティングコマンドを指定し、リストを巡回している間に同じシャード番号を使用する必要があります。)インデックスマップを作成する際に、ESにいくつのシャードを作成するかを伝えました:
"index" : { "number_of_shards" : 1,  "number_of_replicas" : 1  }

子供を見つける

データを作成しましたので、いくつかのクエリを実行できます。

最初のクエリは、親を指定して子のレコードを見つけます。ここでのキーワードはparent_idです。探しているオブジェクトのタイプは「type": "students"です。学校を探すために、学校の"id": "e2cfb3b015d4be50a466aa593ab4d9f490982ede"を指定します。

    curl -XGET --header 'Content-Type: application/json' http://parisx:9200/universities/_search?pretty -d '{
  "query": {
    "parent_id": {
      "type": "students",
      "id": "e2cfb3b015d4be50a466aa593ab4d9f490982ede"
    }
  }
}'

このクエリはこの学生をリストします(1人の学生しか追加していないことを思い出してください)。

    {
        "_index" : "universities",
        "_type" : "doc",
        "_id" : "185fa8736085fd07ce6f5fb8f1c66fce64fa42e3",
        "_score" : 0.18232156,
        "_routing" : "1",
        "_source" : {
          "classes" : [
            {
              "name" : "French",
              "grades" : 1
            }
          ],
          "school" : "University of South Carolina - Columbia",
          "lastName" : "Mann",
          "universities_students" : {
            "name" : "students",
            "parent" : "e2cfb3b015d4be50a466aa593ab4d9f490982ede"
          },
          "firstName" : "Walker"
        }
      }

親を見つける

子供を指すフィールドは親に存在しないため、単に子供のIDをクエリに入れることはできません。それは論理的ではないからです。子供のIDを知っているなら、なぜ親に言及する必要があるのでしょうか。代わりに、子供を持つすべての親ドキュメントをリストできます。

クエリパラメータに何かを指定する必要があるため、existsクエリパラメータを使用します。

    curl -XGET --header 'Content-Type: application/json' http://parisx:9200/universities/_search?pretty -d '{
    "query": {
        "has_child" : {
            "type" : "students",
            "query" : {
                  "exists" : {"field": "firstName"}
        }
    }
}
}'

これにより、学校(親)のレコードがリストされます:

    {
        "_index" : "universities",
        "_type" : "doc",
        "_id" : "e2cfb3b015d4be50a466aa593ab4d9f490982ede",
        "_score" : 1.0,
        "_source" : {
          "school" : "University of South Carolina - Columbia",
          "universities_students" : {
            "name" : "universities"
          }
 }

これらの投稿は私自身の意見であり、BMCの立場、戦略、または意見を必ずしも代表するものではありません。

オーケストレーションの関連記事

The Briefを購読する

このオファーに登録することで、あなたはBMCのプライバシーポリシーを読み、同意したことを確認します。

ありがとう

あなたはThe Briefに登録されました
The Briefはメインフレームとエンタープライズオーケストレーション—BMC AMIとControl-M—および、両者に関するアナリストの研究、顧客の展開、イベントをカバーしています。