Я хочу добавить простое поле поиска, хотел бы использовать что-то вроде

collectionRef.where ('имя', 'содержит', 'searchTerm')

Я пробовал использовать where ('name', '==', '% searchTerm%'), но ничего не вернул.

tehfailsafe

Ответы (21)

Такого оператора нет, допустимые: ==, <, <=, >, > =.

Вы можете фильтровать только по префиксам, например, для всего, что начинается между bar и foo, вы можете использовать

collectionRef.where ('name', '> =', 'bar'). Where ('name', '<=', 'foo')

Для этого вы можете использовать внешний сервис, например Algolia или ElasticSearch.

Мы можем использовать обратную галочку, чтобы распечатать значение строки. Это должно работать:

where('name', '==', `${searchTerm}`)

У меня возникла эта проблема, и я нашел довольно простое решение.

String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")

isGreaterThanOrEqualTo позволяет нам отфильтровать начало нашего поиска и, добавив «z» в конец isLessThanOrEqualTo, мы ограничиваем наш поиск, чтобы не переходить к следующим документам.

Хотя ответ Кубы верен в том, что касается ограничений, вы можете частично имитировать это с помощью структуры, подобной множеству:

{
  'terms': {
    'reebok': true,
    'mens': true,
    'tennis': true,
    'racket': true
  }
}

Теперь вы можете запрашивать

collectionRef.where('terms.tennis', '==', true)

Это работает, потому что Firestore автоматически создает индекс для каждого поля. К сожалению, это не работает напрямую для составных запросов, потому что Firestore не создает составные индексы автоматически.

Вы все еще можете обойти это, сохраняя комбинации слов, но это становится ужасно быстро.

Возможно, вам все же будет лучше с подвесным двигателем полнотекстовый поиск.

Хотя Firebase явно не поддерживает поиск термина в строке,

Firebase (сейчас) поддерживает следующие решения, которые подойдут для вашего случая и многих других:

По состоянию на август 2018 года они поддерживают запрос array-contains. См .: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html

Теперь вы можете поместить все ключевые термины в массив в виде поля, а затем запросить все документы, в которых есть массив, содержащий «X». Вы можете использовать логическое И для дальнейших сравнений для дополнительных запросов. (Это потому, что firebase в настоящее время не поддерживает составные запросы для нескольких запросов, содержащих массив, поэтому запросы сортировки 'И' будут должны быть выполнены на стороне клиента)

Использование массивов в этом стиле позволит оптимизировать их для одновременной записи, что приятно! Не проверял, поддерживает ли он пакетные запросы (в документации не сказано), но держу пари, что он поддерживает, так как это официальное решение.


Использование:

collection("collectionPath").
    where("searchTermsArray", "array-contains", "term").get()

РЕДАКТИРОВАТЬ 05/2021:

Google Firebase теперь имеет расширение для реализации поиска с помощью Algolia. Algolia - это платформа полнотекстового поиска с обширным списком функций. У вас должен быть план «Blaze» на Firebase, и существуют сборы, связанные с запросами Algolia, но это был бы мой рекомендуемый подход для производственных приложений. Если вы предпочитаете бесплатный базовый поиск, см. Мой исходный ответ ниже.

https://firebase.google.com/products/extensions/firestore-algolia-search https://www.algolia.com

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Выбранный ответ работает только для точного поиска и не является естественным поведением пользователей при поиске (поиск «яблоко» в «Джо сегодня съел яблоко» не сработает).

Я думаю, что ответ Дэна Фейна выше должен быть оценен выше. Если данные String, которые вы ищете, короткие, вы можете сохранить все подстроки строки в массиве в своем документе, а затем выполнить поиск по массиву с помощью запроса Firebase array_contains. Документы Firebase ограничены 1 МиБ (1048576 байтов) (Квоты и ограничения Firebase), что составляет около 1 миллиона символов, сохраненных в документе (я думаю, 1 символ ~ = 1 байт). Хранить подстроки можно, если ваш документ не приближается к отметке в 1 миллион.

Пример поиска по именам пользователей:

Шаг 1. Добавьте в проект следующее расширение String. Это позволяет легко разбить строку на подстроки. (Я нашел вот это).

