mongodb, query if any element of array within property contain some regex - mongodb

i have a document structured this way
{
_id:'1'
text:'xxxxxxxc'
choices:{
a:['xxx','yyy','zzz'],
b:['aaa','bbb','ccc'],
c:['lll','mmm','ddd'],
}
}
....and many other document...
{
_id:'2'
text:'xxxxxxxc'
choices:{
a:['ooo','sss','qqq'],
b:['iii','hhh','ggg'],
c:['ddd','eee','fff'],
}
}
so i cant figure out to search if any of choices key contains an array that contains some string
to be more precise: i want to query entire document and return the document if ANY of the choices keys' array has element that matches the query.
so if i search for 'yyy' , it should simply return document of_id:1 and other docs, that has 'yyy' in array in any of its choices key
(be it in a,b,c, these keys can be more than c, that is to z and beyond)
...and not the _id:2 because it has no element 'yyy' in any of its array of its choice

db.collection.aggregate([
{
$addFields: {
"c": {
"$objectToArray": "$choices"
}
}
},
{
$match: {
"c.v": "yyy"
}
},
{
"$project": {
c: 0
}
}
])
Reshape and find what you need. Ignore other temp fields created.
Playground

Related

mongodb query: nested elemMatch

Currently, that's my current document:
{
_id: 'd283015f-91e9-4404-9202-093c28d6a931',
referencedGeneralPractitioner: [
{
resourceType: 'practitioner',
cachedIdentifier: [
{
system: { value: 'urn:oid:1.3.6.1.4.1.19126.3' },
value: { value: '14277399B' }
}
]
}
]
}
Here, there's two nested objects arrays: referencedGeneralPractitioner[{cachedIdentifier[{}]}].
Currently, I'm getting results using this query:
{
"referencedGeneralPractitioner":{
"$elemMatch":{
"cachedIdentifier.value.value":"14277399B",
"cachedIdentifier.system.value":"urn:oid:1.3.6.1.4.1.19126.3"
}
}
}
It's getting my desired document, but I don't quite figure out if above query is which I'm really looking for.
I mean, I'm only applying $elemMatch on referencedGeneralPractitioner field array.
Is it really enought?
Should I add a nested $elemMatch on cachedIdentifier?
Any ideas?
It looks like you need to query it like this:
db.collection.find({
"referencedGeneralPractitioner.cachedIdentifier": {
"$elemMatch": {
"value.value": "14277399B",
"system.value": "urn:oid:1.3.6.1.4.1.19126.3"
}
}
})
playground
This is in case you need to find the full document having $and of both values in same element in any of the elements in the nested array , if you need to extract specific element you will need to $filter
if you need to search also based on element in the 1st array level then you need to modify as follow:
{
"referencedGeneralPractitioner": {
"$elemMatch": {
resourceType: 'practitioner',
"cachedIdentifier": {
"$elemMatch": {
"value.value": 1,
"system.value":2
}
}
}
}
}
This will give you all full documents where at same time there is resouceType:"practitioner" and { value.value:3 and system.value: 2 }
Also is important to stress that this will not gona work correctly!:
{
"referencedGeneralPractitioner":{
"$elemMatch":{
"cachedIdentifier.value.value":"14277399B",
"cachedIdentifier.system.value":"urn:oid:1.3.6.1.4.1.19126.3"
}
}
}
Since it will match false positives based on any single value in the nested elements like:
wrong playground

Is there a way we can query using .find to take key as array of input

I want to provide array in filter of .find method
I found this method which matches a string in an array of input , Is there a way we can do It other way round .
db.collection.find({"phoneNumber.type": { $in: ["ACD", "BFG"] } })
According to your comment where you are looking for something like: db.collection.find({ ['a','b','c'] : 'c' }) I think you can use this aggregate query:
First create an auxiliar field using $objectToArray to get keys as k field and value as v field.
Then $match using $in with keys. This $match return the documents where the key is in the array (in this case ["a", "b", "c"]) and value (v) is desired one. Also $elemMatch is used to ensure the object is the same.
And the last step is $project to remove the auxiliar field.
db.collection.aggregate([
{
"$addFields": {
"aux": {"$objectToArray": "$$ROOT"}
}
},
{
"$match": {
"aux": {
"$elemMatch": {
"k": {"$in": ["a","b","c"]},
"v": "c"
}
}
}
},
{
"$project": {
"aux": 0
}
}
])
Example here
You could dynamically build an array of criteria on the client side:
const fieldnames = [ "field1","field2","field3" ];
const value = "match";
const criteria = fieldNames.map(function(fname){
var ret = {};
ret[fname] = value;
return ret;
});
Then if you want documents where any of them match, use:
Model.find({$or: criteria})
If you only want documwents were all of them match, use:
Model.find({$and: criteria})

Most efficient way to put fields of an embedded document in its parent for an entire MongoDB collection?

