Project fields in mongo Database Command with fulltext search - mongodb

I'm trying to project fields in the result of a mongo fulltext search but so far with no luck. The comman is as this
var textSearchCommand = new CommandDocument
{
{ "text", "mycollection" },
{ "search", keyword },
{"project", "_id:1, Name:1"}
};
I've tried other approaches pro project but with no luck. What would be the correct syntax`?

You need to make a BsonDocument for your "project" value instead of using a string:
var textSearchCommand = new CommandDocument
{
{ "text", "mycollection" },
{ "search", keyword },
{ "project", new BsonDocument { { "_id", 1 }, { "Name", 1 } } }
};

Related

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

What query uses less resources in MongoDB?

I am getting familiarized with Lucene and MongoDB Atlas search, and I have a question about query efficiency.
Which one of those queries uses fewer resources?
If there are better queries for performing the below task, please let me know.
I want to return all movies (sample_mflix) that match on a title value. The movies must be for a specific year (should not return any movie that is not for that year), and I would like to return movies with "$gte" values for movies.awards.nominations & movies.awards.wins.
The first query seems more complex (which seems to increase resource utilization - query complexity?). This query also is not returning values for that year only. That makes me think that there is probably a better way to do this with Atlas search.
The second query uses the $search and a $match in different stages. It has a simple Lucene search (which might return more movies than the first query?), and the match operator will filter the results. The second query is more precise - from my tests, it respects the year constraint. If I apply a limit stage, would this be a better solution?
If those queries were executed in the same scenario, which one would be more efficient, and why (apologies, the second query is formatted for .net driver)?
new BsonArray
{
new BsonDocument("$search",
new BsonDocument
{
{ "index", "nostoreindex" },
{ "compound",
new BsonDocument
{
{ "must",
new BsonDocument("near",
new BsonDocument
{
{ "path", "year" },
{ "origin", 2000 },
{ "pivot", 1 }
}) },
{ "must",
new BsonDocument("text",
new BsonDocument
{
{ "query", "poor" },
{ "path", "title" }
}) },
{ "should",
new BsonDocument("range",
new BsonDocument
{
{ "path", "awards.nominations" },
{ "gte", 1 }
}) },
{ "should",
new BsonDocument("range",
new BsonDocument
{
{ "path", "awards.wins" },
{ "gte", 1 }
}) }
} }
})
}
VS
var searchStage =
new BsonDocument("$search",
new BsonDocument
{
{ "index", "nostoreindex" },
{ "text",
new BsonDocument
{
{ "query", title },
{ "path", "title" }
} }
});
var matchStage = new BsonDocument("$match",
new BsonDocument("$and",
new BsonArray
{
new BsonDocument("year",
new BsonDocument("$eq", year)),
new BsonDocument("awards.nominations",
new BsonDocument("$gte", nominations)),
new BsonDocument("awards.wins",
new BsonDocument("$gte", awards))
})
);
When using Atlas Search, it is better to avoid using a succeeding $match filter after your $search stage. This is because all data will need to be looked up in your mongod by id, which can be quite slow.
So, generally, you are trying to keep your search and filters "in Lucene" if possible, to avoid extra IO and comparisons.
In your case, you are using near which will return all results in order descending from near. You should use range instead which can filter those results and speed up your query.
near is used to score your results higher if they are closer to a specific value, which can simulate a sort. For example, if you want to score results with higher 'awards.wins' you may wish to add a near : { origin: 10000, pivot: 1} then the closer the value is to 10000 the higher the score.
new BsonArray
{
new BsonDocument("$search",
new BsonDocument
{
{ "index", "nostoreindex" },
{ "compound",
new BsonDocument
{
{ "must",
new BsonDocument("range",
new BsonDocument
{
{ "path", "year" },
{ "gte", 2000 },
{ "lte", 2000 }
}) },
{ "must",
new BsonDocument("text",
new BsonDocument
{
{ "query", "poor" },
{ "path", "title" }
}) },
{ "should",
new BsonDocument("range",
new BsonDocument
{
{ "path", "awards.nominations" },
{ "gte", 1 }
}) },
{ "should",
new BsonDocument("range",
new BsonDocument
{
{ "path", "awards.wins" },
{ "gte", 1 }
}) }
} }
})
}

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

Error while using aggregation with limit property on mongodb in asp.net MVC4.0?

I use MongoDB and MVC 4.0.
The below code gave me an error, I tried many different ways but it always shows this error:
"Command 'aggregate' failed: exception: A pipeline stage specification
object must contain exactly one field. (response: { "errmsg" :
"exception: A pipeline stage specification object must contain exactly
one field.", "code" : 16435, "ok" : 0.0 })"
My code:
var matchSumcount2 = new BsonDocument
{
{
"$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{
"Device","$Device"
}
}
},
{
"Clicks",new BsonDocument
{
{
"$sum","$Clicks"
}
}
},
{
"Day",new BsonDocument
{
{
"$sum",1
}
}
}
}
},
{
"$limit",50
}
};
var database = MongoDbManager.GetDatabase();
var pipeline = new[] { matchSumcount2 };
var list = database.GetCollection("rnd").Aggregate(pipeline);
I only want the first 50 records and then perform the aggregation.
What I am doing wrong here? Any suggestion or code sample to do this?
I made the comment above, but you didn't understand. I apologize for not being more clear. I'll use code examples to show you what is wrong.
You are doing this (effectively):
{
{ $group: { _id: { Device: "$Device" } } },
{ $limit: 50 }
}
But that is wrong. $group and $limit should not be siblings in a document. They should be elements in an array.
[
{ $group: { _id: { Device: "$Device" } } },
{ $limit: 50 }
]
Like I mentioned in the comment, I cannot see the start of your code, so I can only make an assumtpion based on the end. Your first line is probably a new BsonDocument(). That is wrong. It should be new BsonArray();
var pipeline = new BsonArray();
pipeline.Add(new BsonDocument(
{
{ "$group", new BsonDocument { { "_id", new BsonDocument { { "Device", "$Device" } } } } }
});
pipeline.Add(new BsonDocument("$limit", 50));