Я хочу додати просте поле пошуку, хотів би використати щось на кшталт
collectionRef.where('name', 'contains', 'searchTerm')
Я спробував використовувати where('name', '==', '%searchTerm%')
, але нічого не повернуло.
Я хочу додати просте поле пошуку, хотів би використати щось на кшталт
collectionRef.where('name', 'contains', 'searchTerm')
Я спробував використовувати where('name', '==', '%searchTerm%')
, але нічого не повернуло.
Відповіді:
Там немає такого оператора, дозволених з них є ==
, <
, <=
, >
, >=
.
Ви можете фільтрувати лише за префіксами, наприклад, для всього, що починається між вами bar
і foo
ви можете використовувати
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Ви можете використовувати зовнішні сервіси, такі як Algolia або ElasticSearch.
tennis
, але на основі доступних операторів запитів неможливо отримати ці результати. Поєднуючись >=
і <=
не працює. Звичайно, я можу використовувати Algolia, але я також можу просто використовувати його з Firebase, щоб робити більшість запитів і не потрібно переходити на Firestore ...
Хоча відповідь Куби справедлива, що стосується обмежень, ви можете частково імітувати це за допомогою набірної структури:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Тепер ви можете здійснювати запит
collectionRef.where('terms.tennis', '==', true)
Це працює, тому що Firestore автоматично створить індекс для кожного поля. На жаль, це не працює безпосередньо для складних запитів, оскільки Firestore не створює автоматично складені індекси.
Ви все ще можете обійти це, зберігаючи комбінації слів, але це стає некрасивим швидко.
Вам все-таки краще все-таки шукати повний текстовий пошук за межами .
where
Я погоджуюся з відповіддю @ Куби, але все ж для цього потрібно додати невелику зміну, щоб ідеально працювати над пошуком за префіксом. ось що для мене працювало
Для пошуку записів, починаючи з імені queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
Символ, який \uf8ff
використовується в запиті, є дуже високою точкою коду в діапазоні Unicode (це код приватної області використання [PUA]). Оскільки це після більшості регулярних символів у Unicode, запит відповідає всім значенням, з яких починається queryText
.
Хоча 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()
Search term
зазвичай розуміється цілий термін, розділений пробілом, пунктуацією тощо з обох сторін. Якщо ви Google abcde
прямо зараз ви знайдете тільки результати для таких речей , як %20abcde.
або , ,abcde!
але не abcdefghijk..
. хоч напевно весь алфавіт набагато частіше зустрічається в Інтернеті, пошук не для abcde * це для ізольованого abcde
'contains'
, що означає саме те, про що я маю на увазі багато мов програмування. Те саме стосується і '%searchTerm%'
точки зору SQL.
Відповідно до документів Firestore , Cloud Firestore не підтримує вбудовану індексацію або пошук текстових полів у документах. Крім того, завантаження цілої колекції для пошуку полів на стороні клієнта не є практичним.
Рекомендуються сторонні пошукові рішення, такі як Algolia та Elastic Search .
1.) \uf8ff
працює так само, як і~
2.) Ви можете використовувати пункт де або почати закінчення:
ref.orderBy('title').startAt(term).endAt(term + '~');
точно так само, як
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) Ні, це не спрацьовує, якщо ви перевертаєтесь startAt()
і endAt()
в кожній комбінації, однак, ви можете досягти того ж результату, створивши друге поле пошуку, яке перевернуто, та об'єднавши результати.
Приклад: спочатку потрібно зберегти перевернуту версію поля, коли поле створене. Щось на зразок цього:
// collection
const postRef = db.collection('posts')
async function searchTitle(term) {
// reverse term
const termR = term.split("").reverse().join("");
// define queries
const titles = postRef.orderBy('title').startAt(term).endAt(term + '~').get();
const titlesR = postRef.orderBy('titleRev').startAt(termR).endAt(termR + '~').get();
// get queries
const [titleSnap, titlesRSnap] = await Promise.all([
titles,
titlesR
]);
return (titleSnap.docs).concat(titlesRSnap.docs);
}
За допомогою цього ви можете шукати останні букви рядкового поля та перші , не випадкові середні літери чи групи літер. Це ближче до бажаного результату. Однак це насправді не допоможе нам, коли ми хочемо випадкових середніх літер чи слів. Крім того, пам’ятайте, що збережіть усе з малого чи великого регістру для пошуку, тому випадок не буде проблемою.
4.) Якщо у вас є лише кілька слів, метод Кен Тана зробить все, що завгодно, або принаймні після того, як ви трохи його модифікуєте. Однак, маючи лише абзац тексту, ви будете експоненціально створювати більше 1 Мб даних, що перевищує обмеження розміру документа firestore (я знаю, я перевірив це).
5.) Якщо ви могли б поєднати масив, що містить (або якусь форму масивів) з \uf8ff
трюком, у вас може виникнути життєздатний пошук, який не досягає меж. Я намагався будь-яку комбінацію, навіть з картами, і не ходити. Хтось зрозуміє це, опублікуйте його тут.
6.) Якщо ви повинні піти від ALGOLIA та ELASTIC SEARCH, і я зовсім не звинувачую вас, ви завжди можете використовувати mySQL, postSQL або neo4Js в Google Cloud. Їх усі 3 легко налаштувати, і вони мають вільний рівень. У вас була б одна хмарна функція для збереження даних наCreate () та інша функція onCall () для пошуку даних. Просте ... іш. Чому б тоді просто не перейти на mySQL? Дані в реальному часі, звичайно! Коли хтось пише DGraph із веб-шкарпетками для даних у режимі реального часу, порахуйте мене!
Algolia та ElasticSearch були побудовані як dbs, що здійснюють лише пошук, тому немає нічого швидкого ... але ви платите за це. Google, чому ви відводите нас від Google, а ви не дотримуєтесь MongoDB noSQL та дозволяєте здійснювати пошук?
ОНОВЛЕННЯ - Я Створив рішення:
Пізня відповідь, але для тих, хто ще шукає відповідь, скажімо, у нас є колекція користувачів, і в кожному документі колекції є поле "ім'я користувача", тому, якщо ви хочете знайти документ, де ім'я користувача починається з "al" ми можемо зробити щось на кшталт
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Я впевнений, що Firebase незабаром вийде з "string-съдържа", щоб захопити будь-який індекс [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
})
запит, як це
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Якщо ви не хочете користуватися стороннім сервісом, таким як Algolia, Firebase Cloud Functions - чудова альтернатива. Ви можете створити функцію, яка може приймати параметр введення, обробляти через сервер записів і повертати ті, які відповідають вашим критеріям.
Насправді я думаю, що найкращим рішенням для цього в Firestore є розміщення всіх підрядків у масиві та просто виконання запиту array_contains. Це дозволяє виконати відповідність підрядків. Трохи надмірного вмісту для зберігання всіх підрядків, але якщо пошукові терміни короткі, це дуже розумно.
У мене просто була ця проблема і я придумав досить просте рішення.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
IsGreaterThanOrEqualTo дозволяє нам відфільтрувати початок нашого пошуку і додавши "z" в кінець isLessThanOrEqualTo ми обмежимо наш пошук, щоб не перекинутись на наступні документи.
Вибрана відповідь працює лише для точного пошуку та не є природною поведінкою користувачів (пошук "яблука" в "Джо з'їв яблуко сьогодні" не працював).
Я думаю, що відповідь Дана Фейна вище слід оцінити вище. Якщо дані String, які ви шукаєте, короткі, ви можете зберегти всі підрядки рядка в масиві у своєму документі, а потім здійснити пошук через масив за допомогою запиту array_contains Firebase. Документи Firebase обмежені 1 МіБ (1048 576 байт) ( квоти та ліміти 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<Int>) -> 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 "," appl "," pple "," apple "], які повинні охоплювати всі критерії пошуку, які може ввести користувач. Ви можете залишити максимальний розмірStringSize як нульовий, якщо хочете отримати всі результати, проте, якщо є довгий текст, я б рекомендував обмежувати його, перш ніж розмір документа стане надто великим - десь близько 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..<lastCharacter]
substringArray.append(substring)
}
}
characterCounter += 1
if let max = maximumStringSize, characterCounter > max {
break
}
}
print(substringArray)
return substringArray
}
Крок 3: Ви можете використовувати функцію array_contains Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
За допомогою Firestore ви можете реалізувати повний пошук тексту, але це все одно буде коштувати більше читань, ніж це було б інакше, а також вам потрібно буде ввести та індексувати дані певним чином, тож у цьому підході ви можете використовувати хмарні функції firebase для токенізуйте, а потім хешуйте введений текст, вибираючи функцію лінійного хешу, h(x)
яка задовольняє наступному - якщо x < y < z then h(x) < h (y) < h(z)
. Для ознайомлення ви можете вибрати кілька легких бібліотек NLP, щоб низький час початку вашої функції був холодним, що може позбавити зайвих слів із вашого речення. Тоді ви можете запустити запит з меншим та більшим, ніж оператор у Firestore. Під час зберігання ваших даних вам також доведеться переконатися, що ви хеште текст перед його збереженням, а також простий текст будете зберігати так, ніби ви зміните звичайний текст, і хешоване значення також зміниться.
Це працювало для мене чудово, але може спричинити проблеми з продуктивністю.
Зробіть це під час запиту firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Зробіть це у своєму FutureBuilder:
return FutureBuilder(
future: searchResults,
builder: (context, snapshot) {
List<Model> searchResults = [];
snapshot.data.documents.forEach((doc) {
Model model = Model.fromDocumet(doc);
if (searchQuery.isNotEmpty &&
!model.property.toLowerCase().contains(searchQuery.toLowerCase())) {
return;
}
searchResults.add(model);
})
};
На сьогоднішній день існує в основному 3 різних способи вирішення, які були запропоновані експертами як відповіді на питання.
Я спробував їх усіх. Я подумав, що може бути корисним документувати мій досвід роботи з кожним із них.
Метод A: Використання: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Запропоновано @Kuba & @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Запити Firestore можуть виконувати лише фільтри діапазону (>, <,> =, <=) в одному полі. Запити з фільтрами діапазону у кількох полях не підтримуються. Використовуючи цей метод, ви не можете мати оператора діапазону в будь-якому іншому полі на db, наприклад, в полі дати.
A.2. Цей метод НЕ працює для пошуку в декількох полях одночасно. Наприклад, ви не можете перевірити, чи є рядок пошуку в якомусь із поданих файлів (ім'я, примітки та адреса).
Метод B: Використання MAP рядків пошуку з "true" для кожного запису на карті та використання оператора "==" у запитах
Запропонував @Gil Gilbert
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 Очевидно, що цей метод вимагає додаткової обробки щоразу, коли дані зберігаються в db, і що ще важливіше, потрібен додатковий простір для зберігання карти рядків пошуку.
B.2 Якщо запит Firestore має одну умову, подібну до наведеної вище, попередньо не потрібно створювати індекс. Це рішення спрацювало б чудово в цьому випадку.
B.3 Однак якщо запит має іншу умову, наприклад (status === "active",), здається, що індекс необхідний для кожної "пошукової рядок", яку вводить користувач. Іншими словами, якщо користувач шукає "Варення", а інший користувач шукає "Масло", слід заздалегідь створити індекс для рядка "Варення", а інший - "Масло" тощо. Якщо ви не зможете передбачити все можливе рядок пошуку користувачів, це НЕ працює - якщо запит має інші умови!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Метод-C: Використання масиву пошукових рядків та оператора "містить масив"
Запропоновано @Albert Renshaw та продемонстровано @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Подібно до методу B, цей метод вимагає додаткової обробки щоразу, коли дані зберігаються у db, і що ще важливіше, потрібен додатковий простір для зберігання масиву рядків пошуку.
C.2 Запити Firestore можуть включати в складний запит щонайбільше один пункт "містить масив" або "масив містить-будь-який".
Загальні обмеження:
Немає жодного рішення, яке підходить усім. Кожне вирішення проблем має свої обмеження. Сподіваюсь, що вищенаведена інформація може допомогти вам під час відбору між цими обхідними схемами.
Щоб отримати список умов запиту Firestore, перегляньте документацію https://firebase.google.com/docs/firestore/query-data/queries .
Я не пробував https://fireblog.io/blog/post/firestore-full-text-search , який пропонує @Jonathan.
Ми можемо використовувати зворотний галочок для друку значення рядка. Це має працювати:
where('name', '==', `${searchTerm}`)