I am looking for the most efficient way to modify all the documents of a collection from this structure:
{
[...]
myValues:
{
a: "any",
b: "content",
c: "can be found here"
}
[...]
}
so it becomes this:
{
[...]
a: "any",
b: "content",
c: "can be found here"
[...]
}
Basically, I want everything under the field myValues to be put in its parent document for all the documents of a collection.
I have been looking for a way to do this in a single query using dbCollection.updateMany(), put it does not seem possible to do such thing, unless the content of myValues is the same for all documents. But in my case the content of myValues changes from one document to the other. For example, I tried:
db.getCollection('myCollection').updateMany({ myValues: { $exists: true } }, { $set: '$myValues' });
thinking it would perhaps resolve the myValues object and use that object to set it in the document. But it returns an error saying it's illegal to assign a string to the $set field.
So what would be the most efficient approach for what I am trying to do? Is there a way to update all the documents of the collection as I need in a single command?
Or do I need to iterate on each document of the collection, and update them one by one?
For now, I iterate on all documents with the following code:
var documents = await myCollection.find({ myValues: { $exists: true } });
for (var document = await documents.next(); document != null; document = await documents.next())
{
await myCollection.updateOne({ _id: document._id }, { $set: document.myValues, $unset: { myValues: 1} });
}
Since my collection is very large, it takes really long to execute.
You can consider using $out as an alternative, single-command solution. It can be used to replace existing collection with the output of an aggregation. Knowing that you can write following aggregation pipeline:
db.myCollection.aggregate([
{
$replaceRoot: {
newRoot: {
$mergeObjects: [ "$$ROOT", "$myValues" ]
}
}
},
{
$project: {
myValues: 0
}
},
{
$out: "myCollection"
}
])
$replaceRoot allows you to promote an object which merges the old $$ROOT and myValues into root level.

Delete array element having object key value mongoose

Here is my collection:
{
"_id":"5b3385af20b7dc2b008ef5b9",
"name":"C",
"distances":[{"_id":"5b3460b05b2edc1bbcb0f362",
"distance":7,
"waypoint":"5b3385af20b7dc2b008ef5b9",
"status":"available"},
{"_id":"5b3460b05b2edc1bbcb0f361",
"distance":4,
"waypoint":"5b3460a15b2edc1bbcb0f360",
"status":"available"}],
"createdAt":"2018-06-27T12:40:15.457Z",
"updatedAt":"2018-06-27T12:57:50.191Z",
"__v":0
}
Let's focus on the distances array only, so which is:
"distances":[{"_id":"5b3460b05b2edc1bbcb0f362",
"distance":7,
"waypoint":"5b3385af20b7dc2b008ef5b9",
"status":"available"},
{"_id":"5b3460b05b2edc1bbcb0f361",
"distance":4,
"waypoint":"5b3460a15b2edc1bbcb0f360",
"status":"available"}]
What i want to do is, I want to delete object and update the distances array which have "waypoint":"5b3460a15b2edc1bbcb0f360"
so far I have tried:
Model.update( {'_id': model._id}, { $pullAll: {distances: [{'waypoint': req.body.id}] } });
this isn't working. Please suggest a way out.
You can use $pull of MongoDB
db.collection.update(
{ },
{ $pull: { distances: { waypoint: req.body.id} } },
)
{multi: true}: adding this in above query will delete all entries matching { waypoint: req.body.id}

Sorting by relevance with MongoDB

I have a collection of documents in the following form:
{ _id: ObjectId(...)
, title: "foo"
, tags: ["bar", "baz", "qux"]
}
The query should find all documents with any of these tags. I currently use this query:
{ "tags": { "$in": ["bar", "hello"] } }
And it works; all documents tagged "bar" or "hello" are returned.
However, I want to sort by relevance, i.e. the more matching tags the earlier the document should occur in the result. For example, a document tagged ["bar", "hello", "baz"] should be higher in the results than a document tagged ["bar", "baz", "boo"] for the query ["bar", "hello"]. How can I achieve this?
MapReduce and doing it client-side is going to be too slow - you should use the aggregation framework (new in MongoDB 2.2).
It might look something like this:
db.collection.aggregate([
{ $match : { "tags": { "$in": ["bar", "hello"] } } },
{ $unwind : "$tags" },
{ $match : { "tags": { "$in": ["bar", "hello"] } } },
{ $group : { _id: "$title", numRelTags: { $sum:1 } } },
{ $sort : { numRelTags : -1 } }
// optionally
, { $limit : 10 }
])
Note the first and third pipeline members look identical, this is intentional and needed. Here is what the steps do:
pass on only documents which have tag "bar" or "hello" in them.
unwind the tags array (meaning split into one document per tags element
pass on only tags exactly "bar" or "hello" (i.e. discard the rest of the tags)
group by title (it could be also by "$_id" or any other combination of original document
adding up how many tags (of "bar" and "hello") it had
sort in descending order by number of relevant tags
(optionally) limit the returned set to top 10.
You could potentially use MapReduce for something like that. You'd process each document in the Map step, figuring out how many tags match the query, and assign a score. Then you could sort based on that score.
http://www.mongodb.org/display/DOCS/MapReduce
Something that complex should be done after querying. Either server-side through db.eval (if your client supports this) or just clientside. Here's an example for what you're looking for.
It will retreive all posts with the tags you specified, then sorts them according to the amount of matches.
remove the db.eva( part and translate it to the language your client uses to query to get the clientside effect (
db.eval(function () {
var tags = ["a","b","c"];
return db.posts.find({tags:{$in:tags}}).toArray().sort(function(a,b){
var matches_a = 0;
var matches_b = 0;
a.tags.forEach(function (tag) {
for (t in tags) {
if (tag == t) {
matches_a++;
} else {
matches_b++;
}
}
});
b.tags.forEach(function(tag) {
for (t in tags) {
if (tag == t) {
matches_b++;
} else {
matches_a++;
}
}
});
return matches_a - matches_b;
});
});