I have query in MongoDB:
db.questions.aggregate([
{ $project: {
total: { $add: [ "$answear_false", "$answear_true" ] }
}},
{ $project: {
percent_true: {
$cond: [
{ $eq: [ "$total", null ] },
0 ,
{ $divide: [ "$answear_true", "$total" ] }
]
}
}},
{ $project: { _id: 1, total: 1, percent_true: 1 } }
])
But result print not exactly, field total not showing on result
{ "_id" : "1004121032231110394769", "percent_true" : 0 }
{ "_id" : "1004121035679127802289", "percent_true" : 0 }
{ "_id" : "1004121038562570811362", "percent_true" : 0 }
Could add >2 $project in one query mongodb ?
The second project filters the total field, try adding to the second project
total: "$total"
Related
I was wondering if it was possible to somehow use the $match operator within the $sum function for aggregation.
{ "$unwind": "$info.avatarInfoList" },
{ "$unwind": "$info.avatarInfoList.equipList" },
{ "$unwind": "$info.avatarInfoList.equipList.flat.reliquarySubstats" },
{
"$project": {
"name" : "$name",
"character" : "$info.avatarInfoList.avatarId",
"artifact" : "$info.avatarInfoList.equipList.itemId",
"statValue" : {
"$sum": [
{"$match" : { "$info.avatarInfoList.equipList.flat.reliquarySubstats.appendPropId" : "FIGHT_PROP_CRITICAL_HURT" } },
{"$multiply": [2, {"$match" : { "$info.avatarInfoList.equipList.flat.reliquarySubstats.appendPropId" : "FIGHT_PROP_CRITICAL" } }]}
]
},
}
},
{ "$sort": { "statValue": -1 }},
{ '$limit' : 30 }
]).to_list(length=None)
print(data)
I want to be able to use the value of the $sum operator within the project fields somehow, I just don't really understand what the right approach would be for this.
Sample Input (may be too long):
https://www.toptal.com/developers/hastebin/ixamekaxoq.json
Sample Output:
( 2 * FIGHT_PROP_CRITICAL ) + FIGHT_PROP_CRITICAL_HURT sorted from highest to lowest for each item.
{name: hat, character: Slayer, artifact: 13, statValue : 25.6}
There are still a few ambiguities about how you want to aggregate your data, but using the full document from your link, here's one way to produce the output you want.
N.B.: Weapons in the "equipList" don't have "reliquarySubstats" so they show a "statValue" of null in the output.
db.collection.aggregate([
{"$unwind": "$info.avatarInfoList"},
{"$unwind": "$info.avatarInfoList.equipList"},
{
"$project": {
"_id": 0,
"name": 1,
"character": "$info.avatarInfoList.avatarId",
"artifact": "$info.avatarInfoList.equipList.itemId",
"statValue": {
"$reduce": {
"input": "$info.avatarInfoList.equipList.flat.reliquarySubstats",
"initialValue": 0,
"in": {
"$switch": {
"branches": [
{
"case": {"$eq": ["$$this.appendPropId", "FIGHT_PROP_CRITICAL"]},
"then": {
"$add": [
"$$value",
{"$multiply": [2, "$$this.statValue"]}
]
}
},
{
"case": {"$eq": ["$$this.appendPropId", "FIGHT_PROP_CRITICAL_HURT"]},
"then": {"$add": ["$$value", "$$this.statValue"]}
}
],
"default": "$$value"
}
}
}
}
}
},
{"$sort": {"statValue": -1}}
])
Try it on mongoplayground.net.
It's not quite clear what you want to achieve, but as mentioned you want to be using $cond here.
like so:
{
"$project": {
"statValue": {
"$sum": [
{
$cond: [
{ // if this condition is true (prop id = prop critical hurt )
$eq: [
"$info.avatarInfoList.equipList.flat.reliquarySubstats.appendPropId",
"FIGHT_PROP_CRITICAL_HURT"
]
},
{ // then use this value for the "$sum"
"$multiply": [
2,
"$info.avatarInfoList.equipList.flat.reliquarySubstats.statValue"
]
},
0 // otherwise use this value for the sum.
]
}
]
}
}
Mongo Playground
generate unique id in nested document - Pymongo
my database looks like this...
{
"_id":"5ea661d6213894a6082af6d1",
"blog_id":"blog_one",
"comments": [
{
"user_id":"1",
"comment":"comment for blog one this is good"
},
{
"user_id":"2",
"comment":"other for blog one"
},
]
}
I want to add unique id in each and every comment,
I want it to output like this,
{
"_id":"5ea661d6213894a6082af6d1",
"blog_id":"blog_one",
"comments": [
{
"id" : "something" (auto generate unique),
"user_id":"1",
"comment":"comment for blog one this is good"
},
{
"id" : "something" (auto generate unique),
"user_id":"2",
"comment":"other for blog one"
},
]
}
I'm using PyMongo, is there a way to update this kind of document?
it's possible or not?
This update will add an unique id value to each of the comments array with nested documents. The id value is calculated based upon the present time as milliseconds. This value is incremented for each array element to get the new id value for the nested documents of the array.
The code runs with MongoDB version 4.2 and PyMongo 3.10.
pipeline = [
{
"$set": {
"comments": {
"$map": {
"input": { "$range": [ 0, { "$size": "$comments" } ] },
"in": {
"$mergeObjects": [
{ "id": { "$add": [ { "$toLong" : datetime.datetime.now() }, "$$this" ] } },
{ "$arrayElemAt": [ "$comments", "$$this" ] }
]
}
}
}
}
}
]
collection.update_one( { }, pipeline )
The updated document:
{
"_id" : "5ea661d6213894a6082af6d1",
"blog_id" : "blog_one",
"comments" : [
{
"id" : NumberLong("1588179349566"),
"user_id" : "1",
"comment" : "comment for blog one this is good"
},
{
"id" : NumberLong("1588179349567"),
"user_id" : "2",
"comment" : "other for blog one"
}
]
}
[ EDIT ADD ]
The following works from mongo shell. It adds unique id for the comments array's nested documents - unique across the documents.
db.collection.aggregate( [
{
"$unwind": "$comments" },
{
"$group": {
"_id": null,
"count": { "$sum": 1 },
"docs": { "$push": "$$ROOT" },
"now": { $first: "$$NOW" }
}
},
{
"$addFields": {
"docs": {
"$map": {
"input": { "$range": [ 0, "$count" ] },
"in": {
"$mergeObjects": [
{ "comments_id": { "$add": [ { "$toLong" : "$now" }, "$$this" ] } },
{ "$arrayElemAt": [ "$docs", "$$this" ] }
]
}
}
}
}
},
{
"$unwind": "$docs"
},
{
"$addFields": {
"docs.comments.comments_id": "$docs.comments_id"
}
},
{
"$replaceRoot": { "newRoot": "$docs" }
},
{
"$group": {
"_id": { "_id": "$_id", "blog_id": "$blog_id" },
"comments": { "$push": "$comments" }
}
},
{
$project: {
"_id": 0,
"_id": "$_id._id",
"blog_id": "$_id.blog_id",
"comments": 1
}
}
] ).forEach(doc => db.blogs.updateOne( { _id: doc._id }, { $set: { comments: doc.comments } } ) )
You can use ObjectId constructor to create the ids and place them in your nested documents.
{
'userid' : '5e6f2f38e8cfcfaf34ee76a6',
'c':[
{'cid':123 ,'flist':['5e6de87050fba047c4c666e1','5e65e475aa1d2a77e1e7d9b3','5e75e5a02dfcda6e321be941']} ,
{'cid':321 ,'flist':['5e92533b0f93cb0f6d813631','5e946afbfd003483a47d412b','5e6de87050fba047c4c666e1']} ,
{'cid':431 ,'flist':['5e65e475aa1d2a77e1e7d9b3','5e946afbfd003483a47d412b','5e75e5a02dfcda6e321be941']}
]
}
userid ='5e6f2f38e8cfcfaf34ee76a6'
fid = '5e6de87050fba047c4c666e1'
db.find({'userid':userid ,'c.flist':{'$eq':fid}} , {'c.$.cid':1} )
i am trying to get all cid that the flist contain fid
i tryed this method but i got only first match without {'c.$.cid':1} i got the whole list
if your intention is to get only cid alone, then below query would work,
db.collection.aggregate([{
'$match': {
'userid': '5e6f2f38e8cfcfaf34ee76a6'
}
},
{
'$unwind': {
'path': '$c'
}
}, {
'$match': {
'c.flist': '5e6de87050fba047c4c666e1'
}
},
{
'$project': {
"c.cid": 1,
"_id": 0
}
}])
would give you below output
{ "c" : { "cid" : "123" } }
{ "c" : { "cid" : "321" } }
I think as per your need, you need to change the structure of collection. And as mention in document of MongoDb projection param,
The $ operator projects the first matching array element from each document in a collection based on some condition from the query statement
you can check that in https://docs.mongodb.com/manual/reference/operator/projection/positional/#project-array-documents.
You need to make cid outside the array I think.
You can use below query
db.collection.aggregate([ { $match: { userid: "5e6f2f38e8cfcfaf34ee76a6", "c.flist": "5e6de87050fba047c4c666e1" } }, { $addFields: { c: { $filter: { input: { $reduce: { input: "$c", initialValue: [], in: { $concatArrays: [ "$$value", [ { cid: "$$this.cid", flist: { $filter: { input: "$$this.flist", as: "item", cond: { $eq: [ "$$item", "5e6de87050fba047c4c666e1" ] } } } } ] ] } } }, as: "item2", cond: { $gt: [ "$$item2.flist", [] ] } } } } } ]).pretty()
to get the following output
{
"_id" : ObjectId("5e95ca8801423e0f9af19b4b"),
"userid" : "5e6f2f38e8cfcfaf34ee76a6",
"c" : [
{
"cid" : 123,
"flist" : [
"5e6de87050fba047c4c666e1"
]
},
{
"cid" : 321,
"flist" : [
"5e6de87050fba047c4c666e1"
]
}
]
}
Thanks for reading my question.
Please execuse any mistakes, i'm working on improving my English.
I have > 4000 records in my MongoDB, this is one of my records :
{
"_id" : ObjectId("5763821ffefb61074041477e"),
"sessionId" : "5138A3B4A5966CE4B2203B8BFC90055F",
"objects" : [
{
"id" : "334449673730",
"point" : 0.5
},
{
"id" : "790373008255",
"point" : 0.5
},
{
"id" : "790373008255",
"point" : 1.0
},
{
"id" : "572453522243",
"point" : 0.5
},
{
"id" : "572453522243",
"point" : 1.0
}
]
}
My result, i want to delete duplicate id but keep the point : 1.0
Result :
{
"_id" : ObjectId("5763821ffefb61074041477e"),
"sessionId" : "5138A3B4A5966CE4B2203B8BFC90055F",
"objects" : [
{
"id" : "334449673730",
"point" : 0.5
},
{
"id" : "790373008255",
"point" : 1.0
},
{
"id" : "572453522243",
"point" : 1.0
}
]
}
I follow this post : How to remove duplicates with a certain condition in mongodb?
its similary with my question but i don't know why result as not as i want :
pipeline = ([
{
"$group": {
"_id": "$id",
"count": { "$sum": 1 },
#"uniqueIds": { "$addToSet": "$_id" },
"Point": { "$max": "$point" }
}
},
{
"$match": {
"count": { "$gte": 1 }
}
}
])
for test_item in collection_forTest.aggregate(pipeline):
print(test_item)
Result :
{'Point': None, 'count': 1, '_id': None}
I can use python code, load all records, check where same id in list, compare if point = 1 and remove same record with point != 1 but i think its slower than aggregation
Can you help me with my problem for all > 4000 records ?
Thanks very much !
MongoDB noob here...
when I do db.students.find().pretty() in the shell I get a long list from my collection...like so..
{
"_id" : 19,
"name" : "Gisela Levin",
"scores" : [
{
"type" : "exam",
"score" : 44.51211101958831
},
{
"type" : "quiz",
"score" : 0.6578497966368002
},
{
"type" : "homework",
"score" : 93.36341655949683
},
{
"type" : "homework",
"score" : 49.43132782777443
}
]
}
now I've got about over 100 of these...I need to run the following on each of them...
lowest_hw_score =
db.students.aggregate(
// Initial document match (uses index, if a suitable one is available)
{ $match: {
_id : 0
}},
// Expand the scores array into a stream of documents
{ $unwind: '$scores' },
// Filter to 'homework' scores
{ $match: {
'scores.type': 'homework'
}},
// Sort in descending order
{ $sort: {
'scores.score': 1
}},
{ $limit: 1}
)
So I can run something like this on each result
for item in lowest_hw_score:
print lowest_hw_score
Right now "lowest_score" works on only one item I to run this on all items in the collection...how do I do this?
> db.students.aggregate(
{ $match : { 'scores.type': 'homework' } },
{ $unwind: "$scores" },
{ $match:{"scores.type":"homework"} },
{ $group: {
_id : "$_id",
maxScore : { $max : "$scores.score"},
minScore: { $min:"$scores.score"}
}
});
You don't really need the first $match, but if "scores.type" is indexed, it means it would be used before unwinding the scores. (I don't believe after the $unwind mongo would be able to use the index.)
Result:
{
"result" : [
{
"_id" : 19,
"maxScore" : 93.36341655949683,
"minScore" : 49.43132782777443
}
],
"ok" : 1
}
Edit: tested and updated in mongo shell