Pymongo find() with "$toString" projection operator - mongodb

I want to query a mongo object getting the _id as a string instead of an ObjectId.
def get_obj():
query = {}
contents = {
'UniqueId' : True,
'Name' : True,
'Address1' : True,
'id' : {
"$toString": "$_id"
}
}
return db.get_db().collection.find(query,contents)
When I run it this way I get this output:
pymongo.errors.OperationFailure: Unsupported projection option: id: { $toString: "$_id" }

I believe that your issue is that your are using an aggregate function ($toString) without doing an aggregation.
There are at least 2 options:
Just use the python str() method.
cursor = list(get_obj())
print([str(doc['_id']) for doc in cursor])
Use aggregation
def get_obj():
contents = [
{'$project': {
'_id': {'$toString': '$_id'},
# other desired fields
}}
]
return db.get_db().collection.aggregate(contents)
result = list(get_obj())
print([doc for doc in result])

Related

MongoDB sort by value in embedded document array

I have a MongoDB collection of documents formatted as shown below:
{
"_id" : ...,
"username" : "foo",
"challengeDetails" : [
{
"ID" : ...,
"pb" : 30081,
},
{
"ID" : ...,
"pb" : 23995,
},
...
]
}
How can I write a find query for records that have a challengeDetails documents with a matching ID and sort them by the corresponding PB?
I have tried (this is using the NodeJS driver, which is why the projection syntax is weird)
const result = await collection
.find(
{ "challengeDetails.ID": challengeObjectID},
{
projection: {"challengeDetails.$": 1},
sort: {"challengeDetails.0.pb": 1}
}
)
This returns the correct records (documents with challengeDetails for only the matching ID) but they're not sorted.
I think this doesn't work because as the docs say:
When the find() method includes a sort(), the find() method applies the sort() to order the matching documents before it applies the positional $ projection operator.
But they don't explain how to sort after projecting. How would I write a query to do this? (I have a feeling aggregation may be required but am not familiar enough with MongoDB to write that myself)
You need to use aggregation to sort n array
$unwind to deconstruct the array
$match to match the value
$sort for sorting
$group to reconstruct the array
Here is the code
db.collection.aggregate([
{ "$unwind": "$challengeDetails" },
{ "$match": { "challengeDetails.ID": 2 } },
{ "$sort": { "challengeDetails.pb": 1 } },
{
"$group": {
"_id": "$_id",
"username": { "$first": "$username" },
"challengeDetails": { $push: "$challengeDetails" }
}
}
])
Working Mongo playground

mongoose how to $match the attribute '_id' of an object with all the results of an array in another object

