I have a mongodb with some JSON data which includes and nested arrays. I am
trying to make a query to count how many documents have a specific
value. For example here is how my json data looks:
{
"_id" : ObjectId("5ecb815bf4b8512918224e71"),
"array1" : [
{
"_id" : ObjectId("5ecb815bf4b8512918224e85"),
"xxxx" : "1450",
"yyyy" : 83,
"array2" : [
{
"_id" : ObjectId("5ecb815bf4b8512918224e88"),
"aaaa" : "1470420945276",
},
{...},
{...}]
}
The query that i am trying is the following:
db.example.aggregate([
{
$project: {
value1: {
$filter: {
input: "$array1",
as: "array",
cond: { $eq: [ "$$array.array2.aaaa" , "1470420945276" ] }
}
}
}
},
{
$project: {
value1Count: { $size: "$value1" }
}
}
])
But doesnt work and returns that value1Count=0. It looks like it doesnt nnavigate into the array2 to
read the value of the 'aaaa'. Any help?
You were almost close to getting the desired value. The problem is $$array.array2.aaaa returns an array value, so we can't use $eq here. Instead, we should use $in operator.
db.example.aggregate([
{
$project: {
value1: {
$filter: {
input: "$array1",
as: "array",
cond: {
$in: [
"1470420945276",
"$$array.array2.aaaa"
]
}
}
}
}
},
{
$project: {
value1Count: {
$size: "$value1"
}
}
}
])
MongoPlayground | Alternative solution
Related
I'm working with a MongoDB collection that has a lot of duplicate keys. I regularly do aggregation queries to find out what those duplicates are, so that I can dig in and find out what is and isn't different about them.
Unfortunately the database is huge and duplicates are often intentional. What I'd like to do is to find the count of keys that have duplicates, instead of printing a result with thousands of lines of output. Is this possible?
(Side Note: I do all of my querying through the shell, so solutions that don't require external tools or a lot of code would be preferred, but I understand that's not always possible.)
Example Records:
[
ObjectId("622f2d94ecf6a5076c2e230b"),
ObjectId("622f329c6f10fe0490252611"),
ObjectId("623026366f10fe0490254341"),
ObjectId("623026de6f10fe0490254583"),
ObjectId("6234346adec0b842dcceb790"),
ObjectId("623434a86f10fe0490260db6"),
ObjectId("62382f91dab1e245d4e152f4"),
ObjectId("6238303b6f10fe0490265acf"),
ObjectId("623bf2af700224301c756394"),
ObjectId("623bf2f76f10fe04902729a4"),
ObjectId("623c5a1f282a052c3c0bbdfd"),
ObjectId("624bf013383df47699e6b141")
]
Here is the query that I've been using to find duplicates based on key:
db.getCollection('weldtestings').aggregate([
{
$match: {
weldId: {
$in: [
ObjectId("622f2d94ecf6a5076c2e230b"),
ObjectId("622f329c6f10fe0490252611"),
ObjectId("623026366f10fe0490254341"),
ObjectId("623026de6f10fe0490254583"),
ObjectId("6234346adec0b842dcceb790"),
ObjectId("623434a86f10fe0490260db6"),
ObjectId("62382f91dab1e245d4e152f4"),
ObjectId("6238303b6f10fe0490265acf"),
ObjectId("623bf2af700224301c756394"),
ObjectId("623bf2f76f10fe04902729a4"),
ObjectId("623c5a1f282a052c3c0bbdfd"),
ObjectId("624bf013383df47699e6b141")]
}
}
},
{
$facet: {
"NDEfailedDate": [
{
$match: { testResult: 'Failed' }
},
{
$group: {
_id: { $dateToString: { format: "%Y-%m-%d", date: "$testDate" } },
count: { $sum : 1 }
}
},
{ $sort: { _id: 1 } }
],
"NDEfailedCount": [
{
$match: { testResult: 'Failed' }
},
{
$group: {
_id: "$weldId",
data: { "$addToSet": "$testDate" }
}
},
{ $count: "totalCount" }
],
}
}
])
Which gives me an output of:
{
"NDEfailedDate" : [
{
"_id" : "2022-04-08",
"count" : 6.0
}
],
"NDEfailedCount" : [
{
"totalCount" : 5
}
]
}
The result I want to get instead:
"_id" : "2022-04-08",
"count" : 5
db.collection.aggregate([
{
$project: {
_id: {
$first: "$NDEfailedDate._id"
},
count: {
$first: "$NDEfailedCount.totalCount"
}
}
}
])
mongoplayground
I'm trying to display a MongoDB aggregation result via react chartjs. in aggregation, I can remove one field whose value is static via the set operator. is there a way to remove a second field by an association whose value is dynamic? in the example below, {"A": "N"} denotes the field that is readily removed by the set operator, whereas {"A_count":1} denotes the corresponding dynamic field that I am trying to remove.
starting aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"},{"A":"N"}],
"A_count_set":[{"A_count":0},{"A_count":1}]
}]
set operation for static field removal
{$set: {
A_set: {
$filter: {
input: "$A_set",
as: "x",
cond: { "$ne": [ "$$x", {"A":"N"}] }
}
}
}}
current aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"}],
"A_count_set":[{"A_count":0},{"A_count":1}]
}]
target aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"}],
"A_count_set":[{"A_count":0}]
}]
$project merge two array with the same position
$set filter array
$addFields recover the original array
$project remove the merge array
aggregate
db.collection.aggregate([
{
$project: {
anotherValue: {
$map: {
input: {
$range: [
0,
{
$size: "$A_set"
}
]
},
as: "idx",
in: {
$mergeObjects: [
{
$arrayElemAt: [
"$A_set",
"$$idx"
]
},
{
$arrayElemAt: [
"$A_count_set",
"$$idx"
]
}
]
}
}
}
}
},
{
$set: {
anotherValue: {
$filter: {
input: "$anotherValue",
as: "x",
cond: {
"$ne": [
"$$x.A",
"N"
]
}
}
}
}
},
{
$addFields: {
"A_set": {
$map: {
input: "$anotherValue",
as: "a",
in: {
"A": "$$a.A"
}
}
},
"A_count_set": {
$map: {
input: "$anotherValue",
as: "a",
in: {
"A_count": "$$a.A_count"
}
}
}
}
},
{
"$project": {
"anotherValue": 0
}
}
])
mongoplayground
I have following structure of my document:
{
"daily": [
{
"general": [
{
"status": false,
"_id": "5e728265f4796c0017203662",
"title": "Sport"
},...
]
}
]
}
I would like to pull the the document inside the "general" Array by it's "_id".
I tried several things but nothing seems to work for me.
Every help will be appreciate :)
The update query to pull the sub-document from the nested array field general using the _id:
ID = "5e728265f4796c0017203662"
db.collection.update(
{ "daily.general._id": ID },
{ $pull: { "daily.$.general": { _id: ID } } }
)
This can only be done in Mongo 4.2+, where they introduced pipeline'd updates.
Now we can use aggregation expressions to update documents:
db.collection.updateOne(
{},
[
{
$set: {
"daily": {
$map: {
input: "$daily",
as: "item",
in: {
"general": {
$filter: {
input: "$$item.general",
as: "datum",
cond: {$ne: ["$$datum._id", "5e728265f4796c0017203662"]}
}
}
}
}
}
}
}
]);
Or if objects in daily have more than just the general field you can do it like this:
db.collection.updateOne(
{},
[
{
$set: {
"daily": {
$map: {
input: "$daily",
as: "item",
in: {
$mergeObjects: [
"$$item",
{
"general": {
$filter: {
input: "$$item.general",
as: "datum",
cond: {$ne: ["$$datum._id", "5e728265f4796c0017203662"]}
}
}
}
]
}
}
}
}
}
]);
Unfortunately for any other Mongo version this is not possible, you'll have to restructure your data or do it in code.
When use js code,i can use functional expression one by one;For example:
array.map(***).filter(...)
can i use filter after map like above in mongoose?
My question is like this.I have an dataset like below:
{
"_id" : ObjectId("5e3bd328f3dec754e1b8e17d"),
"userId" : "5e33ee0b4a3895a6d246f3ee",
"userName" : "jackiewillen",
"hasReviewedTimes" : 4,
"notes" : [
{
"time" : ISODate("2020-02-23T10:12:19.190Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b1"),
"memoryTime" : ISODate("2020-02-23T10:42:19.190Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b0"),
"memoryTime" : ISODate("2020-02-23T22:12:19.190Z")
}
]
},
{
"time" : ISODate("2020-02-23T10:45:26.615Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bd"),
"memoryTime" : ISODate("2020-02-23T11:15:26.615Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bc"),
"memoryTime" : ISODate("2020-02-23T22:45:26.615Z")
}
]
},
}
i use $map to get item which contain memoryTime less than now in memoryLine like below:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
]
},
},
}
}
}
])
my result is like below:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
},
{ // =====> this item is not needed because of containing empty memoryLine
"time": "2020-02-23T10:45:26.615Z",
"memoryLine": [] // =======> i dont want empty item
},
]
but i want result like this:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
}
]
so i use $filter after $map to filter item which contain empty memoryLine:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
],
$filter: {
input: "$$this",
as: "note",
cond: { $ne: ["$$note.memoryLine", []] }
}
},
},
}
}
}
Then this goes wrong.
You need to run another $filter as a separate pipeline stage (for readability) or as the most outer one for your current $project. I would prefer the first one:
{
$addFields: {
notes: {
$filter: {
input: "$notes",
cond: {
$ne: [ "$$this.memoryLine", [] ]
}
}
}
}
}
This is how a documents looks like in my dataset:
{
username: 'stack',
attempts: { 1517761701: false, 1512341532: true }
}
{
username: 'overflow',
attempts: { 1217563721: false }
}
Now, I want to retrieve every document in my dataset where attempts contains more than óne key. So the query should return the document of user 'stack' but not of user 'overflow'. What query can I apply here?
try $objectToArray to convert object to array and count the number of keys if you are using mongo 3.6+
db.cols.aggregate(
[
{$addFields: {count : {$size : {$ifNull : [{$objectToArray : "$attempts"}, []]}}}},
{$match: {count : {$gt : 1}}},
{$project: {count : 0}}
]
)
Use $redact for a single pipeline:
db.collection.aggregate([
{
"$redact": {
"$cond": [
{
"$gt": [
{ "$size": {
"$objectToArray": {
"$ifNull": [
"$attempts",
{ }
]
}
} },
1
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
])
Convert attempts to an array, $unwind and then use $sortByCount to do all the work..
db.collection_name.aggregate( [
{ $addFields : { array : { $objectToArray : "$attempts"} } },
{ $unwind : "$array" },
{ $sortByCount : "$username" },
{ $match : { count : { $gte : 2 } } }
])
Outputs:
{
"_id" : "stack",
"count" : 2
}