Looking to construct a query against a firestore collection ('parent') where the documents have a nested map (2 logical levels deep). Specifically when the first map has dynamic keys which are not known at the time of running the query. As an example:
Document 1
{
codes: {
abc: {
id: 'hi'
},
def: {
id: 'there'
}
}
}
Document 2
{
codes: {
ghi: {
id: 'you'
},
zmp: {
id: 'guys'
}
}
}
What I would like to do is have a WHERE clause that takes a wildcard for a key in the document. ie.
firestore.collection('parent').WHERE('codes.*.id', '==', 'there')
// Results in Document 1
or
firestore.collection('parent').WHERE('codes.*.id', '==', 'you')
// Results in Document 2
Is there any way to achieve this behavior without having to resort to generating subcollection documents to be used for indexing, or polluting the document itself with a second map that maps ids to codes.
== Not ideal solution 1 (subcollections) ==
Build out the server so that when these documents are filed, a subcollection ('child') is maintained with documents that contain the related information. As an example filing Document 1 above would require filing two documents in the child subcollection:
{
id: 'hi'
code: 'abc'
}
{
id: 'there'
code: 'def'
}
Now we can query for the id we want, and get the parent reference, and follow that all the way back to the parent...
firestore.collectionGroup('child').where('id', '==', 'there')
.get()
.then(snapshot => {
for(const doc of snapshot.docs) {
return doc.ref.parent.parent
}
return Promise.reject('no parents, how sad.')
})
.then(ref => ref.get())
.then(snapshot => snapshot.data())
.then(parent => {
// Thank goodness, the parent is Document 1!
}
The downside to this is maintenance of the sub collections, as well as a number of extra operations against firestore.
== Not ideal solution 2 (model pollution) ==
Another way to achieve this is to implement another map or an array in the document itself which simply contains the ids which would then let us query on those values. ie
{
codes: {
abc: {
id: 'hi'
},
def: {
id: 'there'
}
},
codeids:['hi','there']
}
Although this is easy to query:
.WHERE('codeids', 'ARRAY CONTAINS', 'hi')
I don't like the idea of adding fields that are not meaningful to the consumer of the document (the purpose of the field only being to facilitate a documents ability to be queried due to system constraints)
Open to suggestions!
Related
I want to be able to update an array of objects where each object has a new unique value assigned to it.
Here is a simplified example of what I'm doing. items is an array of my collection items.
let items = [{_id: '903040349304', number: 55}, {_id: '12341244', number: 1166}, {_id: '667554', number: 51115}]
I want to assign a new number to each item, and then update it in collection:
items = items.map(item => {
item.number = randomInt(0, 1000000);
return item;
})
What would be the best way to update the collection at once? I know that I could do it in forEach instead of map, how ever this seems as a dirty way of doing it, as it won't do the bulk update.
items.forEach(async (item) => {
await this.itemModel.update({_id: item._id}, {number: randomInt(0, 1000000)})
});
I've checked the updateMany as well but my understanding of it is that it's only used to update the documents with a same new value - not like in my case, that every document has a new unique value assigned to it.
After a bit of thinking, I came up with this solution using bulkWrite.
const updateQueries = [];
items.forEach(async (item) => {
updateQueries.push({
updateOne: {
filter: { _id: item._id },
update: { number: item.number },
},
});
});
await this.itemModel.bulkWrite(updateQueries);
About bulkWrite
Sends multiple insertOne, updateOne, updateMany, replaceOne,
deleteOne, and/or deleteMany operations to the MongoDB server in one
command. This is faster than sending multiple independent operations
(like) if you use create()) because with bulkWrite() there is only one
round trip to MongoDB.
You can call an aggregate() to instantly update them without needing to pull them first:
Step1: get a random number with mongoDb build in $rand option which returns a number between 0 and 1
Step2: $multiply this number by 1000000 since that is what you defined ;)
Step3: use another $set with $floor to remove the decimal portion
YourModel.aggregate([
{
'$set': {
'value': {
'$multiply': [
{
'$rand': {}
}, 1000000
]
}
}
}, {
'$set': {
'value': {
'$floor': '$value'
}
}
}
])
Here a picture of how that looks in mongo Compass as a proof of it working:
I have a Mongoose abTest document that has two fields:
status. This is a string enum and can be of type active, inactive or draft.
validCountryCodes. This is an array of strings enums (GB, EU, AU etc). By default, it will be empty.
In the DB, at any one time, I only want there to be one active abTest for each validCountryCode so I'm performing some validation prior to creating or editing a new abTest.
To do this, I've written a function that attempts to count the number of documents that have a status of active and that contain one of the countryCodes.
The function will then return if the count is more than one. If so, I will throw a validation error.
if (params.status === 'active') {
const activeTestForCountryExists = await checkIfActiveAbTestForCountry(
validCountryCodes,
);
if (params.activeTestForCountryExists) {
throw new ValidationError({
message: 'There can only be one active test for each country code.',
});
}
}
const abTest = await AbTest.create(params);
checkIfActiveAbTestForCountry() looks like this:
const checkIfActiveAbTestForCountry = async countryCodes => {
const query = {
status: 'active',
};
if (
!countryCodes ||
(Array.isArray(countryCodes) && countryCodes.length === 0)
) {
query.validCountryCodes = {
$eq: [],
};
} else {
query.validCountryCodes = { $in: [countryCodes] };
}
const count = await AbTest.countDocuments(query);
return count > 0;
};
The count query should count not only exact array matches, but for any partial matches.
If in the DB there is an active abTest with a validCountryCodes array of ['GB', 'AU',], the attempting to create a new abTest with ['GB' should fail. As there is already a test with GB as a validCountryCode.
Similarly, if there is a test with a validCountryCodes array of ['AU'], then creating a test with validCountryCodes of ['AU,'NZ'] should also fail.
Neither is enforced right now.
How can I do this? Is this possible write a query that checks for this?
I considered iterating over params.validCountryCodes and counting the docs that include each, but this seems like bad practice.
take a look at this MongoDB documantation.
As I understood what you need is to find out if there is any document that contains at least one of the specified countryCodes and it has active status. then your query should look like this:
{
status: 'active',
$or: [
{ validCountryCodes: countryCodes[0] },
{ validCountryCodes: countryCodes[1] },
// ...
]
}
note that counting documents is not an efficient manner to check if a document exists or not, instead use findOne with only one field being projected.
You are using the correct mongo-query for your requirement. Can you verify the actual queries executed from your application is the same? Check here
{ status: 'active', validCountryCodes: { $in: [ countryCodes ] } }
For eg; below query :
{ status: 'active', validCountryCodes: { $in: ['GB' ] } }
should match document :
{ status: 'active', validCountryCodes: ['AU','GB'] }
How can I retrieve all the documents who match a child in a data structure like this:
{
[
id: {
name: "name",
products: {
items: [
productName: "this is the product Name"
]
}
}
]
}
The parameter i try to compare is the one inside products.items[0].productName.
this is how i tried but it does not retrieve anything:
try{
var data = [];
const byName = await dbRef.where('producto.items[0].producto', '==', req.params.nombre).get();
console.log(byName);
if (byName.empty) {
console.log('No matching documents.');
res.send('No matching documents.');
return;
}
byName.forEach(doc => {
console.log(doc.id, '=>', doc.data());
data.push(doc.data());
});
res.send(data);
}catch(err){
res.send(err);
}
If you want to search across all items in the items array for one that matches the value you have, you can use the array-contains operator:
dbRef.where('producto.items', 'array-contains', { producto: req.params.nombre})
But note that this only works if the array only contains the producto field in each item. The reason is that array-contains (and other array-level operators) work on complete items only.
So if the items in producto.items have multiple subfields, and you want to match on one/some of them, you can't use array-contains. In that case, you're options are:
Store the items names in a separate/additional array field product-names and then query on that with array-contains.
Store the array items in a subcollection and query that.
Use a map instead of an array to store these values. This will generate many extra indexes though, which both adds to your storage cost, and may get you to the limit on the number of indexes.
Product
.findAll()
.then((products) => {
/* Perform operations */
}
The above query returns an array of products. For eg,
[
{
name: Mobile,
price: 10000
},
{
name: Laptop,
price: 20000
},
]
I need to make some changes to the products array (add new fields based on the values of existing fields, and delete those existing fields). I tried few methods, but none are working:
products.forEach((product) => {
product.[updatedPrice] = updatedPrice;
delete product[price];
}
Array.map() is also not working.
The following works, but I don't know the working behind, and why it is happening. Also, how to delete a field using the same.
products.forEach((product) => {
product.set('updatedPrice', updatedPrice, {strict: false})
}
The thing here to note is that in .then((products) products object here is not a JSON object but a Mongoose Document object and the set method you are using is defined by the mongoose. You can refer it from here https://mongoosejs.com/docs/guide.html#strict
We can do 2 things:
use lean() (returns a plain js object)
Product.findAll().lean().then((products) => {
/* Perform operations*/
});
use toJSON() method on products object to convert mongoose object to js object
Product.findAll().then((products) => {
products = products.toJSON();
/* Perform operations*/
});
Thanks
The ReThinkDB docs mention:
Documents can be filtered in a variety of ways—ranges, nested values, boolean conditions, and the results of anonymous functions.
Say I have:
{
name: 'Joe'
orders: [
{
id: 123456,
date: '2016-01-19T09:12:48.898000+00:00'
}
]
}
And I would like to retrieve users who have an order with id 123456 in their orders
Per the docs, I have tried using...
(long list of things cut out, now I've found the actual answer)
But I get no results.
This works too:
r.db('myapp').table('people').filter(function(person) {
return person('orders').map(function(order){
return order('id')
}).contains(123456)
})
Got it:
r.db('myapp').table('people').filter(function(person){
return person("orders").contains(function (order) {
return order('id').eq(123456);
})
})