[英]How to do an SQL style search query in google cloud datastore?
Google Cloud datastore does not allow to make SQL style search queries like Google Cloud数据存储区不允许进行 SQL 样式的搜索查询,例如
SELECT * FROM Person WHERE Name LIKE "Rob*"
that would return Rob, Robert, Roberto, Roberta, Roby
and so on.那将返回
Rob, Robert, Roberto, Roberta, Roby
等等。
GCP datastore only allows to filter on fields using operators: >, >=, <, <=
with rules like: GCP数据存储区仅允许使用运算符过滤字段:
>, >=, <, <=
,规则如下:
a < b < c < ... < z
With these rules, the query有了这些规则,查询
query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Order("Name")
would not only return all Person whose name starts with Rob
, but also all Person whose name is greater than Rob
(Ruben, Sarah, Zoe) and all Person whose names starts with a lower case letter.不仅会返回名称以
Rob
开头的所有 Person ,还会返回名称大于Rob
(Ruben、Sarah、Zoe)的所有 Person 以及名称以小写字母开头的所有 Person 。
The present post is a hack that I found in Go to emulate a SQL style search query.这篇文章是我在 Go 中找到的一个模拟 SQL 样式搜索查询的技巧。
Specifically for prefix matches you can use multiple inequality filters on the same property.特别是对于前缀匹配,您可以在同一属性上使用多个不等式过滤器。 Ie from https://cloud.google.com/datastore/docs/concepts/queries "If a query has multiple inequality filters on a given property, an entity will match the query only if at least one of its individual values for the property satisfies all of the filters."
即来自https://cloud.google.com/datastore/docs/concepts/queries “如果一个查询在给定的属性上有多个不等式过滤器,则实体将匹配该查询,前提是该实体至少有一个单独的值满足所有过滤器。”
The example on that page is SELECT * FROM Task WHERE tag > 'learn' AND tag < 'math'
.该页面上的示例是
SELECT * FROM Task WHERE tag > 'learn' AND tag < 'math'
。 Or for your case query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Filter("Name <= Rob~").Order("Name")
或者对于您的案例
query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Filter("Name <= Rob~").Order("Name")
The following solution addresses the issue programmatically.以下解决方案以编程方式解决了该问题。 It is coded in
go
but i believe it could be easily adapted to any language.它是用
go
编码的,但我相信它可以很容易地适应任何语言。
I'll first produce the whole snippet and then I shall break it down.我将首先生成整个片段,然后将其分解。
func (store *PersonStore) Search(name string) ([]Person, error) {
context := context.Background()
persons := make([]*Person, 0)
query := datastore.NewQuery("Person").Filter("Name >= ", strings.ToLower(name)).Order("Name")
keys, err := store.client.GetAll(context, query, &persons)
if err != nil {
return nil, fmt.Errorf("unable to search for persons w/ names containing %s - %v", name, err)
}
filteredPersons := make([]*Perons, 0)
for i, p := range persons {
p.ID = keys[i].ID
if !strings.Contains(p.Name, strings.ToLower(name)) {
break
} else {
filteredPersons = append(filteredPersons, p)
}
}
}
In order for this code to work we first need to make a very strong assumption that all names are in lower case .为了让这段代码能够工作,我们首先需要做出一个非常强的假设,即所有的名字都是小写的。 If, for some reason, you cannot make this assumption, you can still partially use this code, but it will be less efficient.
如果由于某种原因,您不能做出这个假设,您仍然可以部分使用此代码,但效率会降低。
The first part of the code is dedicated to fetching the datastore for Persons whose name is matching the desired pattern.代码的第一部分专用于获取名称与所需模式匹配的人员的数据存储。
context := context.Background()
persons := make([]*Person, 0)
query := datastore.NewQuery("Person").Filter("Name >= ", strings.ToLower(name)).Order("Name")
keys, err := store.client.GetAll(context, query, &persons)
if err != nil {
return nil, fmt.Errorf("unable to search for persons w/ names containing %s - %v", name, err)
}
We make sure we use strings.ToLower(name)
to make sure we won't fetch ALL Person
s.我们确保使用
strings.ToLower(name)
来确保我们不会获取所有Person
。 Ideally name
should be also trimmed, but this is usually done in the front-end, so we ommitted this here.理想情况下
name
也应该被修剪,但这通常是在前端完成的,所以我们在这里省略了。
Once again, this is base on the assumption that all Name
s are in lower case.再一次,这是基于所有
Name
都是小写的假设。 If you cannot assume this, you can always use如果你不能假设这一点,你可以随时使用
query := datastore.NewQuery("Person").Filter("Name >= ", name).Order("Name")
You'll just simply get an initial list with ( possibly a lot ) more Person
s.您只需简单地获得一个包含(可能很多)更多
Person
的初始列表。
Finally, we order our fetched list with .Order("Name")
as this is going to be the base point of the second part of the code.最后,我们使用
.Order("Name")
对获取的列表进行.Order("Name")
因为这将成为代码第二部分的基点。
Up to here it was a simple GetAll
piece of code.到这里为止,它是一段简单的
GetAll
代码。 We still need to insert the keys into the Person
structures.我们仍然需要将键插入到
Person
结构中。 We need to find a way to optimize that.我们需要找到一种方法来优化它。 For this we can rest on the fact that
persons
and keys
list are in exact length and order.为此,我们可以依赖于
persons
和keys
列表的长度和顺序准确的事实。 So the upcoming for
loop starts exactly like a regular insert Key into structure bit.因此,即将到来的
for
循环与常规将 Key 插入结构位完全一样。
for i, p := range persons {
p.ID = keys[i].ID
The next bit is where the optimization is: since we know that the Person
s are ordered by Name
we are certain that, as soon as strings.Contains(p.Name, strings.ToLower(name))
is not true we have selected all Person
s whose Name
matches our criteria, that is, as soon as p.Name
doesn't start with Rob
anymore but with Roc
or Rod
or anything lexicographically greater than this.下一点是优化的地方:因为我们知道
Person
是按Name
排序的,所以我们确定,只要strings.Contains(p.Name, strings.ToLower(name))
不为真,我们就选择了所有Person
s 的Name
符合我们的标准,也就是说,只要p.Name
不再以Rob
开头,而是以Roc
或Rod
或任何字典序大于此的p.Name
开头。
if !strings.Contains(p.Name, strings.ToLower(name)) {
break
We can then escape our loop using a break
instruction, hopefuly having only parsed the first few elements of persons
list that matche our criteria.然后我们可以使用
break
指令来逃避我们的循环,希望只解析了符合我们标准的persons
列表的前几个元素。 These fall into the else
statement:这些属于
else
语句:
} else {
filteredPersons = append(filteredPersons, p)
}
As I said earlier, if you cannot assume all names are in lower case, you can still use this code, but it will not be optimized, because you will mandatorily have to scan the full persons
list returned by the query.正如我之前所说,如果你不能假设所有的名字都是小写的,你仍然可以使用这个代码,但它不会被优化,因为你必须扫描查询返回的完整
persons
列表。
The code should look liske this代码应该看起来像这样
filteredPersons := make([]*Perons, 0)
for i, p := range persons {
p.ID = keys[i].ID
if strings.Contains(p.Name, strings.ToLower(name)) {
filteredPersons = append(filteredPersons, p)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.