간단한 검색 필드를 추가하고 싶습니다. 다음과 같은 것을 사용하고 싶습니다.
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를 사용할 수 있지만, 나는 또한 단지 경우 FireStore로 전환해야 대부분의 질의가 아니라 할 중포 기지와 함께 사용할 수
제한이 진행되는 한 Kuba의 대답은 사실이지만 세트와 같은 구조로 부분적으로 에뮬레이션 할 수 있습니다.
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
이제 다음으로 쿼리 할 수 있습니다.
collectionRef.where('terms.tennis', '==', true)
이는 Firestore가 모든 필드에 대해 자동으로 색인을 생성하기 때문에 작동합니다. 불행히도 Firestore는 복합 색인을 자동으로 생성하지 않기 때문에 복합 쿼리에 대해 직접 작동하지 않습니다.
단어 조합을 저장하여이 문제를 해결할 수 있지만 속도가 너무 빠릅니다.
여전히 아웃 보드 전체 텍스트 검색을 사용하는 것이 좋습니다 .
where
@Kuba의 답변에 동의하지만 접두사 검색에 완벽하게 작동하려면 작은 변경 사항을 추가해야합니다. 여기 나를 위해 일한 것
이름으로 시작하는 레코드 검색 queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
\uf8ff
쿼리에 사용 된 문자 는 유니 코드 범위에서 매우 높은 코드 포인트입니다 (PUA (Private Usage Area) 코드). 유니 코드에서 대부분의 일반 문자 뒤에 있기 때문에 쿼리는로 시작하는 모든 값과 일치합니다 queryText
.
Firebase는 문자열 내에서 용어 검색을 명시 적으로 지원하지 않지만
Firebase는 (현재) 귀하의 사례와 다른 많은 문제를 해결할 다음을 지원합니다.
2018 년 8 월부터 array-contains
쿼리 를 지원 합니다. 참조 : https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
이제 모든 주요 용어를 필드로 배열에 설정 한 다음 'X'가 포함 된 배열이있는 모든 문서를 쿼리 할 수 있습니다. 논리 AND를 사용하여 추가 쿼리에 대한 추가 비교를 수행 할 수 있습니다. (Firebase 는 현재 여러 배열 포함 쿼리에 대한 복합 쿼리를 기본적으로 지원하지 않기 때문입니다. 'AND'정렬 쿼리는 클라이언트 측에서 수행해야합니다.)
이 스타일로 배열을 사용하면 동시 쓰기에 최적화 될 수 있습니다. 일괄 요청을 지원하는지 테스트하지 않았지만 (문서는 말하지 않음) 공식 솔루션이기 때문에 그렇게 할 것입니다.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
일반적으로 양쪽에서 공백, 구두점 등으로 구분 된 전체 용어를 의미하는 것으로 이해됩니다. 당신이 구글 경우 abcde
지금 당신은 단지 같은 것들에 대한 결과를 찾을 수 %20abcde.
또는 ,abcde!
아니지만을 abcdefghijk..
. 도 확실히 전체 알파벳 입력 된 훨씬 더 일반적인 것은 인터넷에서 찾을 수있다하지만, 검색은 고립 된 ABCDE위한거야 * ABCDE위한 것이 아닙니다
'contains'
나는 많은 프로그래밍 언어에서 내가 말하는 것을 정확히 의미 하는 단어로 오도했을 것입니다 . '%searchTerm%'
SQL 관점에서도 마찬가지 입니다.
당 경우 FireStore 워드 프로세서 , 클라우드 경우 FireStore은 기본 인덱싱을 지원하지 않거나 문서에서 텍스트 필드를 검색합니다. 또한 클라이언트 측에서 필드를 검색하기 위해 전체 컬렉션을 다운로드하는 것은 실용적이지 않습니다.
Algolia 및 Elastic Search 와 같은 타사 검색 솔루션 이 권장됩니다.
1.) \uf8ff
다음과 같은 방식으로 작동합니다.~
2.) where 절 또는 start end 절을 사용할 수 있습니다.
ref.orderBy('title').startAt(term).endAt(term + '~');
정확히 동일
ref.where('title', '>=', term).where('title', '<=', term + '~');
3) 당신이 반대하면 아니, 그것은 작동하지 않습니다 startAt()
및 endAt()
모든 조합에서, 그러나, 당신이 반전되는 제 2 검색 필드를 생성하고, 그 결과를 조합하여 동일한 결과를 얻을 수 있습니다.
예 : 먼저 필드를 만들 때 필드의 반전 된 버전을 저장해야합니다. 이 같은:
// 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.) 단어가 몇 개 밖에없는 경우 Ken Tan의 방법 은 원하는 모든 것을 수행하거나 최소한 약간 수정 한 후에 수행합니다. 그러나 텍스트 단락만으로도 1MB 이상의 데이터를 기하 급수적으로 생성 할 수 있으며 이는 firestore의 문서 크기 제한보다 큽니다 (알고 있습니다. 테스트했습니다).
5.) 배열 포함을 결합 할 수있는 경우 (또는 배열의 일부 형태)을 \uf8ff
트릭 과 한계에 도달하지 않는 실행 가능한 검색이있을 수 있습니다. 나는지도와 함께 모든 조합을 시도했고, 가도 안된다. 누구나 이것을 알아 내고 여기에 게시하십시오.
6.) ALGOLIA 및 ELASTIC SEARCH에서 벗어나야하고 내가 당신을 탓하지 않는다면 Google Cloud에서 항상 mySQL, postSQL 또는 neo4J를 사용할 수 있습니다. 세 가지 모두 설정이 쉬우 며 프리 티어가 있습니다. 데이터를 저장하는 클라우드 함수 onCreate ()와 데이터 검색을위한 onCall () 함수가 있습니다. 단순하다 ... 그렇다면 mySQL로 전환하지 않는 이유는 무엇입니까? 물론 실시간 데이터! 누군가 가 실시간 데이터를 위해 websocks 로 DGraph를 작성하면 저를 세어보세요!
Algolia와 ElasticSearch는 검색 전용 db로 구축되었으므로 빠르지는 않지만 비용을 지불하면됩니다. Google, 왜 우리를 Google에서 멀어지게하고 MongoDB noSQL을 따르고 검색을 허용하지 않습니까?
업데이트-솔루션을 만들었습니다.
늦은 답변이지만 여전히 답변을 찾고있는 모든 사용자를 위해 사용자 컬렉션이 있고 컬렉션의 각 문서에 "username"필드가 있으므로 사용자 이름이 "al"로 시작하는 문서를 찾고 싶다면 우리는 다음과 같이 할 수 있습니다
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Firebase가 곧 문자열에 색인 [i] startAt을 캡처하기 위해 "string-contains"와 함께 나올 것이라고 확신합니다.하지만 웹을 조사한 결과 다른 사람이 다음과 같은 데이터를 설정 한이 솔루션을 발견했습니다. 이
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 가 훌륭한 대안입니다. 입력 매개 변수를 수신하고 서버 측 레코드를 처리 한 다음 기준과 일치하는 항목을 반환 할 수있는 함수를 만들 수 있습니다.
나는 방금이 문제가 있었고 매우 간단한 해결책을 찾았습니다.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
isGreaterThanOrEqualTo를 사용하면 검색 시작 부분을 필터링하고 isLessThanOrEqualTo 끝에 "z"를 추가하여 다음 문서로 롤오버되지 않도록 검색을 제한합니다.
선택한 답변은 정확한 검색에만 적용되며 자연스러운 사용자 검색 동작이 아닙니다 ( "Joe ate an apple today"에서 "apple"검색은 작동하지 않음).
위의 Dan Fein의 대답은 더 높은 순위가되어야한다고 생각합니다. 검색중인 문자열 데이터가 짧은 경우 문서의 배열에 문자열의 모든 하위 문자열을 저장 한 다음 Firebase의 array_contains 쿼리를 사용하여 배열을 검색 할 수 있습니다. Firebase 문서는 1MiB (1,048,576 바이트)로 제한됩니다 ( Firebase 할당량 및 한도 )로 되며 문서에 약 1 백만 문자가 저장됩니다 (1 문자 ~ = 1 바이트라고 생각합니다). 문서가 1 백만 마크에 가까워지지 않는 한 부분 문자열을 저장하는 것이 좋습니다.
사용자 이름을 검색하는 예 :
1 단계 : 프로젝트에 다음 문자열 확장을 추가합니다. 이렇게하면 문자열을 하위 문자열로 쉽게 나눌 수 있습니다. ( 여기에서 찾았습니다 ).
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 "], 사용자가 입력 할 수있는 모든 검색 기준을 포함해야합니다. 모든 결과를 원하면 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..<lastCharacter]
substringArray.append(substring)
}
}
characterCounter += 1
if let max = maximumStringSize, characterCounter > max {
break
}
}
print(substringArray)
return substringArray
}
3 단계 : Firebase의 array_contains 함수를 사용할 수 있습니다!
[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 : 맵의 각 항목에 대해 "true"인 검색 문자열의 MAP 사용 및 쿼리에서 "=="연산자 사용
@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")) 사용자가 입력하는 각 "검색 문자열"에 대해 인덱스가 필요한 것 같습니다. 즉, 사용자가 "Jam"을 검색하고 다른 사용자가 "Butter"를 검색하면 문자열 "Jam"에 대한 인덱스를 미리 만들고 "Butter"에 대한 인덱스를 만들어야합니다. 가능한 모든 것을 예측할 수없는 경우 사용자의 검색 문자열은 작동하지 않습니다-쿼리에 다른 조건이있는 경우!
.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 Method-B와 유사하게,이 방법은 데이터가 db에 저장 될 때마다 추가 처리가 필요하며 더 중요한 것은 검색 문자열 배열을 저장하기위한 추가 공간이 필요하다는 것입니다.
C.2 Firestore 쿼리는 복합 쿼리에 "array-contains"또는 "array-contains-any"절을 최대 하나만 포함 할 수 있습니다.
일반 제한 :
모든 것에 적합한 솔루션은 없습니다. 각 해결 방법에는 제한 사항이 있습니다. 위의 정보가 이러한 해결 방법 사이의 선택 과정에서 도움이되기를 바랍니다.
Firestore 쿼리 조건 목록은 https://firebase.google.com/docs/firestore/query-data/queries 문서를 확인 하세요 .
@Jonathan이 제안한 https://fireblog.io/blog/post/firestore-full-text-search 시도하지 않았습니다 .
백틱을 사용하여 문자열 값을 출력 할 수 있습니다. 이것은 작동합니다.
where('name', '==', `${searchTerm}`)