I have a problem try to query a document using DateTime object
document in path /users/{userId}/payments/{paymentDoc}
which I have document structure like this
Document data: {
banking: '',
isPaid: false,
endDate: Timestamp { _seconds: 1592179200, _nanoseconds: 0 },
startDate: Timestamp { _seconds: 1590969600, _nanoseconds: 0 }, **June 1, 2020 at 7:00:00 AM UTC+7**
totalIncome: 100
}
where I try to query document is
firstDayOfMonth = new Date(2020, 05, 01) **maybe wrong here?**
userPaymentRef = db.collection('users')
.doc(userId)
.collection('payments')
.where('startDate', '==', firstDayOfMonth)
.get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
return true;
} else {
console.log("No such document!");
return false;
}
}).catch(function (error) {
console.log("Error getting document:", error);
});
But in firebase functions log its said No such document!
I try to log the timestamp of startDate I stored and timestamp of date I want to query. It's the same, but it's said 'No such document' is my query is wrong? or DateTime I want to query is wrong?
Edit:
Functions log
the timestamp of document I store and the timestamp from DateTime is matched but can't find document
If you have a timestamp field field in a document, it can only be matched exactly by other Date or Timestamp objects. The precision down to the nanosecond must match. Two timestamps with the same day but different times of the day are not equal.
Also bear in mind that the timestamp objects don't encode a timezone - they always use UTC. If you use a Date object, it must be created with the exact same moment in time as the timestamp in order to get an equality match. Date objects that don't specify precise moment in time will use the local computer's timestamp, which is definitely not guaranteed to match the time of day on any other computer.
The bottom line is this: if you want two timestamps to be equal, they must both represent the exact same moment in time, measured to the nanosecond.
I define a schema like this:
const query = new GraphQLObjectType({
name: 'Query',
fields: {
quote: {
type: queryType,
args: {
id: { type: QueryID }
},
},
},
});
const schema = new GraphQLSchema({
query,
});
The QueryID is a customised scalar type.
const QueryID = new GraphQLScalarType({
name: 'QueryID',
description: 'query id field',
serialize(dt) {
// value sent to the client
return dt;
},
parseLiteral(ast) {
if (ast.kind === 'IntValue') {
return Number(ast.value);
}
return null;
},
parseValue(v) {
// value from the client
return v;
},
});
client query
query {
quote(queryType: 1)
}
I found that the parseValue method is not called when clients send query to my server. I can see parseLiteral is called correctly.
In most of the document I can find, they use gql to define schema and they need to put scalar QueryID in their schema definition. But in my case, I am using GraphQLSchema object for schema. Is this the root cause of that? If yes, what is the best way to make it works? I don't want to switch to gql format because I need to construct my schema at runtime.
serialize is only called when sending the scalar back to the client in the response. The value it receives as a parameter is the value returned in the resolver (or if the resolver returned a Promise, the value the Promise resolved to).
parseLiteral is only called when parsing a literal value in a query. Literal values include strings ("foo"), numbers (42), booleans (true) and null. The value the method receives as a parameter is the AST representation of this literal value.
parseValue is only called when parsing a variable value in a query. In this case, the method receives as a parameter the relevant JSON value from the variables object submitted along with the query.
So, assuming a schema like this:
type Query {
someField(someArg: CustomScalar): String
someOtherField: CustomScalar
}
serialize:
query {
someOtherField: CustomScalar
}
parseLiteral:
query {
someField(someArg: "something")
}
parseValue:
query ($myVariable: CustomScalar) {
someField(someArg: $myVariable)
}
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...!
}
}
}
});
}
I have the following node js code that should list all items from a DynamoDB table,
import * as dynamoDbLib from "../../libs/dynamodb-lib";
import { success, failure } from "../../libs/response-lib";
export async function main(event, context) {
const params = {
TableName: "brands",
KeyConditionExpression: "brandId = :brandId",
ExpressionAttributeValues: {
":brandId": ''
}
};
try {
const result = await dynamoDbLib.call("query", params);
return success(result.Items);
} catch (e) {
console.log(e);
return failure({ status: false });
}
}
The id is in uuid format which when inserted from my node js was imported by using:
import uuid from "uuid";
then inserted to the table like:
brandId: uuid.v1()
Now when I query the items in the table I can only get only one record if and only if I hard coded the uuid of a record in the expression attribute value (either the KeyConditions or KeyConditionExpression parameter must be specified). So I thought about adding a regular expression to match all the uuids, my regular expression was copied from some solutions on the web but it didn't work, it was like the following:
[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}
and
\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b
and I have tried other examples but non of them worked, is it right to add a regular expression to get all the items, and if so what is the right regex for it?
Use a Scan operation to get all items in a table.
From the AWS Developer Guide:
The scan method reads every item in the table and returns all the data in the table. You can provide an optional filter_expression, so that only the items matching your criteria are returned. However, the filter is applied only after the entire table has been scanned.
I want to sort my query result by the Float value. But the value stored in MongoDB is type String, Can I parse the String to Float and sort it dynamically? Just like complex sort.
The following are parts of my schema and sort code:
Schema:
var ScenicSpotSchema = new Schema({
...
detail_info: {
...
overall_rating: String,
...
},
});
Sort function:
ScenicSpot.find({'name': new RegExp(req.query.keyword)}, )
.sort('-detail_info.overall_rating')
.skip(pageSize * pageNumber)
.limit(pageSize)
.exec(function (err, scenicSpots) {
if (err) {
callback(err);
} else {
callback(null, scenicSpots);
}
});
Any kind of help and advice is appreciated. :)
.sort mongoose do not support converting data type. see: http://mongoosejs.com/docs/api.html#query_Query-sort
It only accept column names and order.
There are two path to acheive your goal:
use mapreduce in mongo, first convert type, and the sort
retrieve all data from database, and sort it in your node.js program.
But both are terrible and ugly
if you wanna sort a String column but parse it as Float. This action would scan all data in that collection, and can not use index. It's very very slow action.
So I think the fastest and correct operation is convert the String column to Float in your mongodb database. And then you can use normal .sort('-detail_info.overall_rating') to get things done.
user collation after sort query it will sort string float number in ascending and descending order both correctly.
cenicSpot.find({'name': new RegExp(req.query.keyword)}, )
.sort('-detail_info.overall_rating')
.collation({ locale: "en-US", numericOrdering: true })
.skip(pageSize * pageNumber)
.limit(pageSize)
.exec(function (err, scenicSpots) {
if (err) {
callback(err);
} else {
callback(null, scenicSpots);
}
});