extension String {

var length: Int {
    return count
}

subscript (i: Int) -> String {
    return self[i ..< i + 1]
}

func substring(fromIndex: Int) -> String {
    return self[min(fromIndex, length) ..< length]
}

func substring(toIndex: Int) -> String {
    return self[0 ..< max(0, toIndex)]
}

subscript (r: Range) -> String {
    let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                        upper: min(length, max(0, r.upperBound))))
    let start = index(startIndex, offsetBy: range.lowerBound)
    let end = index(start, offsetBy: range.upperBound - range.lowerBound)
    return String(self[start ..< end])
}

Шаг 2: Когда вы сохраняете имя пользователя, также сохраняйте результат этой функции в виде массива в том же документе. Это создает все варианты исходного текста и сохраняет их в массиве. Например, при вводе текста «Apple» будет создан следующий массив: [«a», «p», «p», «l», «e», «ap», «pp», «pl», «le "," app "," ppl "," ple "," app "," pple "," apple "], которые должны охватывать все критерии поиска, которые может ввести пользователь. Вы можете оставить maximumStringSize равным nil, если хотите получить все результаты, однако, если есть длинный текст, я бы рекомендовал ограничить его, прежде чем размер документа станет слишком большим - где-то около 15 мне подходит (большинство людей все равно не ищут длинные фразы ).

func createSubstringArray(forText text: String, maximumStringSize: Int?) -> [String] {
    
    var substringArray = [String]()
    var characterCounter = 1
    let textLowercased = text.lowercased()
    
    let characterCount = text.count
    for _ in 0...characterCount {
        for x in 0...characterCount {
            let lastCharacter = x + characterCounter
            if lastCharacter <= characterCount {
                let substring = textLowercased[x.. max {
            break
        }
    }
    
    print(substringArray)
    return substringArray
}

Шаг 3. Вы можете использовать функцию array_contains Firebase!

[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....

У меня это сработало отлично, но могло вызвать проблемы с производительностью.

Сделайте это при запросе firestore:

   Future searchResults = collectionRef
        .where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
        .getDocuments();

Сделайте это в FutureBuilder:

    return FutureBuilder(
          future: searchResults,
          builder: (context, snapshot) {           
            List searchResults = [];
            snapshot.data.documents.forEach((doc) {
              Model model = Model.fromDocumet(doc);
              if (searchQuery.isNotEmpty &&
                  !model.property.toLowerCase().contains(searchQuery.toLowerCase())) {
                return;
              }

              searchResults.add(model);
            })
   };

Я уверен, что Firebase скоро выйдет с "string-contains" для захвата любого index [i] startAt в строке ... Но Я исследовал сети и обнаружил, что это решение придумал кто-то другой настройте свои данные следующим образом

state = {title:"Knitting"}
...
const c = this.state.title.toLowerCase()

var array = [];
for (let i = 1; i < c.length + 1; i++) {
 array.push(c.substring(0, i));
}

firebase
.firestore()
.collection("clubs")
.doc(documentId)
.update({
 title: this.state.title,
 titleAsArray: array
})

enter image description here

такой запрос

firebase
.firestore()
.collection("clubs")
.where(
 "titleAsArray",
 "array-contains",
 this.state.userQuery.toLowerCase()
)

Следующий фрагмент кода принимает ввод от пользователя и собирает данные, начиная с набранного.

Пример данных:

В коллекции Firebase «Пользователи»

user1: {name: 'Ali', age: 28},

user2: {name: 'Khan', age: 30},

user3: {name: 'Hassan', age: 26},

user4: {name: 'Adil', возраст: 32}

TextInput: A

Результат:

{имя: 'Али', возраст: 28},

{имя: 'Адиль', возраст: 32}

let timer;

// method called onChangeText from TextInput

const textInputSearch = (text) => {

const inputStart = text.trim();
let lastLetterCode = inputStart.charCodeAt(inputStart.length-1);
lastLetterCode++;
const newLastLetter = String.fromCharCode(lastLetterCode);
const inputEnd = inputStart.slice(0,inputStart.length-1) + lastLetterCode;

clearTimeout(timer);

timer = setTimeout(() => {
    firestore().collection('Users')
        .where('name', '>=', inputStart)
        .where('name', '<', inputEnd)
        .limit(10)
        .get()
        .then(querySnapshot => {
            const users = [];
                querySnapshot.forEach(doc => {
                    users.push(doc.data());
                })
            setUsers(users); //  Setting Respective State
        });
    }, 1000);

};

Полнотекстовый поиск, релевантный поиск и поиск по триграмме!

ОБНОВЛЕНИЕ - 17.02.21 - Я создал несколько новых опций полнотекстового поиска.

Подробнее см. Fireblog.io.


Кроме того, примечание, dgraph теперь имеет веб-сокеты для работы в реальном времени ... вау, никогда не ожидал, что это произойдет, какое удовольствие!Slash Dgraph - потрясающе!


- Исходное сообщение -

Здесь несколько примечаний:

1.) \ uf8ff работает так же, как ~

2.) Вы можете использовать предложение where или предложения start end:

ref.orderBy ('title'). StartAt (срок) .endAt (срок + '~');

в точности совпадает с

ref.where ('заголовок', '> =', термин) .where ('заголовок', '<=', термин + '~');

3.) Нет, это не сработает, если поменять местами startAt () и endAt () в каждой комбинации, однако вы можете добиться тот же результат путем создания второго поля поиска, которое перевернуто, и объединения результатов.

Пример: Сначала вы должны сохранить обратную версию поля при создании поля. Примерно так:

// сбор
const postRef = db.collection ('сообщения')

асинхронная функция searchTitle (термин) {

  // обратный термин
  const termR = term.split (""). reverse (). join ("");

  // определяем запросы
  const title = postRef.orderBy ('название'). startAt (термин) .endAt (термин + '~'). get ();
  const titleR = postRef.orderBy ('titleRev'). startAt (termR) .endAt (termR + '~'). get ();

  // получаем запросы
  const [titleSnap, titleRSnap] = await Promise.all ([
    названия,
    названияR
  ]);
  возврат (titleSnap.docs) .concat (titleRSnap.docs);
}

При этом вы можете искать последние буквы строкового поля и первые, а не случайные средние буквы или группы букв. Это ближе к желаемому результату. Однако это не особо поможет, когда нам нужны случайные средние буквы или слова. Кроме того, не забудьте сохранить все строчные буквы или копию строчных букв для поиска, так что регистр не будет проблемой.

4.) Если у вас всего несколько слов, Метод Кена Тана сделает все, что вы хотите, или, по крайней мере, после того, как вы его немного измените. Однако, имея всего лишь абзац текста, вы в геометрической прогрессии создадите более 1 МБ данных, что больше, чем ограничение на размер документа firestore (я знаю, я это тестировал).

5.) Если бы вы могли объединить array-contains (или другую форму массивов) с трюком \ uf8ff, вы могли бы получить жизнеспособный поиск, не доходящий до предела. Я пробовал каждую комбинацию, даже с картами, и ничего не вышло. Кто-нибудь в этом догадывается, опубликуйте здесь.

6.) Если вам нужно уйти от АЛГОЛИИ и ЭЛАСТИЧЕСКОГО ПОИСКА, и я вас ни в чем не виню, вы всегда можете использовать mySQL, postSQL или neo4Js в Google Cloud. Их все 3 легко настроить, и у них есть бесплатные уровни. У вас будет одна облачная функция для сохранения данных onCreate () и другая функция onCall () для поиска данных. Просто ... иш. Почему бы тогда просто не перейти на mySQL? Конечно же, данные в реальном времени! Когда кто-то пишет DGraph с помощью websocks для данных в реальном времени, посчитайте меня!

