$elemMatch equivalent in spring data mongodb - mongodb

I need to know the equivalent code in spring data mongo db to the code below:-
db.inventory.find( {
qty: { $all: [
{ "$elemMatch" : { size: "M", num: { $gt: 50} } },
{ "$elemMatch" : { num : 100, color: "green" } }
] }
} )

I am able to get the answer. This can be done in Spring data mongodb using following code
Query query = new Query();
query.addCriteria(Criteria.where("qty").elemMatch(Criteria.where("size").is("M").and("num").gt(50).elemMatch(Criteria.where("num").is(100).and("color").is("green"))));

I think query in your answer generated below query
{ "qty" : { "$elemMatch" : { "num" : 100 , "color" : "green"}}}
I think thats not you need.
Its only check last elemMatch expression not all.
Try with this.
query = new Query();
Criteria first = Criteria.where("qty").elemMatch(Criteria.where("size").is("M").and("num").gt(50));
Criteria two = Criteria.where("qty").elemMatch(Criteria.where("num").is(100).and("color").is("green"));
query.addCriteria(new Criteria().andOperator(first, two));

Just implemented $all with $elemMatch with spring data Criteria API:
var elemMatch1 = new Criteria()
.elemMatch(Criteria.where("size").is("M").and("num").gt(50));
var elemMatch2 = new Criteria()
.elemMatch(Criteria.where("num").is(100).and("color").is("green"));
var criteria = Criteria.where("qty")
.all(elemMatch1.getCriteriaObject(), elemMatch2.getCriteriaObject());
mongoTemplate.find(Query.query(criteria), Inventory.class);
Note: important part is calling getCriteriaObject method inside Criteria.all(...) for each Criteria.elemMatch(...) element.

Just to make #Vaibhav answer a bit more clearer.
given Document in DB
{
"modified": true,
"items": [
{
"modified": true,
"created": false
},
{
"modified": false,
"created": false
},
{
"modified": true,
"created": true
}
]
}
You could do following Query if you need items where both attribute of an item are true.
Query query = new Query();
query.addCriteria(Criteria.where("modified").is(true));
query.addCriteria(Criteria.where("items")
.elemMatch(Criteria.where("modified").is(true)
.and("created").is(true)));
here an example how to query with OR in elemMatch
Query query = new Query();
query.addCriteria(Criteria.where("modified").is(true));
query.addCriteria(Criteria.where("items")
.elemMatch(new Criteria().orOperator(
Criteria.where("modified").is(true),
Criteria.where("created").is(true))));

Hi i implemented too in Kotlin, its is extended about if statements to create dynamic query :)
val map = segmentFilter.segmentValueMap.map {
val segmentCriteria = where("segment").isEqualTo(it.key)
if (it.value.isNotEmpty()) {
segmentCriteria.and("segmentValue").`in`(it.value)
}
if (sovOverviewInDateRange != null) {
segmentCriteria.and("date").lte(sovOverviewInDateRange.recent).gte(sovOverviewInDateRange.older)
}
Criteria().elemMatch(segmentCriteria)
}
criteriaQuery.and("sovOverview").all(map.map { it.criteriaObject })
sovOverviews: {
$all: [
{
$elemMatch: {
segment: "Velikost",
segmentValue: "S"
}
},
{
$elemMatch: {
segment: "Kategorie"
}
}
]
}

Related

how can I modify a field name / key in a nested array of objects in mongodb?

