간단한 검색 필드를 추가하고 싶습니다. 다음과 같은 것을 사용하고 싶습니다.
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}`)