Algolia и ElasticSearch созданы для работы с базами данных только для поиска, поэтому нет ничего более быстрого ... но вы платите за это. Google, почему вы уводите нас от Google, не следите за MongoDB noSQL и не разрешаете поиск?

На сегодняшний день существует в основном 3 различных обходных пути, которые были предложены экспертами в качестве ответов на вопрос.

Я все перепробовал. Я подумал, что было бы полезно задокументировать мой опыт работы с каждым из них.

Метод-A: Использование: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")

Предложено @Kuba и @Ankit Prajapati

.where ("dbField1", "> =", searchString)
.where ("dbField1", "<=", searchString + "\ uf8ff");

A.1 Запросы Firestore могут выполнять фильтры диапазона (>, <,> =, <=) только для одного поля. Запросы с фильтрами диапазона для нескольких полей не поддерживаются. Используя этот метод, вы не можете иметь оператор диапазона в любом другом поле базы данных, например. поле даты.

А.2. Этот метод НЕ работает для поиска в нескольких полях одновременно. Например, вы не можете проверить, находится ли строка поиска в каком-либо из полей (имя, примечания и адрес).

Метод-B: Использование MAP строк поиска с «true» для каждой записи на карте и использование оператора «==» в запросах

Предложил @Gil Gilbert

документ1 = {
  'searchKeywordsMap': {
    'Jam': правда,
    'Масло': правда,
    'Мухамед': правда,
    'Зеленый район': правда,
    'Мухамед, Зеленый район': правда,
  }
}