I have a mongodb collection with a number of objects like this:
{
"_id" : "1234",
"type" : "automatic",
"subtypes" : [
{
"_id" : "dfgd",
"name" : "test subtype",
"subjetRequired" : true,
},
{
"_id" : "dfgd",
"name" : "test subtype2",
"subjetRequired" : false,
}
],
"anotherField" : "some value"
}
As you can see, one of the keys in the subtypes array is incorrectly spelled - "subjetRequired" instead of "subjectRequired".
I want to correct that key name. How can I do that.
I'll preface this by saying I've not worked with mongodb very much in the past.
After a lot of researching, the best I could come up with is the following (which doesn't work):
function remap(doc) {
subtypes = doc.subtypes;
var count = 0;
subtypes.forEach(function(subtype){
db.taskType.update({"_id": subtype._id}, {
$set: {"subtypes.subjectRequired" : subtype.subjetRequired},
$unset: {"subtypes.subjetRequired": 1}
});
}
)
}
db.taskType.find({"subtypes.subjetRequired":{$ne:null}}).forEach(remap);
This doesn't work.
I know the loop is correct, as if I replace the other logic with print statements I can access and print the fields who's names I want to modify.
What am I doing wrong here?
You can use this update and avoid using any code, it's also stable so you can execute it multiple times with no fear.
db.collection.updateMany({
"subtypes.subjetRequired": {
$exists: true
}
},
[
{
$set: {
subtypes: {
$map: {
input: "$subtypes",
in: {
$mergeObjects: [
"$$this",
{
subjectRequired: "$$this.subjetRequired",
}
]
}
}
}
}
},
{
$unset: "subtypes.subjetRequired"
}
])
Mongo Playground
I could modify your loop to override the whole array of subtypes:
function remap(doc) {
correctSubtypes = doc.subtypes.map(({ subjetRequired, ...rest }) => ({
...rest,
subjectRequired: subjetRequired,
}));
var count = 0;
db.taskType.findByIdAndUpdate(doc._id, {
$set: {
subtypes: correctSubtypes,
},
});
}

Trying to iterate through a collection of MongoDb results to update another collection

I'm trying to update some documents from DB-collection1 (source db) over to DB-collection2 (destination DB) .. all on the same MongoDb (with same permissions, etc).
So for each document from DB-Collection1, update a specific document in DB-collectoin2, if it exists.
The documents in DB-collection1 have following shape:
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
And the matching document in DB-collection2 is:
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"name": "jill"
},
I'm using a cursor to iterate through the source collection but I'm not sure how I can do this?
This is the javascript code mongo shell script I'm trying right now, when I run the following command on a machine where mongo is installed:
CLI: root#f0cc2f13e70c:/src/scripts# mongo --host localhost --username root --password example copyFoosToBars.js
// copyFoosToBars.js
function main() {
print('Starting script.')
print()
var foosDb = db.getSiblingDB('foos');
var barsDb = db.getSiblingDB('bars');
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = foosDb.getCollection('foos')
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
sourceFoos.forEach(function(foo){
// Check if this document exists in the bars-db
var desinationBars = barsDb.getCollection('bars')
.find(
{
"_id.documentId" : foo.appliesTo.targets._id.documentId,
},
);
printjson(desinationBars);
// If destinationBars document exists, then add a the field 'Text' : 'hi there' to the document -or- update the existing field, if the 'Text' field already exists in this document.
});
print()
print()
print('----------------------------------------------')
}
main()
So here's some sample json output for the first part of the query -> which proves that I have some data which passes that 'find/search' clause:
Starting script.
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "54f25223-67bb-4d5d-ad47-24392e4acbdf"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "34c83da3-eafd-41bf-93af-3a45d1644225"
}
}
]
}
}
This doesn't work.
MongoDB server version: 4.0.22
WARNING: shell and server versions do not match
Starting script.
uncaught exception: TypeError: comment.appliesTo.targets._id is undefined :
main/
<snip>
Can someone please suggest some clues as to how I can fix this, please?
First of all you need to safeguard against multiple items in the appliesTo.targets.
A document
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
},
"type": "AnyOtherType"
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
Will be selected by
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
with the resulting document:
{
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
}
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
}
}
]
}
}
so foo.appliesTo.targets[0]._id.documentId will be "should-not-be-updated".
Structure of the document does not allow to use $elemMatch, so you have to either use aggregation framework or filter the array clientside. The aggregation has benefit of running serverside and reduce amount of data to transfer to the client.
Secondly, there is no point to find documents from DB-collection2. You can update all matching ones straight away, like in "update...where" SQL .
So the code must be something like following:
var sourceFoos = db.foos.aggregate([
{
$unwind: "$appliesTo.targets"
},
{
$match: {
"appliesTo.targets.type": "Document",
"appliesTo.targets._id.documentId": {
$exists: true
},
"_meta.deleted": null
}
},
{
$project: {
_id: 0,
"documentId": "$appliesTo.targets._id.documentId"
}
}
]);
sourceFoos.forEach(function(foo){
db.bars.updateMany(
{"_id.documentId" : foo.documentId},
{$set:{'Text' : 'hi there'}}
)
})
If there are a lot of documents expected in the cursor I would recommend to look at bulk updates to speed it up, but as I mentioned earlier in this case mongo shell might not be an ideal tool.
target is an array so for accessing to the _id try like this :
foo.appliesTo.targets[0]._id.barId
use async/await with try/catch and use .toArray() after find query
// copyFoosToBars.js
async function main() {
try {
var foosDb = db.getSiblingDB("foos");
var barsDb = db.getSiblingDB("bars");
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = await foosDb
.getCollection("foos")
.find(
{
"appliesTo.targets.type": "Bar",
"_meta.deleted": null,
},
{
_id: 0,
"appliesTo.targets._id.fooId": 1,
}
).toArray();
for (foo of sourceFoos) {
var desinationBars = await barsDb
.getCollection("bars")
.find({
"_id.barId": foo.appliesTo.targets[0]._id.barId,
})
.toArray();
console.log(desinationBars);
if(desinationBars.length> 0){
//do somthings
}
}
} catch (error) {
console.log(error)
}
}
in the first find query you select "appliesTo.targets._id.fooId" : 1so select ( fooId but in the result json show "barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f" it's conflict, Anyway, I hope this solution solves your problem

Selecting and updating a nested object by it's ObjectId in Mongoose.js

I'm having trouble with something that thought would be trivial in MongoDB with Mongoose.
With a fairly simple schema like this
const UserSchema = new Schema({
groups: [
{
name: String,
members: [
{
hasAccepted: {
type: Boolean
}
}
]
}
]
});
When i create new groups, each member object gets an _id property of course. I simply want to select that member by its _id and update its hasAccepted property.
When I run a query with the _id of the member, I get back the entire record for the user, which makes it difficult to find the nested member to update it.
How can I trim the result down to just the member with the found ID and update its property?
I'm using Mongo 3.6.2 and have tried the new arrayFilters, but with no luck.
My code (using Node) is below, which returns the whole document, but with nothing updated.
const query = {
groups : {
$elemMatch : { members : { $elemMatch : {_id : <id>} } }
}
};
const update = {$set: {'groups.$[].members.$[o].hasAccepted':true }};
const options = { new: true, arrayFilters:[{"o._id":<id>}] };
// Find the document
User.findOneAndUpdate(query, update, options, function(error, result) {
if (error) {
res.send(error);
} else {
res.send(result);
}
});
EDIT: here's the full data from the test db i'm working with. The _id I've been testing with is one the for the member in Group 1: 5a753f168b5b7f0231ab0621
[
{
"_id": {
"$oid": "5a7505452f93de2c90f49a20"
},
"groups": [
{
"name": "Group 2",
"_id": {
"$oid": "5a7543b8e254ab02cd728c42"
},
"members": [
{
"user": {
"$oid": "5a7543b8e254ab02cd728c41"
},
"_id": {
"$oid": "5a7543b8e254ab02cd728c43"
},
"hasAccepted": false
}
]
},
{
"name": "Group 1",
"_id": {
"$oid": "5a753f168b5b7f0231ab0620"
},
"members": [
{
"user": {
"$oid": "5a753f168b5b7f0231ab061f"
},
"_id": {
"$oid": "5a753f168b5b7f0231ab0621"
},
"hasAccepted": false
}
]
}
]
},
{
"_id": {
"$oid": "5a753f168b5b7f0231ab061f"
},
"groups": [],
},
{
"_id": {
"$oid": "5a7543b8e254ab02cd728c41"
},
"groups": [],
}
]
Thanks for any help you can offer.
OK, so it turns out the the thing I needed to understand better are arrayFilters (that, and I needed to add the group name into the data I used to get to the value I needed to updated.
The thing that helped me understand arrayFilters the best was to think of the as a sort of subquery, like is used in the SQL world. Once I got that, I was able to figure out how to write my update.
This article was also very helpful in understanding how arrayFilters are used: http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html
Here's the code that worked for me. Note that you need Mongo 3.6 and Mongoose 5.0.0 to get support for arrayFilters.
Also, you need to be sure to require Mongoose's ObjectId like so
const ObjectId = require('mongoose').Types.ObjectId;
Here's the rest of the working code:
const query = {
groups : {
$elemMatch : { members : { $elemMatch : {_id : new ObjectId("theideofmymemberobject"), hasAccepted : false} } }
}
};
const update = {$set: {'groups.$[group].members.$[member].hasAccepted':true } };
const options = { arrayFilters: [{ 'group.name': 'Group 3' },{'member._id': new ObjectId("theideofmymemberobject")}] };
// update the document
User.update(query, update, options, function(error, result) {
if (error) {
res.send(error);
} else {
res.send(result);
}
});
You can try below aggregation to filter only the matching group and member from the group and member arrays
replace _id with the id to be searched, use the result to update the hasAccepted status
db.groups.aggregate(
[
{$addFields : {"groups" : {$arrayElemAt : [{$filter : {input : "$groups", as : "g", cond : {$in : [_id, "$$g.members._id"]}}}, 0]}}},
{$addFields : {"groups.members" : {$filter : {input : "$groups.members", as : "gm", cond : {$eq : [_id, "$$gm._id"]}}}}}
]
).pretty()
// An easier more modern way to do this is to pass a wildcard such as /:id in your API endpoint
// Use Object.assign()
// use the save() method
// If you are using JWT
// Otherwise find another unique identifier
const user = UserSchema.findOne({ id: req.user.id });
for (const oldObject of user.groups) {
if(oldObject.id === req.params.id) {
newObject = {
propertyName: req.body.val,
propertyName2: req.body.val2,
propertyName3: req.body.val3
}
// Update the oldObject
Object.assign(oldObject, newObject);
break;
}
}
user.save()
res.json(user);

MongoDb $projection query on C#

I need help on how to build a MongoDB query from the C# Driver. What I'm trying to make is a datediff in milliseconds and then filter those results where the datediff in milliseconds is greater or equal than an specific number.
The mongodb query that I use in the mongo shell is:
db.getCollection('Coll').aggregate(
[
{$project : {
"dateInMillis" : {$subtract: [ new Date(), "$UpdateDate" ]},
"Param2": "$Param2",
"Param3": "$Param3"}
},
{$match :{ dateInMillis : { $gte : 2662790910}}}
],
{
allowDiskUse : true
});
Which would be the equivalente C# expression?
I've been trying to make the query in many different ways without any result.
I finally found the way to make the aggregate query through the mongodb c# driver. I don't know if its the most efficient way but it's working.
var project = new BsonDocument()
{
{
"$project",
new BsonDocument
{
{"dateInMillis", new BsonDocument
{
{
"$subtract", new BsonArray() {new BsonDateTime(DateTime.UtcNow), "$UpdateDate" }
}
}
},
{
"Param2", "$Param2"
},
{
"Param3", "$Param3"
},
{
"_id", 0
}
}
}
};
var match = new BsonDocument()
{
{
"$match",
new BsonDocument
{
{
"dateInMillis",
new BsonDocument {
{
"$gte",
intervalInMilliseconds
}
}
}
}
}
};
var collection = db.GetCollection<CollClass>("Coll");
var pipeline = new[] { project, match };
var resultPipe = collection.Aggregate<CollClassRS>(pipeline);

dynamic mongodb query based on user preference in Meteor

Based on the logged in user his/her preferences I want to create a collection and display in a view.
I'm in no way experienced with mongodb and now i'm ending up with this huge if/else statement and it's already slow (with 7 users in DB). But afaik it does give me the right results.
Meteor.publish('listprofiles', function () {
if ( ! this.userId ) return [];
var user = Meteor.users.findOne({ _id: this.userId }, {
fields : {
'profile.gender': 1,
'profile.preference': 1
}
}),
query;
user.gender = user.profile.gender;
user.preference = user.profile.preference;
if (user.gender === 'man') {
if (user.preference === 'straight') {
query = {
$and: [
{ 'profile.gender': 'woman' },
{
$or : [{ 'profile.preference' : 'straight' },
{ 'profile.preference' : 'bi' }]
}
]
};
} else if (user.preference === 'gay') {
query = {
$and: [
{ 'profile.gender': 'man' },
{
$or : [{ 'profile.preference' : 'gay' },
{ 'profile.preference' : 'bi' }]
},
]
};
} else if (user.preference === 'bi') {
query = {
$or: [
{
$and: [
{ 'profile.gender': 'man' },
{
$or : [{ 'profile.preference' : 'gay' },
{ 'profile.preference' : 'bi' }]
},
]
},
{
$and: [
{ 'profile.gender': 'woman' },
{
$or : [{ 'profile.preference' : 'straight' },
{ 'profile.preference' : 'bi' }]
}
]
}
]
};
}
The queries work, I tested them, but I'm unsure how to fit them dynamically. My guess is that query also shouldn't be an object, but I'm not sure how to create a valid variable..
var dbFindQuery = Meteor.users.find({
'profile.invisible': false,
queryShouldBeHereButObviouslyThisDoesNotWork
}, {
fields : {
'profile.name': 1,
'profile.city': 1,
'profile.country': 1,
'profile.gender': 1,
'profile.preference': 1,
'profile.story': 1
}
});
console.log(dbFindQuery.fetch());
return dbFindQuery;
anyone can give me a pointer in the right direction?
You can certainly factor out the common query objects. Here's one way to approach it:
Meteor.publish('listprofiles', function() {
if (!this.userId)
return [];
var user = Meteor.users.findOne(this.userId);
var gender = user.profile.gender;
var preference = user.profile.preference;
var straightOrBiWoman = {
'profile.gender': 'woman',
'profile.preference': {$in: ['straight', 'bi']}
};
var gayOrBiMan = {
'profile.gender': 'man',
'profile.preference': {$in: ['gay', 'bi']}
};
var query = {};
if (gender === 'man') {
switch (preference) {
case 'straight':
query = straightOrBiWoman;
break;
case 'gay':
query = gayOrBiMan;
break;
default:
query = {$or: [gayOrBiMan, straightOrBiWoman]};
}
}
query['profile.invisible'] = false;
return Meteor.users.find(query, {fields: {profile: 1}});
});
Here we are reusing straightOrBiWoman and gayOrBiMan based on the user's gender and preference. Note that I used the $in operator to simplify the query. I'd also suggest that you not specify 2nd-level fields in your fields modifier for reasons explained here. Finally, I'd recommend testing this code, as I may have missed something in the logic when rewriting it. Hopefully this example will help guide you in the right direction.