Как выполнить поисковый запрос в стиле SQL в облачном хранилище данных Google? - PullRequest
0 голосов
/ 06 марта 2020

Google Cloud хранилище данных не позволяет создавать поисковые запросы в стиле SQL, например

SELECT * FROM Person WHERE Name LIKE "Rob*"

, которые возвращали бы Rob, Robert, Roberto, Roberta, Roby и т. Д.

GCP хранилище данных позволяет фильтровать только поля с использованием операторов: >, >=, <, <= с такими правилами, как:

  • любая заглавная буква меньше любой строчной буквы
  • a < b < c < ... < z

С этими правилами запрос

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

вернет не только всех лиц, чье имя начинается с Rob, но также всех лиц, чье имя больше Rob (Рубен, Сара, Зои) и все Персоны, чьи имена начинаются со строчной буквы.

Настоящий пост является хаком, который я нашел в Go для эмуляции поискового запроса в стиле SQL.

Ответы [ 2 ]

1 голос
/ 06 марта 2020

Специально для префиксных совпадений вы можете использовать несколько фильтров неравенства для одного свойства. Т.е. из https://cloud.google.com/datastore/docs/concepts/queries "Если запрос имеет несколько фильтров неравенства для данного свойства, объект будет соответствовать запросу, только если хотя бы одно из его отдельных значений для свойства удовлетворяет всем фильтрам."

Пример на этой странице SELECT * FROM Task WHERE tag > 'learn' AND tag < 'math'. Или для вашего случая query := datastore.NewQuery("Person").Filter("Name >= ", "Rob").Filter("Name <= Rob~").Order("Name")

0 голосов
/ 06 марта 2020

Следующее решение решает проблему программно. Он закодирован в 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 Person с. В идеале name также следует обрезать, но обычно это делается во внешнем интерфейсе, поэтому мы здесь это опускаем.

Еще раз, это основано на предположении, что все Name s находятся ниже дело. Если вы не можете этого допустить, вы всегда можете использовать

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

Вы просто получите начальный список с ( возможно много ) больше Person с.

Наконец, мы упорядочиваем наш извлеченный список с .Order("Name"), так как это будет базовая точка второй части кода.

3 - Фильтр по именам

Вплоть до здесь это был простой GetAll кусок кода. Нам все еще нужно вставить ключи в Person структуры. Нам нужно найти способ оптимизировать это. Для этого мы можем опираться на тот факт, что список persons и keys имеют точную длину и порядок. Таким образом, предстоящий for l oop начинается точно так же, как обычная вставка ключа в структурный бит.

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

Следующий бит - это то место, где происходит оптимизация: поскольку мы знаем, что Person s упорядочены к Name мы уверены, что, как только strings.Contains(p.Name, strings.ToLower(name)) не соответствует действительности, мы выбрали все Person s, чей Name соответствует нашим критериям, то есть, как только p.Name не начинается с Rob больше, но с Roc или Rod или чем-то лексикографически большим, чем это.

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

Затем мы можем избежать нашего l oop, используя инструкцию 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)
        }
    }
...