.where (`searchKeywordsMap. $ {searchString}`, "==", истина);

B.1 Очевидно, этот метод требует дополнительной обработки каждый раз, когда данные сохраняются в базу данных, и, что более важно, требует дополнительного места для хранения карты строк поиска.

B.2 Если запрос Firestore имеет единственное условие, подобное приведенному выше, то заранее создавать индекс не нужно. В этом случае это решение будет работать нормально.

B.3 Однако, если в запросе есть другое условие, например (status === "active",) Кажется, что индекс требуется для каждой "строки поиска", которую вводит пользователь. Другими словами, если пользователь ищет «Джем», а другой пользователь ищет «Масло», необходимо заранее создать индекс для строки «Джем», а другой - для «Масло» и т. Д. Если вы не можете предсказать все возможные строки поиска пользователей, это НЕ работает - если в запросе есть другие условия!

.where (searchKeywordsMap ["Jam"], "==", true); // требуется индекс для searchKeywordsMap ["Jam"]
.where ("статус", "==", "активный");

**Метод-C: Использование ARRAY строк поиска и оператора "array-contains"

Предложено @Albert Renshaw и продемонстрировано @Nick Carducci

документ1 = {
  'searchKeywordsArray': [
    'Варенье',
    'Масло сливочное',
    «Мухамед»,
    «Зеленый квартал»,
    «Мухамед, Зеленый квартал»,
  ]
}

.where ("searchKeywordsArray", "массив-содержит", searchString);

C.1 Подобно методу-B, этот метод требует дополнительной обработки каждый раз, когда данные сохраняются в базу данных, и, что более важно, требует дополнительного места для хранения массива строк поиска.

C.2 Запросы Firestore могут включать не более одного предложения "array-contains" или "array-contains-any" в составном запросе.

Общие ограничения:

  1. Ни одно из этих решений, похоже, не поддерживает поиск частичных строк. Например, если поле db содержит «1 Peter St, Green District», вы не сможете искать строку «strict».
  2. Практически невозможно охватить все возможные комбинации ожидаемых строк поиска. Например, если поле db содержит «1 Mohamed St, Green District», вы НЕ сможете найти строку «Green Mohamed», которая представляет собой строку, в которой порядок слов отличается от порядка, используемого в db. поле.

Не существует универсального решения. У каждого обходного пути есть свои ограничения. Я надеюсь, что приведенная выше информация поможет вам в процессе выбора между этими обходными путями.

Список условий запроса Firestore можно найти в документации https://firebase.google.com/docs/firestore/query-data/queries.

Я не пробовал https://fireblog.io/blog/post/firestore-full-text-search, что предлагает @ Jonathan.

Я использовал триграмму, как сказал Джонатан.

триграммы - это группы из 3 букв, хранящиеся в базе данных для помощи при поиске. поэтому, если у меня есть данные о пользователях, и я позволяю «сказать, что хочу запросить« trum »для дональда Трампа, я должен сохранить их таким образом

enter image description here

