I have an array where any of the elements could have a match with a mongodb collection.
var elementsArray = ["item1", "item2", "item3"];
db.getCollection("myCollections").aggregate([
...
{ $group: {...} },
{
$match: {
for(item in elementsArray ){ // this for loop is not appropriate but to give the idea
$or:[
{ "word": item },
{ "preferredWord": item },
{ "synonym": item }
],
}
}
}
])
I know the for loop is inappropriate here but how can iterate through the elementsArray to find a match with any of the keywords (word, preferredWord, or synonym) that can perform the similar operations if the for loop was allowed like this?
Thanks
You can run your array outside of Aggregate, by just creating an array and including it inside of your Aggregate query:
var elementsArray = ["item1", "item2", "item3"];
var orArray = [];
for (item in elementsArray ) {
orArray.push( { "word": elementsArray[item], "preferredWord": elementsArray[item], "synonym": elementsArray[item] } );
}
Then you can use orArray object inside your aggregate:
db.getCollection("myCollections").aggregate([
...
{ $group: {...} },
{
{
$match: {
"$or" : orArray
}
}
]);
You can use the $in aggregation operator to match a field with elements from the array. You can add the other aggregation stages where appropriate (I cannot specify the entire query without the definition of the collection's input document)
Example matches collection:
{ _id: 1, fld1: "blah", word: "item2", synonym: "item3" }
{ _id: 2, fld1: "zzz", word: "item1", synonym: "item2" }
{ _id: 3, fld1: "123", word: "item99", synonym: "item99" }
The Query (from mogo shell):
var arr = [ "item1", "item2" ];
db.matches.aggregate( [
{
$addFields: {
"hasMatches": { $or: [ {$in: [ "$word", arr ]}, {$in: [ "$synonym", arr ]} ] }
}
},
{
$match: { hasMatches: true }
}
] )
The Output:
{ "_id" : 1, "fld1" : "blah", "word" : "item2", "synonym" : "item3", "hasMatches" : true }
{ "_id" : 2, "fld1" : "zzz", "word" : "item1", "synonym" : "item2", "hasMatches" : true }
Related
I have the following set of data in my MongoDB collection called orders:
{
"_id" : ObjectId("618e0e1b17687316dcdd6246"),
"groupUID": "abc",
"orderData" : {
"charges" : {
"total" : 18480.0,
"subtotal" : 13980.0
},
"items" : [
{
"name" : "Chocolate cookies",
"imageURL": "domainURL2.com/cookies"
},
{
"name" : "Chocolate muffins",
"imageURL": "domainURL2.com/muffins"
}
]
}
}
Now I want to update the imageURL substring part of "domainURL2" to "domainURL1" field in every document of this collection. I have the following query so far:
db.orders.update(
{
"groupUID" : "abc"
},
{ "$set": { "orderData.items.$.imageURL":"myURL.com" } } )
I also have the query in JavaScript form but I want this to be in pure Mongo query. So the query below will not do it for me unfortunately.
db.getCollection("orders").find({"groupUID" : "abc"}).forEach(function(aRow) {
if (aRow.orderDetails !== undefined) {
var updated = false;
aRow.orderData.items.forEach(function(item) {
item.imageURL = item.imageURL.replace("eddress/", "noknok-app/");
})
db.getCollection("orders").save(aRow);
}
});
I want to update all records' imageURL field's substring part. I am unable to figure out the rest of the query. Can anyone please help me?
My answer may look complex (Welcome for suggestion/improvement).
Work the update with Aggegration Pipeline.
$set - Update orderData.items field.
1.1. $map - Iterate orderData.items and returns new array.
1.1.1. $mergeObjects - Merge current object and imageURL field from 1.1.1.1.
1.1.1.1. $cond - With $regexMatch to find the imageURL starts with "domainURL2.com".
1.1.1.2. If true, then replace "domainURL2.com" with "domainURL1.com".
1.1.1.3. If false, remain existing value.
db.collection.update({
"groupUID": "abc"
},
[
{
"$set": {
"orderData.items": {
$map: {
input: "$orderData.items",
in: {
$mergeObjects: [
"$$this",
{
imageURL: {
$cond: {
if: {
$regexMatch: {
input: "$$this.imageURL",
regex: "^domainURL2.com"
}
},
then: {
$concat: [
"domainURL1.com",
{
$arrayElemAt: [
{
$split: [
"$$this.imageURL",
"domainURL2.com"
]
},
-1
]
}
]
},
else: "$$this.imageURL"
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground
Another approach is using $replaceOne (suggested by #rickhg12hs) which will be much easier.
$replaceOne to replace for 1.1.1.1.
db.collection.update({
"groupUID": "abc"
},
[
{
"$set": {
"orderData.items": {
$map: {
input: "$orderData.items",
in: {
$mergeObjects: [
"$$this",
{
imageURL: {
$replaceOne: {
input: "$$this.imageURL",
find: "domainURL2.com",
replacement: "domainURL1.com"
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground ($replaceOne)
for example i have
{
...
myObjects = [ {nmbr: 1}, {nmbr:2}]
}
now I want:
{
...
myObjects = [ {nmbr: 1, id: 1}, {nmbr:2, id :2}]
}
using:
db.collection.aggregate([
{
"$addFields": {
"myObjects.id": "$myObjects.nmbr"
}
}
])
has this result
{
...
myObjects = [ {nmbr: 1, id_:[1,2]}, {nmbr:2, id:[1,2]}]
}
which is not what I expected, any solution?
$unwind: Deconstructs myObjects array field from the source documents to output a document for each element.
$addFields: Create id property with value myObject.nmbr in myObject field.
$group: Group by $id (ObjectId) to combine into myObjects array (reverse $unwind).
db.collection.aggregate([
{
"$unwind": "$myObjects"
},
{
"$addFields": {
"myObjects.id": "$myObjects.nmbr"
}
},
{
$group: {
_id: "$id",
"myObjects": {
$push: "$myObjects"
}
}
}
])
Output
[
{
"_id": null,
"myObjects": [
{
"id": 1,
"nmbr": 1
},
{
"id": 2,
"nmbr": 2
}
]
}
]
Sample Mongo Playground
Sample data: there are multiple similar collection:
{
"_id" : NumberLong(301),
"telecom" : [
{
"countryCode" : {
"value" : "+1"
},
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "8887778888"
},
"system" : {
"value" : "phone"
},
"useCode" : {
"value" : "Home Phone"
},
"value" : {
"value" : "8887778888"
}
},
{
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "abc#test.com"
},
"system" : {
"value" : "email"
},
"useCode" : {
"value" : "work"
},
"value" : {
"value" : "abc#test.com"
}
}
]
}
Issue: I want to cont the collection where telecom.system.value = email and countryCode doesn't exist in the email part object. here I am attaching a script but I need one line query
var count = 0,i;
db.getCollection('practitioner').find({"telecom.system.value":"email"}).forEach(function(practitioner){
//print("updating : " +practitioner._id.valueOf())
telecom = practitioner.telecom.valueOf()
for(i= 0;i<telecom.length;i++){
if(telecom[i].system.value === 'email' && telecom[i].countryCode){
count+=1;
}
}
});
print(" Total count of the practitioner with country code in email object: "+count)
Above mention, the script is working fine and the output is as I expected. but the script is not optimised and I want to write in a single line query. Thanks in advance.
You can try aggregation method aggregate(),
Approach 1:
$match condition for countryCode should exists and system.value should be email
$filter to iterate loop of telecom array and check both condition, this will return expected elements
$size to get total element from above filter result
$group by null and count total
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{
$project: {
count: {
$size: {
$filter: {
input: "$telecom",
cond: {
$and: [
{ $ne: [{ $type: "$$this.countryCode" }, "missing"] },
{ $eq: ["$$this.system.value", "email"] }
]
}
}
}
}
}
},
{
$group: {
_id: null,
count: { $sum: "$count" }
}
}
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
Approach 2:
$match condition for countryCode should exists and system.value should be email
$unwind deconstruct telecom array
$match to filter document using above conditions
$count to get total elements count
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{ $unwind: "$telecom" },
{
$match: {
"telecom.countryCode": { $exists: true },
"telecom.system.value": "email"
}
},
{ $count: "count" }
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
I have not tested the performance but you can check and use as per your requirement.
I have a USER table with documents:
{
_id: 1,
name: 'funny-guy43',
image: '../../../img1.jpg',
friends: [2, 3]
},
{
_id: 2,
name: 'SurfinGirl3',
image: '../../../img2.jpg',
friends: []
},
{
_id: 3,
name: 'FooBarMan',
image: '../../../img3.jpg',
friends: [2]
}
friends is an array of USER _ids. (1) I want to get user by _id, (2) look at his friends and (3) query the USER table with the friend ids to return all friends.
for example, find user 1, query the table based on his friends 2 and 3, and return 2 and 3.
Can I do that in one transaction? Or do I query the table to get user array of friends, then query the table again with array of friends ids.
I'm using .Net Core if that matters.
I am very open to alternative approaches as well.
It is, in fact, possible to do this in one transaction. Or, to be more exact, in one aggregation.
I would first split the users into 2 different subsets, one called searched_user and the other other_users, where searched_user will have only the user we are searching for and other_users will have everyone else. We can do that using $facet. Here is the idea:
{
"$facet": {
"searched_user": [
{
$match: {
_id: 1
}
}
],
"other_users": [
{
$match: {
_id: {
$ne: 1
}
}
}
]
}
}
Once they are separated like this, we can search the other_users subset using the friend ids from the searched_user. So here is the full aggregation:
db.collection.aggregate([
{
"$facet": {
"searched_user": [
{
$match: {
_id: 1
}
}
],
"other_users": [
{
$match: {
_id: {
$ne: 1
}
}
}
]
}
},
{
"$unwind": "$searched_user"
},
{
$project: {
user_friends: {
$filter: {
input: "$other_users",
as: "other_users",
cond: {
$in: [
"$$other_users._id",
"$searched_user.friends"
]
}
}
}
}
}
])
Here we are looking for user 1 and the result will be user 1's friends.
[
{
"user_friends": [
{
"_id": 2,
"friends": [],
"image": "../../../img2.jpg",
"name": "SurfinGirl3"
},
{
"_id": 3,
"friends": [
2
],
"image": "../../../img3.jpg",
"name": "FooBarMan"
}
]
}
]
Playground: https://mongoplayground.net/p/-8pNnQXg8r6
You can achieve this by using lookup in aggregation, Tried it with MongoDB version v4.2.11.
db.users.aggregate([
{
'$match': {
'_id': 1,
}
},
{
'$lookup': {
'from' : 'users',
'let' : {
'friendIds': '$friends',
},
'pipeline': [
{
'$match':{
'$expr': {'$in': [ '$_id', '$$friendIds']}
}
}
],
'as': 'friendsArr'
}
}
])
Result:
[
{
"_id" : 1,
"name" : "funny-guy43",
"image" : "../../../img1.jpg",
"friends" : [
2,
3
],
"friendsArr" : [
{
"_id" : 2,
"name" : "SurfinGirl3",
"image" : "../../../img2.jpg",
"friends" : [ ]
},
{
"_id" : 3,
"name" : "FooBarMan",
"image" : "../../../img3.jpg",
"friends" : [
2
]
}
]
}
]
I have a Collection in my database where most documents have an array-field. These arrays contain exactly 2 elements. Now i want to find all documents where all of those array elements are elements of my query array.
Example Documents:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] },
{ a:["1","4"] }
Query array:
["1","2","3"]
The query should find the first 3 documents, but not the last one, since there is no "4" in my query array.
Expected Result:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] }
Looking forward to a helpful answer :).
Since the size is static, you can just check that both elements are in [1,2,3];
db.test.find(
{ $and: [ { "a.0": {$in: ["1","2","3"] } },
{ "a.1": {$in: ["1","2","3"] } } ] },
{ _id: 0, a: 1 }
)
>>> { "a" : [ "1", "2" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "3" ] }
EDIT: Doing it dynamically is a bit more hairy, I can't think of a way without the aggregation framework. Just count matches as 0 and non matches as 1, and finally remove all groups that have a sum != 0;
db.test.aggregate(
{ $unwind: "$a" },
{ $group: { _id: "$_id",
a: { $push: "$a" },
fail: { $sum: {$cond: { if: { $or: [ { $eq:["$a", "1"] },
{ $eq:["$a", "2"] },
{ $eq:["$a", "3"] }]
},
then: 0,
else: 1 } } } } },
{ $match: { fail: 0 } },
{ $project:{ _id: 0, a: 1 } }
)
>>> { "a" : [ "1", "3" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "2" ] }
I also think, that it's impossible without the aggregation framework (if elements count is dynamic).
But I found out more universal way of doing that:
db.tests.aggregate({
$redact: {
$cond: {
if: {$eq: [ {$setIsSubset: [ '$a', [ "1", "2", "3" ] ]}]},
then: '$$KEEP',
else: '$$PRUNE'
}
}
})
I believe the answer to your problem is to use
$in
(from the docs:)
Consider the following example:
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
This query selects all documents in the inventory collection where the qty field value is either 5 or 15. Although you can express this query using the $or operator, choose the $in operator rather than the $or operator when performing equality checks on the same field.
You can also do more complex stuff using arrays. Checkout:
http://docs.mongodb.org/manual/reference/operator/query/in/