I'm making an API in NodeJS using mongoose, and I have to models that I need to relate:
const userSchema = new Schema({{...}
favs: [{
type: Schema.Types.ObjectId,
ref: 'Propuesta'
}]
}, {...}
})
and an object called 'Propuesta', which has his own id
What I need is in the controller, use the $match operator to get all the id's of the model Propuesta that are include in the array 'favs', and then use $group to get the most repeated Partido (which is another model and this functionality works fine).
Obviously, 'favs' is an array that grows dynamically, so for what I've seen, I need to use the operator $or inside the code of $match, but I don't know how to write the syntax to say "if this id is equals favs[0], OR favs[1], OR favs[n]"
This is my method by now (I know that right now is comparing if my id is equal to the whole String "[id:{...},id:{...}...]"
export const partidoAfin = ({ user, querymen: { query, select, cursor } }, res, next) => {
Propuesta
.aggregate([
{
'$match': {
$or: {'_id': user.favs}
}
},
{'$group': {
'_id': '$partido',
'partidoCount': {'$sum': 1}
}},
{'$sort': {partidoCount: -1}}
])
.then(success(res))
.catch(next)
}
Thank you so much
I fixed it by doing this:
export const partidoAfin = ({ user, querymen: { query } }, res, next) => {
let arrayIds = []
arrayIds = user.favs
Propuesta
.aggregate([
{
'$match': {
'_id': {'$in': arrayIds}
}
},
{'$group': {
'_id': '$partido',
'partidoCount': {'$sum': 1}
}},
{'$sort': {partidoCount: -1}}
]).then((result) => ({
count: result.length,
rows: result
}))
.then(success(res))
.catch(next)
}

mongoDb $in with aggregate query

How do I write an $in query along with aggregate in mongoDB? I want to write an equivalent mongoDB query for the SQL below
SELECT name,count(something) from collection1
where name in (<<list of Array>>) and cond1 = 'false'
group by name
The equivalent mongo query follows:
db.collection1.aggregate([
{ "$match": {
"name": { "$in": arrayList },
"cond1": "false"
} },
{ "$group": {
"_id": "$name",
"count": { "$sum": "$something" }
} }
])
Suppose you have an Schema with field tags
{
tags: ['banana', 'apple', 'orange']
}
and you want find out apple inside tags with aggregate function then
const keywords = "apple"; // req.body.keywords
const filter = { $match : { tags: {$in : [keywords] } }}
Schema.aggregate(filter).then(result=>{
// You can do what you want with result
})

How do I forEach on runCommand result

I got E QUERY TypeError: Object [object Object] has no method 'forEach'
How could I for each the result ?
cur = db[source_collection].runCommand('aggregate',
pipeline: pipeline
allowDiskUse: true)
cur.forEach (customer) ->
db[output_collection].update({'_id': customer._id},{
'$pull': {
'$records.items': {
$regex: /\s+/
}
}
})
Use the aggregate() helper instead since in 2.6 and later, the aggregate() helper always returns a cursor:
cur = db[source_collection].aggregate pipeline
cur.forEach (customer) ->
db[output_collection].update('_id': customer._id,
'$pull':
'records.$.items':
'$regex': /\s+/
)
As per mongo aggregation runCommand documentation
Using the aggregate command to return a cursor is a low-level operation, intended for authors of drivers.
and
runCommand returns a document that contains results
so you write cur.result.forEach
It is old but even though I will right this:
For the first case I think it will help:
cur.result.forEach(
function(doc)
{
...
...
})
I have more one that can I add more for future questions around it:
db.runCommand(
{ aggregate: "MyCollectionName",
pipeline: [
{ $group :
{
_id : "$GroupColumnName"
,"count": { "$sum": 1 }
, AllDocs: { $push: "$$ROOT" }
}
},
{ "$match": {"_id" :{ "$ne" : null } , "count" : {"$gt": 1} } }
],
allowDiskUse: true
}
).result.forEach(
function(doc)
{
doc.AllDocs.forEach(
function(docInternal)
{
print(docInternal._id);
}
);
})

mongodb aggregation framework - Fetch first document's field of the nested array

Consider the following is my document stored in collection Users
{
_id : "User1",
joined : ISODate("2011-03-02"),
likes : {
sublikes: [
{WebsiteID:'001': WebsiteName: 'ABCD'},
{WebsiteID:'002': WebsiteName: '2BC2'},
{WebsiteID:'003': WebsiteName: '3BC3'},
//...........
//...........
{WebsiteID:'999999': WebsiteName: 'SOME_NAME'}
]
}
}
Now using mongodb aggregation framework I need to fetch that
collection.aggregate([
{ $project: {
_id: 1,
"UserID": "$_id",
"WebsiteName": "$likes.sublikes[0]['WebsiteName']"
}
},
{ $match: { _id: 'User1'} }
], function (err, doc) {
///Not able to get the WebsiteName: 'ABCD'
});
If I use $unwind the document becomes bulk (specifically in the above case), So I don't want to unwind it for getting only first item in an array (irrespective of others)
Can anyone give me hints on how to access is and rename that field?
Update 1: Even I tried to project with "WebsiteName": "$likes.sublikes.0.WebsiteName". Didn't work :-(
Update 2: Open issue - https://jira.mongodb.org/browse/SERVER-4589
As of now $at does not work. Throws an error:
{ [MongoError: exception: invalid operator '$at']
name: 'MongoError',
errmsg: 'exception: invalid operator \'$at\'',
code: 15999,
ok: 0 }
Till then using
$unwind
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
The easiest way to achieve your result is using a normal find query and the $slice operator:
db.collection.find( {_id: "User1"}, {"likes.sublikes": {$slice: 1}} )
The aggregation framework (as at MongoDB 2.4.1) does not support $slice or array indexes (vote/watch feature requests: SERVER-6074 and SERVER-4589).
You could do this in aggregation framework using $unwind, $group and the $first operator, eg:
db.collection.aggregate([
{ $match: {
_id : "User1"
}},
{ $unwind: "$likes.sublikes" },
{ $group: {
_id: "$_id",
like: { $first: "$likes.sublikes" }
}},
{ $project: {
_id: 0,
"UserID": "$_id",
"WebsiteName": "$like.WebsiteName"
}}
])
The normal $slice should be the most performant option.