Indexing/Searching "complex" JSON 在弹性搜索中
Indexing/Searching "complex" JSON in elasticsearch
我有一些 JSON 如下所示:我们将该字段称为 metadata
{
"somekey1": "val1",
"someotherkey2": "val2",
"more_data": {
"contains_more": [
{
"foo": "val5",
"bar": "val6"
},
{
"foo": "val66",
"baz": "val44"
},
],
"even_more": {
"foz" : 1234,
}
}
}
这只是一个简单的例子。真实的可以变得更加复杂。
键可以出现多次。值也可以是 int 或 str.
现在第一个问题是我不太确定我必须如何在 elasticsearch 中正确索引它以便我可以找到具有特定请求的内容。
我正在使用 Django/Haystack,索引如下所示:
class FooIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
metadata = indexes.CharField(model_attr='get_metadata')
# and some more specific fields
和模板:
{
"foo": {{ object.foo }},
"metadata": {{ object.metadata}},
# and some more
}
然后元数据将被上面的示例填充,结果将如下所示:
{
"foo": "someValue",
"metadata": {
"somekey1": "val1",
"someotherkey2": "val2",
"more_data": {
"contains_more": [
{
"foo": "val5",
"bar": "val6"
},
{
"foo": "val66",
"baz": "val44"
},
],
"even_more": {
"foz" : 1234,
}
}
},
}
这将进入 elasticsearch 中的 'text' 列。
所以现在的目标是能够搜索以下内容:
- foo: val5
- foz: 12*
- bar: val*
- somekey1: val1
- 等等
第二个问题:
当我搜索时对于 foo: val5,它匹配仅具有键 "foo" 的所有对象以及在其结构中其他位置具有 val5 的所有对象。
这是我在 Django 中搜索的方式:
self.searchqueryset.auto_query(self.cleaned_data['q'])
有时结果是 "okayish" 有时完全没用。
我可能需要一个正确方向的指针,并了解我在这里犯的错误。谢谢!
编辑:我在下面添加了我的最终解决方案作为答案!
有一点可以肯定的是,你首先需要根据你的具体数据,根据你的查询需求,制作一个自定义映射,我的建议是contains_more
应该是nested
type这样您就可以对您的字段进行更精确的查询。
我不知道您的字段的确切名称,但根据您显示的内容,一种可能的映射可能是这样的。
{
"your_type_name": {
"properties": {
"foo": {
"type": "string"
},
"metadata": {
"type": "object",
"properties": {
"some_key": {
"type": "string"
},
"someotherkey2": {
"type": "string"
},
"more_data": {
"type": "object",
"properties": {
"contains_more": {
"type": "nested",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "string"
},
"baz": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
然后,正如 mark 在他的评论中已经提到的,auto_query
不会削减它,主要是因为多层嵌套。据我所知,Django/Haystack 不支持开箱即用的嵌套查询,但您可以扩展 Haystack 来支持它。这里有一篇博客 post 解释了如何解决这个问题:http://www.stamkracht.com/extending-haystacks-elasticsearch-backend。不确定这是否有帮助,但您应该尝试一下,如果您需要更多帮助,请告诉我们。
索引:
首先你应该使用动态templates,如果你想定义相对于键名的特定映射,或者如果你的文档没有相同的结构。
但是 30 键并没有那么高,你应该更喜欢定义自己的映射而不是让 Elasticsearch 为你猜测它(如果首先添加了不正确的数据,映射将根据这些数据定义)
正在搜索:
您无法搜索
foz: val5
因为 "foz" 密钥不存在。
但是键 "metadata.more_data.even_more.foz" 是 => 你所有的键都从文档的根部开始展开
这样你就必须搜索
foo: val5
metadata.more_data.even_more.foz: 12*
metadata.more_data.contains_more.bar: val*
metadata.somekey1: val1
例如使用query_string
"query_string": {
"default_field": "metadata.more_data.even_more.foz",
"query": "12*"
}
或者如果您想在多个字段中搜索
"query_string": {
"fields" : ["metadata.more_data.contains_more.bar", "metadata.somekey1"],
"query": "val*"
}
我花了一段时间才找出 适合我的正确解决方案
它混合了 @juliendangers 和 @Val 提供的答案以及更多自定义内容。
- 我用更具体的 django-simple-elasticsearch
替换了 Haystack
向模型添加了自定义 get_type_mapping
方法
@classmethod
def get_type_mapping(cls):
return {
"properties": {
"somekey": {
"type": "<specific_type>",
"format": "<specific_format>",
},
"more_data": {
"type": "nested",
"include_in_parent": True,
"properties": {
"even_more": {
"type": "nested",
"include_in_parent": True,
}
/* and so on for each level you care about */
}
}
}
向模型添加了自定义 get_document
方法
@classmethod
def get_document(cls, obj):
return {
'somekey': obj.somekey,
'more_data': obj.more_data,
/* and so on */
}
添加自定义搜索表单
class Searchform(ElasticsearchForm):
q = forms.Charfield(required=False)
def get_index(self):
return 'your_index'
def get_type(self):
return 'your_model'
def prepare_query(self):
if not self.cleaned_data['q']:
q = "*"
else:
q = str(self.cleaned_data['q'])
return {
"query": {
"query_string": {
"query": q
}
}
}
def search(self):
esp = ElasticsearchProcessor(self.es)
esp.add_search(self.prepare_query, page=1, page_size=25, index=self.get_index(), doc_type=self.get_type())
responses = esp.search()
return responses[0]
所以这对我有用,涵盖了我的用例。也许它可以对某人有所帮助。
我有一些 JSON 如下所示:我们将该字段称为 metadata
{
"somekey1": "val1",
"someotherkey2": "val2",
"more_data": {
"contains_more": [
{
"foo": "val5",
"bar": "val6"
},
{
"foo": "val66",
"baz": "val44"
},
],
"even_more": {
"foz" : 1234,
}
}
}
这只是一个简单的例子。真实的可以变得更加复杂。 键可以出现多次。值也可以是 int 或 str.
现在第一个问题是我不太确定我必须如何在 elasticsearch 中正确索引它以便我可以找到具有特定请求的内容。
我正在使用 Django/Haystack,索引如下所示:
class FooIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
metadata = indexes.CharField(model_attr='get_metadata')
# and some more specific fields
和模板:
{
"foo": {{ object.foo }},
"metadata": {{ object.metadata}},
# and some more
}
然后元数据将被上面的示例填充,结果将如下所示:
{
"foo": "someValue",
"metadata": {
"somekey1": "val1",
"someotherkey2": "val2",
"more_data": {
"contains_more": [
{
"foo": "val5",
"bar": "val6"
},
{
"foo": "val66",
"baz": "val44"
},
],
"even_more": {
"foz" : 1234,
}
}
},
}
这将进入 elasticsearch 中的 'text' 列。
所以现在的目标是能够搜索以下内容:
- foo: val5
- foz: 12*
- bar: val*
- somekey1: val1
- 等等
第二个问题: 当我搜索时对于 foo: val5,它匹配仅具有键 "foo" 的所有对象以及在其结构中其他位置具有 val5 的所有对象。
这是我在 Django 中搜索的方式:
self.searchqueryset.auto_query(self.cleaned_data['q'])
有时结果是 "okayish" 有时完全没用。
我可能需要一个正确方向的指针,并了解我在这里犯的错误。谢谢!
编辑:我在下面添加了我的最终解决方案作为答案!
有一点可以肯定的是,你首先需要根据你的具体数据,根据你的查询需求,制作一个自定义映射,我的建议是contains_more
应该是nested
type这样您就可以对您的字段进行更精确的查询。
我不知道您的字段的确切名称,但根据您显示的内容,一种可能的映射可能是这样的。
{
"your_type_name": {
"properties": {
"foo": {
"type": "string"
},
"metadata": {
"type": "object",
"properties": {
"some_key": {
"type": "string"
},
"someotherkey2": {
"type": "string"
},
"more_data": {
"type": "object",
"properties": {
"contains_more": {
"type": "nested",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "string"
},
"baz": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
然后,正如 mark 在他的评论中已经提到的,auto_query
不会削减它,主要是因为多层嵌套。据我所知,Django/Haystack 不支持开箱即用的嵌套查询,但您可以扩展 Haystack 来支持它。这里有一篇博客 post 解释了如何解决这个问题:http://www.stamkracht.com/extending-haystacks-elasticsearch-backend。不确定这是否有帮助,但您应该尝试一下,如果您需要更多帮助,请告诉我们。
索引:
首先你应该使用动态templates,如果你想定义相对于键名的特定映射,或者如果你的文档没有相同的结构。
但是 30 键并没有那么高,你应该更喜欢定义自己的映射而不是让 Elasticsearch 为你猜测它(如果首先添加了不正确的数据,映射将根据这些数据定义)
正在搜索:
您无法搜索
foz: val5
因为 "foz" 密钥不存在。
但是键 "metadata.more_data.even_more.foz" 是 => 你所有的键都从文档的根部开始展开
这样你就必须搜索
foo: val5
metadata.more_data.even_more.foz: 12*
metadata.more_data.contains_more.bar: val*
metadata.somekey1: val1
例如使用query_string
"query_string": {
"default_field": "metadata.more_data.even_more.foz",
"query": "12*"
}
或者如果您想在多个字段中搜索
"query_string": {
"fields" : ["metadata.more_data.contains_more.bar", "metadata.somekey1"],
"query": "val*"
}
我花了一段时间才找出 适合我的正确解决方案
它混合了 @juliendangers 和 @Val 提供的答案以及更多自定义内容。
- 我用更具体的 django-simple-elasticsearch 替换了 Haystack
向模型添加了自定义
get_type_mapping
方法@classmethod def get_type_mapping(cls): return { "properties": { "somekey": { "type": "<specific_type>", "format": "<specific_format>", }, "more_data": { "type": "nested", "include_in_parent": True, "properties": { "even_more": { "type": "nested", "include_in_parent": True, } /* and so on for each level you care about */ } } }
向模型添加了自定义
get_document
方法@classmethod def get_document(cls, obj): return { 'somekey': obj.somekey, 'more_data': obj.more_data, /* and so on */ }
添加自定义搜索表单
class Searchform(ElasticsearchForm): q = forms.Charfield(required=False) def get_index(self): return 'your_index' def get_type(self): return 'your_model' def prepare_query(self): if not self.cleaned_data['q']: q = "*" else: q = str(self.cleaned_data['q']) return { "query": { "query_string": { "query": q } } } def search(self): esp = ElasticsearchProcessor(self.es) esp.add_search(self.prepare_query, page=1, page_size=25, index=self.get_index(), doc_type=self.get_type()) responses = esp.search() return responses[0]
所以这对我有用,涵盖了我的用例。也许它可以对某人有所帮助。