如何在 google 云数据存储中执行 SQL 样式的搜索查询?

How to do an SQL style search query in google cloud datastore?

Google Cloud datastore 不允许进行 SQL 风格的搜索查询,例如

SELECT * FROM Person WHERE Name LIKE "Rob*"

那会 return Rob, Robert, Roberto, Roberta, Roby 等等。

GCP datastore 仅允许使用运算符过滤字段:>, >=, <, <= 规则如下:

使用这些规则,查询

query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Order("Name")

不仅 return 名字以 Rob 开头的所有 Person,还有名字大于 Rob(Ruben、Sarah、Zoe)的所有 Person 和所有名字以 Rob 开头的 Person名称以小写字母开头。

目前的 post 是我在 Go 中发现的一个 hack,用于模拟 SQL 风格的搜索查询。

以下解决方案以编程方式解决了该问题。它是用 go 编码的,但我相信它可以很容易地适应任何语言。

我会先制作整个片段,然后再分解它。

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)
        }
    }
}

1 - 小写假设

为了使这段代码能够工作,我们首先需要做出一个非常有力的假设,即所有名称都是小写的。如果由于某种原因,你不能做出这个假设,你仍然可以部分使用这段代码,但它会降低效率。

2 - 查询数据存储

代码的第一部分专门用于获取名称与所需模式匹配的人员的数据存储。

    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)
    }

我们确保使用 strings.ToLower(name) 来确保我们不会获取 ALL Persons。理想情况下 name 也应该被修剪,但是这通常是在前端完成的,所以我们在这里省略了。

再一次,这是基于所有 Name 都是小写的假设。如果你不能假设这一点,你总是可以使用

query := datastore.NewQuery("Person").Filter("Name >= ", name).Order("Name")

您只会得到一个初始列表,其中包含(可能很多)更多 Persons.

最后,我们用 .Order("Name") 对获取的列表进行排序,因为这将成为代码第二部分的基点。

3 - 按名称过滤

到这里为止是一段简单的 GetAll 代码。我们仍然需要将键插入到 Person 结构中。我们需要找到一种方法来优化它。为此,我们可以基于 personskeys 列表的确切长度和​​顺序这一事实。因此,即将到来的 for 循环开始时与将 Key 插入结构位完全相同。

    for i, p := range persons {
        p.ID = keys[i].ID

下一位是优化的地方:因为我们知道 Person 是按 Name 排序的,所以我们确信,一旦 strings.Contains(p.Name, strings.ToLower(name)) 不正确,我们已选择所有 Name 符合我们标准的 Person,也就是说,只要 p.Name 不再以 Rob 开头,而是以 RocRod 或字典序大于此的任何内容。

        if !strings.Contains(p.Name, strings.ToLower(name)) {
            break

然后我们可以使用 break 指令退出循环,希望只解析了符合我们条件的 persons 列表的前几个元素。这些属于 else 语句:

        } else {
            filteredPersons = append(filteredPersons, p)
        }

4 - 在没有小写假设的情况下过滤名称

正如我之前所说,如果你不能假设所有的名字都是小写的,你仍然可以使用这段代码,但它不会被优化,因为你将必须扫描完整的 persons 列表由查询返回。

代码应该像这样

    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)
        }
    }

特别是对于前缀匹配,您可以在同一个 属性 上使用多个不等式过滤器。 IE。来自 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."

该页面上的示例是 SELECT * FROM Task WHERE tag > 'learn' AND tag < 'math'。或者你的情况 query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Filter("Name <= Rob~").Order("Name")