Conditional Searching in mongodb - mongodb

I have three fields to be searched inside a mongodb database. Sometimes I have to search based on only one field, sometimes two and sometimes three as well. It is something like-if there is only one field value present, then search on the basis of only one field, if two are there then find using like $and and if three all of the three,find using like $and. Is there any single query to make the search working?
the below is the image where i should make selection from

You need to make a query string like below,
let queryString = {};
if (field1 !== undefined) {
queryString.field1 = field1;
}
if (field2 !== undefined) {
queryString.field2 = field2;
}
if (field3 !== undefined) {
queryString.field3 = field3;
}
let documents = await db.find({ $and: [queryString] }).exec();

Related

How to get document which meets one of two conditions [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

Firestore - getting documents fields if included in an array [duplicate]

In Firebase Cloud Firestore, I have "user_goals" in collections and goals may be a predefined goal (master_id: "XXXX") or a custom goal (no "master_id" key)
In JavaScript, I need to write two functions, one to get all predefined goals and other to get all custom goals.
I have got some workaround to get custom goals by setting "master_id" as "" empty string and able to get as below:
db.collection('user_goals')
.where('challenge_id', '==', '') // workaround works
.get()
Still this is not the correct way, I continued to use this for predefined goals where it has a "master_id" as below
db.collection('user_goals')
.where('challenge_id', '<', '') // this workaround
.where('challenge_id', '>', '') // is not working
.get()
Since Firestore has no "!=" operator, I need to use "<" and ">" operator but still no success.
Question: Ignoring these workarounds, what is the preferred way to get docs by checking whether a specific field exists or does not exists?
As #Emile Moureau solution. I prefer
.orderBy(`field`)
To query documents with the field exists. Since it will work with any type of data with any value even for null.
But as #Doug Stevenson said:
You can't query for something that doesn't exist in Firestore. A field needs to exist in order for a Firestore index to be aware of it.
You can't query for documents without the field. At least for now.
The preferred way to get docs where a specified field exists is to use the:
.orderBy(fieldPath)
As specified in the Firebase documentation:
Thus the answer provided by #hisoft is valid. I just decided to provide the official source, as the question was for the preferred way.
Firestore is an indexed database. For each field in a document, that document is inserted into that field's index as appropriate based on your configuration. If a document doesn't contain a particular field (like challenge_id) it will not appear in that field's index and will be omitted from queries on that field. Normally, because of the way Firestore is designed, queries should read an index in one continuous sweep. Prior to the introduction of the != and not-in operators, this meant you couldn't exclude particular values as this would require jumping over sections of an index. This limitation is still encountered when trying to use exclusive ranges (v<2 || v>4) in a single query.
Field values are sorted according to the Realtime Database sort order except that the results can be sorted by multiple fields when duplicates are encountered instead of just the document's ID.
Firestore Value Sort Order
Priority
Sorted Values
Priority
Sorted Values
1
null
6
strings
2
false
7
DocumentReference
3
true
8
GeoPoint
4
numbers
9
arrays
5
Timestamp
10
maps
Inequality !=/<>
This section documents how inequalities worked prior to the release of the != and not-in operators in Sep 2020. See the documentation on how to use these operators. The following section will be left for historical purposes.
To perform an inequality query on Firestore, you must rework your query so that it can be read by reading from Firestore's indexes. For an inequality, this is done by using two queries - one for values less than the equality and another for values greater than the equality.
As a trivial example, let's say I wanted the numbers that aren't equal to 3.
const someNumbersThatAreNotThree = someNumbers.filter(n => n !== 3)
can be written as
const someNumbersThatAreNotThree = [
...someNumbers.filter(n => n < 3),
...someNumbers.filter(n => n > 3)
];
Applying this to Firestore, you can convert this (formerly) incorrect query:
const docsWithChallengeID = await colRef
.where('challenge_id', '!=', '')
.get()
.then(querySnapshot => querySnapshot.docs);
into these two queries and merge their results:
const docsWithChallengeID = await Promise.all([
colRef
.orderBy('challenge_id')
.endBefore('')
.get()
.then(querySnapshot => querySnapshot.docs),
colRef
.orderBy('challenge_id')
.startAfter('')
.get()
.then(querySnapshot => querySnapshot.docs),
]).then(results => results.flat());
Important Note: The requesting user must be able to read all the documents that would match the queries to not get a permissions error.
Missing/Undefined Fields
Simply put, in Firestore, if a field doesn't appear in a document, that document won't appear in that field's index. This is in contrast to the Realtime Database where omitted fields had a value of null.
Because of the nature of NoSQL databases where the schema you are working with might change leaving your older documents with missing fields, you might need a solution to "patch your database". To do this, you would iterate over your collection and add the new field to the documents where it is missing.
To avoid permissions errors, it is best to make these adjustments using the Admin SDK with a service account, but you can do this using a regular SDK using a user with the appropriate read/write access to your database.
This function is recursive, and is intended to be executed once.
async function addDefaultValueForField(queryRef, fieldName, defaultFieldValue, pageSize = 100) {
let checkedCount = 0, pageCount = 1;
const initFieldPromises = [], newData = { [fieldName]: defaultFieldValue };
// get first page of results
console.log(`Fetching page ${pageCount}...`);
let querySnapshot = await queryRef
.limit(pageSize)
.get();
// while page has data, parse documents
while (!querySnapshot.empty) {
// for fetching the next page
let lastSnapshot = undefined;
// for each document in this page, add the field as needed
querySnapshot.forEach(doc => {
if (doc.get(fieldName) === undefined) {
const addFieldPromise = doc.ref.update(newData)
.then(
() => ({ success: true, ref: doc.ref }),
(error) => ({ success: false, ref: doc.ref, error }) // trap errors for later analysis
);
initFieldPromises.push(addFieldPromise);
}
lastSnapshot = doc;
});
checkedCount += querySnapshot.size;
pageCount++;
// fetch next page of results
console.log(`Fetching page ${pageCount}... (${checkedCount} documents checked so far, ${initFieldPromises.length} need initialization)`);
querySnapshot = await queryRef
.limit(pageSize)
.startAfter(lastSnapshot)
.get();
}
console.log(`Finished searching documents. Waiting for writes to complete...`);
// wait for all writes to resolve
const initFieldResults = await Promise.all(initFieldPromises);
console.log(`Finished`);
// count & sort results
let initializedCount = 0, errored = [];
initFieldResults.forEach((res) => {
if (res.success) {
initializedCount++;
} else {
errored.push(res);
}
});
const results = {
attemptedCount: initFieldResults.length,
checkedCount,
errored,
erroredCount: errored.length,
initializedCount
};
console.log([
`From ${results.checkedCount} documents, ${results.attemptedCount} needed the "${fieldName}" field added.`,
results.attemptedCount == 0
? ""
: ` ${results.initializedCount} were successfully updated and ${results.erroredCount} failed.`
].join(""));
const errorCountByCode = errored.reduce((counters, result) => {
const code = result.error.code || "unknown";
counters[code] = (counters[code] || 0) + 1;
return counters;
}, {});
console.log("Errors by reported code:", errorCountByCode);
return results;
}
You would then apply changes using:
const goalsQuery = firebase.firestore()
.collection("user_goals");
addDefaultValueForField(goalsQuery, "challenge_id", "")
.catch((err) => console.error("failed to patch collection with new default value", err));
The above function could also be tweaked to allow the default value to be calculated based on the document's other fields:
let getUpdateData;
if (typeof defaultFieldValue === "function") {
getUpdateData = (doc) => ({ [fieldName]: defaultFieldValue(doc) });
} else {
const updateData = { [fieldName]: defaultFieldValue };
getUpdateData = () => updateData;
}
/* ... later ... */
const addFieldPromise = doc.ref.update(getUpdateData(doc))
The solution I use is:
Use: .where('field', '>', ''),
Where "field" is the field we are looking for!
As you correctly state, it is not possible to filter based on !=. If possible, I would add an extra field to define the goal type. It is possible to use != in security rules, along with various string comparison methods, so you can enforce the correct goal type, based on your challenge_id format.
Specify the goal type
Create a type field and filter based on this field.
type: master or type: custom and search .where('type', '==', 'master') or search for custom.
Flag custom goals
Create a customGoal field which can be true or false.
customGoal: true and search .where('customGoal', '==', true) or false (as required).
Update
It is now possible to perform a != query in Cloud Firestore
Firestore does pick up on boolean, which is a thing! and can be orderBy'd.
So often, like now, for this, I add this into the array-pushing from onSnapshot or get, use .get().then( for dev...
if (this.props.auth !== undefined) {
if (community && community.place_name) {
const sc =
community.place_name && community.place_name.split(",")[1];
const splitComma = sc ? sc : false
if (community.splitComma !== splitComma) {
firebase
.firestore()
.collection("communities")
.doc(community.id)
.update({ splitComma });
}
const sc2 =
community.place_name && community.place_name.split(",")[2];
const splitComma2 =sc2 ? sc2 : false
console.log(splitComma2);
if (community.splitComma2 !== splitComma2) {
firebase
.firestore()
.collection("communities")
.doc(community.id)
.update({
splitComma2
});
}
}
This way, I can query with orderBy instead of where
browseCommunities = (paginate, cities) => {
const collection = firebase.firestore().collection("communities");
const query =
cities === 1 //countries
? collection.where("splitComma2", "==", false) //without a second comma
: cities //cities
? collection
.where("splitComma2", ">", "")
.orderBy("splitComma2", "desc") //has at least two
: collection.orderBy("members", "desc");
var shot = null;
if (!paginate) {
shot = query.limit(10);
} else if (paginate === "undo") {
shot = query.startAfter(this.state.undoCommunity).limit(10);
} else if (paginate === "last") {
shot = query.endBefore(this.state.lastCommunity).limitToLast(10);
}
shot &&
shot.onSnapshot(
(querySnapshot) => {
let p = 0;
let browsedCommunities = [];
if (querySnapshot.empty) {
this.setState({
[nuller]: null
});
}
querySnapshot.docs.forEach((doc) => {
p++;
if (doc.exists) {
var community = doc.data();
community.id = doc.id;
It is not an ideal solution, but here is my workaround when a field does not exist:
let user_goals = await db.collection('user_goals').get()
user_goals.forEach(goal => {
let data = goal.data()
if(!Object.keys(data).includes(challenge_id)){
//Perform your task here
}
})
Note that it would impact your read counts a lot so only use this if you have small collection or can afford the reads.

How can I apply conditional filtering to MongoDB documents?

I am having a really hard time figuring out how to apply multiple levels of filtering to my mongoDB documents (not sure if this phrasing is correct).
I am trying to create an app that will allow users to perform a search and retrieve only those documents that match the filters they have chosen to apply. A user might chose to apply only one filter or combine multiple filters.
For example, the user might be looking for a house. The available filters could be location, size and type. If the user applies the location filter with a value of ‘London’, they should get only those houses available in London. If they choose to combine the above location with the type filter with a value of ‘2-bedroom-apartment’, they should get all 2-bedroom apartments available in London.
How can I make sure that the results are conditionally filtered, depending on the filters that the user has applied?
I think I am supposed to use $match, but I don’t understand if I can use multiple queries with it.
What I have come up with so far is the following:
const getFilteredData = async(req, res) => {
try {
const { filter1, filter2, filter3 } = req.query;
const filteredData = await dataModel.aggregate([
{$match:{
$and:[{filter1:filter1},{filter2: filter2}, {filter3:filter3}] //1st option: all of the filters are applied by the user
}}
])
res.status(201).json({data: filteredData});
}
catch(err) {
res.status(404).json({message: err.message});
}
}
With the above code, the results are filtered only when all 3 filters are being applied. How can I cater to different combinations of filters being applied by the user (only one filter, filter1 & filter3 combined etc)?
Any help will be massively appreciated.
Assuming req.query can be {name: "Coat", city: "Athens"} You can do something like:
const getFilteredData = async(req, res) => {
try {
const filtersArr = [];
for (const filterKey of ['name', 'city', 'category']) {
if (req.query[filterKey]) {
const thisFilter = {};
thisFilter[filterKey] = req.query[filterKey];
filtersArr.push(thisFilter);
}
}
console.log(filtersArr)
const filteredData = await filteredDataModel.aggregate([
{$match:{
$and: filtersArr //1st option: all of the filters are applied by the user
}}
])
res.status(201).json({data: filteredData});
}
catch(err) {
res.status(404).json({message: err.message});
}
}
You can also use the original req.query like this:
const filteredData = await filteredDataModel.find(req.query)
But iterating using the code allows you to validate the keys that you want...

Firestore flutter query on multi fields with OR principle [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

Mongodb - How to find string in multiple fields?

Using Pymongo for this scenario.
I have User that has email, first_name, last_name.
I am using this Pymongo snippet:
user_found = users.find({'$or':[
{'email':{'$regex':searchString, '$options':'i'}},
{'first_name':{'$regex':searchString, '$options':'i'}},
{'last_name':{'$regex':searchString, '$options':'i'}}]})
this example works, if I want to find searchString in:
email, or
first_name, or
last_name
now I need to also find searchString in first_name + last_name combined.
how can I do that?
Is there a way in mongo, through the query, to combine the two into a "fullname" then search the fullname?
Easiest way is to add an array field and populate it with all of the variants that you want to search on. Index that array field.
That way you only need one index and your search across all fields is simple and doesn't change when you want to search on some new search variant. You can also normalize the text you put into the search array, for example, lower casing it, removing punctuation etc.
See https://stackoverflow.com/q/8206188/224370
Edit: MongoDB's documentation now covers keyword search and the new full-text search feature.
I had the same problem. I already used regex string search, so my solution was:
generate a helper collection. Here I combine all relevant string, like:
{
search_field: email + " " + first_name + " " + last_name,
ref_id: (id to real object)
}
I then use a regexp to creat what i allow to be looked for:
// logic found here: http://stackoverflow.com/questions/10870372/regex-match-if-string-contain-all-the-words-or-a-condition
var words = query.split(/[ ,]+/);
var regstr = "";
for (var i = 0; i < words.length; ++i) {
var word = words[i];
regstr += "(?=.*?\\b" + word + ")";
}
regstr += "^.*$";
regex = new RegExp(regstr, "i");
This then also gives some flexibility about the order.
Searching is not the fastest, since it still uses regex on all elements, but it is ok for me. (I also index the collection on search_field.
Getting results also becomes a nested call, since first you need to get the _ids you really want, and then you can query for them like so:
connection.find({ "search_field" : regex }, { _id: 0, ref_id: 1 }, { limit: limit, skip: start }).toArray(function (err, docs) {
if (err) throw err;
// map array of documents into simple array of ids
var ids = [];
for (var i = 0; i < docs.length; ++i)
{
var doc = docs[i];
ids.push(doc.ref_id);
}
if (ids.length > 0)
MongooseEmails.find({ "_id": { $in: ids } }, function (err, docres) {
if (err) throw err;
res.send(JSON.stringify(docsres));
});
else
res.send("");
});
This is edited code.. perhaps there is a syntax error, generally, it is working for me.