How do I query a document by a nested value in RethinkDB? - nested

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);
})
})

Related

Firestore Query Nested Map

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!

Nodejs Elasticsearch query default behaviour

On a daily basis, I'm pushing data (time_series) to Elasticsearch. I created an index pattern, and my index have the name: myindex_* , where * is today date (an index pattern has been setup). Thus after a week, I have: myindex_2022-06-20, myindex_2022-06-21... myindex_2022-06-27.
Let's assume my index is indexing products' prices. Thus inside each myindex_*, I have got:
myindex_2022-06-26 is including many products prices like this:
{
"reference_code": "123456789",
"price": 10.00
},
...
myindex_2022-06-27:
{
"reference_code": "123456789",
"price": 12.00
},
I'm using this query to get the reference code and the corresponding prices. And it works great.
const data = await elasticClient.search({
index: myindex_2022-06-27,
body: {
query: {
match: {
"reference_code": "123456789"
}
}
}
});
But, I would like to have a query that if in the index of the date 2022-06-27, there is no data, then it checks, in the previous index 2022-06-26, and so on (until e.g. 10x).
Not sure, but it seems it's doing this when I replace myindex_2022-06-27 by myindex_* (not sure it's the default behaviour).
The issue is that when I'm using this way, I got prices from other index but it seems to use the oldest one. I would like to get the newest one instead, thus the opposite way.
How should I proceed?
If you query with index wildcard, it should return a list of documents, where every document will include some meta fields as _index and _id.
You can sort by _index, to make elastic search return the latest document at position [0] in your list.
const data = await elasticClient.search({
index: myindex_2022-*,
body: {
query: {
match: {
"reference_code": "123456789"
}
}
sort : { "_index" : "desc" },
}
});

Mongoose bulk update

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:

MongoDB query to find most recently added object in an array within a document then making a further query based on that result

I have two collections in MongoDB; users & challenges.
The structure of the users collection looks like this:
name: "John Doe"
email: "john#doe.com"
progress: [
{
_id : ObjectId("610be25ae20ce4872b814b24")
challenge: ObjectId("60f9629edd16a8943d2cab9b")
completed: true
date_completed: 2021-08-06T12:15:32.129+00:00
}
{
_id : ObjectId("611be24ae32ce4772b814b32")
challenge: ObjectId("60g6723efd44a6941l2cab81")
completed: true
date_completed: 2021-08-07T12:15:32.129+00:00
}
]
date: 2021-08-05T13:06:34.129+00:00
The structure of the challenges collection looks like this:
_id: ObjectId("610be25ae20ce4872b814b24")
section_no: 1
section_name: "Print Statements"
challenge_no: 1
challenge_name: "Hello World!"
default_code: "public class Main {public static void main(String[] args) {}}"
solution: "Hello World!"
What I want to be able to do is find the most recent entry in a particular user's 'progress' array within the users collection and based on that result I want to query the challenges collection to find the next challenge for that user.
So say the most recent challenge entry in that user's 'progress' array is...
{
_id : ObjectId("611be24ae32ce4772b814b32")
challenge: ObjectId("60g6723efd44a6941l2cab81")
completed: true
date_completed: 2021-08-07T12:15:32.129+00:00
}
...which is Section 1 Challenge 2. I want to be able to query the challenges collection to return Section 1 Challenge 3, and if that doesn't exist then return Section 2 Challenge 1.
Apologies if this is worded poorly, I am fairly new to MongoDb and unsure of how to create complex queries in it.
Thanks in advance!
One approach:
[
{ // Unwind all arrays
"$unwind":"$progress"
},
{ // Sort in descending order all documents
"$sort":{
"progress.date_completed":-1
}
},
{ // Group them together again but pick only the most recent array element
"$group":{
"_id":"$_id",
"latestProgress":{
"$first":"$progress"
}
}
},
{ // Join with other collection
"$lookup":{
"from":"challenges",
"localField":"latestProgress.challenge",
"foreignField":"challenge",
"as":"Progress"
}
},
{ // Only pick the first array element (since there will be just one)
"$set":{
"Progress":{
"$first":"$Progress"
}
}
}
]
I have provided a comment for each stage so that it would be easier to understand the idea. I'm not confident it's the best approach but it does work since I have tested.
Just a note that there could be a case where Progress field is missing. In that case there is no such challenge document.

How do I count the documents that include a value within an array?

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'] }

Resources