и я просто напомню вот так

 onPressed: () {
      //LET SAY YOU TYPE FOR 'tru' for trump
      List search = ['tru', 'rum'];
      Future inst = FirebaseFirestore.instance
          .collection("users")
          .where('trigram', arrayContainsAny: search)
          .get();
      print('result=');
      inst.then((value) {
        for (var i in value.docs) {
          print(i.data()['name']);
        }
      });

, что даст правильный результат несмотря ни на что

enter image description here

То же, что и @nicksarno, но с более отточенным кодом, не нуждающимся в расширении:

Шаг 1

func getSubstrings (from string: String, maximumSubstringLenght: Int = .max) -> [Substring] {
    пусть строка = строка.lowercased ()
    пусть stringLength = string.count
    пусть stringStartIndex = string.startIndex
    подстроки var: [Substring] = []
    для lowBound в 0 .. 

Шаг 2

let name = "Лоренцо"
ref.setData (["name": имя, "nameSubstrings": getSubstrings (from: name)])

Шаг 3

Firestore.firestore (). Collection ("Пользователи")
  .whereField ("nameSubstrings", arrayContains: searchText)
  .getDocuments ...

Поздний ответ, но для тех, кто все еще ищет ответ. Допустим, у нас есть коллекция пользователей, и в каждом документе этой коллекции есть поле «имя пользователя», поэтому, если вы хотите найти документ, в котором имя пользователя начинается с "al" мы можем сделать что-то вроде

 FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")

Я на самом деле думаю, что лучшее решение для этого в Firestore - это поместить все подстроки в массив и просто выполнить запрос array_contains. Это позволяет вам выполнять сопоставление подстрок. Немного излишне хранить все подстроки, но если ваши условия поиска короткие, это очень разумно.

Firebase предлагает Algolia или ElasticSearch для полнотекстового поиска, но более дешевой альтернативой может быть MongoDB. Самый дешевый кластер (около 10 долларов США в месяц) позволяет выполнять полнотекстовое индексирование.

Если вы не хотите использовать сторонний сервис, такой как Algolia, Firebase Cloud Functions - отличная альтернатива. Вы можете создать функцию, которая может получать входной параметр, обрабатывать записи на стороне сервера и затем возвращать те, которые соответствуют вашим критериям.

Обновление до 2021 года

Позаимствовал кое-что из других ответов. Это включает:

  • Многословный поиск с использованием разделения (действует как ИЛИ)
  • Многоключевой поиск с использованием плоской

Немного ограничена чувствительность к регистру, вы можете решить эту проблему, сохранив повторяющиеся свойства в верхнем регистре. Пример: query.toUpperCase () user.last_name_upper


// query: searchable terms as string

let users = await searchResults("Bob Dylan", 'users');

async function searchResults(query = null, collection = 'users', keys = ['last_name', 'first_name', 'email']) {

    let querySnapshot = { docs : [] };

    try {
        if (query) {
            let search = async (query)=> {
                let queryWords = query.trim().split(' ');
                return queryWords.map((queryWord) => keys.map(async (key) =>
                    await firebase
                        .firestore()
                        .collection(collection)
                        .where(key, '>=', queryWord)
                        .where(key, '<=', queryWord +  '\uf8ff')
                        .get())).flat();
            }

            let results = await search(query);

            await (await Promise.all(results)).forEach((search) => {
                querySnapshot.docs = querySnapshot.docs.concat(search.docs);
            });
        } else {
            // No query
            querySnapshot = await firebase
                .firestore()
                .collection(collection)
                // Pagination (optional)
                // .orderBy(sortField, sortOrder)
                // .startAfter(startAfter)
                // .limit(perPage)
                .get();
        }
    } catch(err) {
        console.log(err)
    }

    // Appends id and creates clean Array
    const items = [];
    querySnapshot.docs.forEach(doc => {
        let item = doc.data();
        item.id = doc.id;
        items.push(item);
    });

    // Filters duplicates
    return items.filter((v, i, a) => a.findIndex(t => (t.id === v.id)) === i);
}

Примечание: количество вызовов Firebase эквивалентно количеству слов в строке запроса * количеству ключей, по которым вы выполняете поиск.

Согласно документам Firestore, Cloud Firestore не поддерживает собственное индексирование или поиск текстовых полей в документах. Кроме того, загрузка всей коллекции для поиска полей на стороне клиента нецелесообразна.

Рекомендуются сторонние поисковые решения, такие как Algolia и Elastic Search.

Я согласен с ответом @Kuba, но все же нужно добавить небольшое изменение, чтобы поиск по префиксу работал идеально. вот что сработало для меня

Для поиска записей, начинающихся с имени queryText

collectionRef.where ('name', '> =', queryText) .where ('name', '<=', queryText + '\ uf8ff').

Используемый в запросе символ \ uf8ff является очень высокой кодовой точкой в ​​диапазоне Unicode (это код частной области использования [PUA]). Поскольку он находится после большинства обычных символов в Юникоде, запрос соответствует всем значениям, начинающимся с queryText.

С Firestore вы можете реализовать полнотекстовый поиск, но он все равно будет стоить больше операций чтения, чем в противном случае, а также вам нужно будет вводить и индексировать данные определенным образом, поэтому в этом подходе вы можете использовать firebase облачные функции для токенизации и последующего хеширования входящего текста при выборе линейной хеш-функции h (x), которая удовлетворяет следующему - если x . Для токенизации вы можете выбрать несколько облегченных библиотек NLP, чтобы сократить время холодного запуска вашей функции, что может удалить ненужные слова из вашего предложения. Затем вы можете запустить запрос с операторами «меньше» и «больше» в Firestore. При сохранении ваших данных вам также необходимо убедиться, что вы хэшируете текст перед его сохранением, и сохраните простой текст также, как если бы вы изменили простой текст, хешированное значение также изменится.

2022 WebDevInsider