I have a problem which I have already solved with $unwind and $group, but I am really interested in whether it is possible to solve with $reduce.
Currently using MongoDb 3.4
Here is the input:
{
"myArray": [
{"foo":"x",
"bar":"y",
"important": {
"imp_1": "a",
"imp_2": "b"
}
},
{
"foo":"x",
"bar":"y",
"important": {
"imp_1": "aa",
"imp_3": "bb"
}
}
]
}
And here is what I would like to achieve:
{
"myArray": [
{"foo":"x",
"bar":"y",
"important": [
{"k":"imp_1", "v":"a"},
{"k":"imp_2": "v":"b"}
]
}
},
{
"foo":"x",
"bar":"y",
"important": [
{"k":"imp_1","v": "aa"},
{"k":"imp_3","v": "bb"}
]
}
]
}
Is it possible to solve it with 1 $reduce?
My problem is, that it created only 1 "important" array, instead of 2 for each object in the initial array.
Is $reduce at all better then a $unwind, $group and a $project right after each other?
Thx & Regards,
Grunci
Related
My structure is something like this:
{
"_id": "13hu13o13uuo1",
"profiles": {
"1": [1,2,3],
"847891": [3, 4],
..
}
}
"profiles" is an "object-array"/dictionary that has dynamic keys and their values are arrays.
How can I update all profile elements, inserting, let's say, 11 in every array?
Version: Mongo 4.2.3
While the syntax for the other answer is technically correct, I recommend not using the aggregation pipeline $merge approach for most usecases. The main reason is that such a pipeline is not atomic.
You can simply execute the exact same logic using the aggregation pipeline update feature, hence gaining the atomic property of the update operations in Mongo.
Here's how this looks:
db.collection.updateMany(
{},
[
{
"$set": {
"profiles": {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$profiles"
},
"as": "element",
"in": {
k: "$$element.k",
v: {
"$concatArrays": [
"$$element.v",
[
11
]
]
}
}
}
}
}
}
}
])
Mongo Playground
Let's say I have three document structured like so :
{
"_id": 1,
"conditions": [
["Apple", "Orange"],
["Lemon"],
["Strawberry"]
]
},
{
"_id": 2,
"conditions": [
["Apple"],
["Strawberry"]
]
},
{
"_id": 3,
"conditions": [
["Apple", "Lime"]
]
}
And I have an array, I'll call it ARC for this example :
ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
I would like to return all document in which all conditions subarray values can't be found in the ARC array.
For example, with the data above, the first document should be returned because :
The Apple AND Orange combination is not in the ARC array
Lemon is not in the ARC array
Strawberry is not in the ARC array
The second document shouldn't be returned because :
Apple is in the ARC array
And the third document shouldn't be returned because :
The Apple AND Lime combination is in the ARC array
I've tried
db.example.find({"conditions": {$not: {$elemMatch: {$all: [ARC]}}}})
But it seems way too simple.. So, as expected, it doesn't work.
I know mongoDB is pretty powerful with all the aggregation and stuff but I'm a bit lost.
Do you know if it's possible with a query alone and if so, what should I look for ?
The query below should solve your problem.
var ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
];
db.test.find(
{ $expr: {
$eq: [
{ $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } },
[ ]
]
}
}
)
It's made up of lots of parts so I'll try to break it down a bit, The first part is $expr within a find (or can be used within a $match in an aggregation) this allows us aggregation expressions within the query. So this allows us to use a $filter.
The $filter expression allows us to filter down the arrays in the condition field to check if any are a subset of the array ARC passed in.
We can actually take that filter an execute it on its own using an aggregation query:
db.test.aggregate([
{ $project: {
"example" : { $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } }
} }])
{ "_id" : 1, "example" : [ ] }
{ "_id" : 2, "example" : [ [ "Apple" ] ] }
{ "_id" : 3, "example" : [ [ "Apple", "Lime" ] ] }
The last part of the query is the $eq which is taking the value that is created with the filter and then matching it against an empty array [ ].
This is an aggregation approach. You should use $setIsSubset.
Below should be helpful:
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
true,
{
$allElementsTrue: {
$map: {
input: "$conditions",
as: "c",
in: {
$not: {
$setIsSubset: [
"$$c",
[
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
]
}
}
}
}
}
]
}
}
}
])
MongoPlayGroundLink
I have an array of objects like this one:
{
"actions": [{
"help": {
"messages": [{}, {}]
}
}, {
"animals": [{
"sea": {
"messages": [{}, {}]
}
}, {
"land": {
"messages": [{}, {}]
}
}]
}]
}
I'm trying to get the messages array from each element. (Only the matched one)
I've tried something like:
db.getCollection('responses').find({"actions": "help"})
And
db.getCollection('responses').find({"actions.animals": "sea"})
With no luck as I'm still getting an empty array.
If this isn't possible, I'm open for alternatives.
I've been looking for similar questions like this one: Mongo db - Querying nested array and objects but in that question they're looking for a specific element inside the "messages" object (in my case) for example. Same as in this other question: Query for a field in an object in array with Mongo? where they're using $elementMatch and I don't think it fits my needs.
I thought that in the Mongo Tutorial: Query array of documents might help, but again, they have a similar structure like mine but they have a property for the help element for example, something like:
{
"actions": [{
"action": "help",
"messages": [{}, {}]
}, {
"action": "animals",
"classifications": [{
"classification": "sea",
"messages": [{}, {}]
}, {
"classification": "land",
"messages": [{}, {}]
}]
}]
}
Is this the only way to make it to work? Or can I still maintain my original structure?
Edit
After trying #Graciano's answer I'm getting the following:
db.getCollection('responses').aggregate([{
$project: { "messages": "$actions.animals.sea.messages" }
}])
Result:
/* 1 */
{
"_id" : ObjectId("5ac42e65734d1d5beda1f99b"),
"messages" : [
[
[
{
"type" : 0,
"speech" : "There are a lot of sea animals, to name a few: "
},
{
"type" : 0,
"speech" : "Whale\nFishes\nSharks"
}
]
]
]
}
Now the error to solve is that it must be a single array, not an array of arrays of arrays of messages... how to solve this?
I just updated your query and now it will look like this:
db.collection.aggregate([
{$project: { "messages": "$actions.animals.sea.messages" }},
{$unwind: "$messages"},
{$unwind: "$messages"}
])
And the result will be:
{
"_id" : ObjectId("5ac5b80dd39d9355012f6af3"),
"messages" : [
{
"type" : 0,
"speech" : "There are a lot of sea animals, to name a few: "
},
{
"type" : 0,
"speech" : "Whale\nFishes\nSharks"
}
]
}
Now you will get only single array, all you need to do $unwind the arrays respectively.
if all you need are the messages you can use an aggregation and create an Array from the elements you want
db.collection.aggregate([
{$project: { items: "$actions."+parameter+".messages" },
{$unwind: "$messages"},
{$unwind: "$messages"}
}])
tldr; I'm struggling to construct a query to
Make an aggregation to get a count of values on a certain key ("original_text_source"), which
Is in a sub-document that is in an array
Full description
I have embedded documents with arrays that are structured like this:
{
"_id" : ObjectId("0123456789"),
"type" : "some_object",
"relationships" : {
"x" : [ ObjectId("0123456789") ],
"y" : [ ObjectId("0123456789") ],
},
"properties" : [
{
"a" : "1"
},
{
"b" : "1"
},
{
"original_text_source" : "foo.txt"
},
]
}
The docs were created from exactly 10k text files, sorted in various folders. During inserting documents into the MongoDB (in batches) I messed up and moved a few files around, causing one file to be imported twice (my database has a count of exactly 10001 docs), but obviously I don't know which one it is. Since one of the "original_text_source" values has to have a count of 2, I was planning on just deleting one.
I read up on solutions with $elemMatch, but since my array element is a document, I'm not sure how to proceed. Maybe with mapReduce? But I can't transfer the logic to my doc structure.
I also could just create a new collection and reupload all, but in case I mess up again, I'd rather like to learn how to query for duplicates. It seems more elegant :-)
You can find duplicates with a simple aggregation like this:
db.collection.aggregate(
{ $group: { _id: "$properties.original_text_source", docIds: { $push: "$_id" }, docCount: { $sum: 1 } } },
{ $match: { "docCount": { $gt: 1 } } }
)
which gives you something like this:
{
"_id" : [
"foo.txt"
],
"docIds" : [
ObjectId("59d6323613940a78ba1d5ffa"),
ObjectId("59d6324213940a78ba1d5ffc")
],
"docCount" : 2.0
}
Run the following:
db.collection.aggregate([
{ $group: {
_id: { name: "$properties.original_text_source" },
idsForDuplicatedDocs: { $addToSet: "$_id" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} }
]);
Given a collection which contains two copies of the document you showed in your question, the above command will return:
{
"_id" : {
"name" : [
"foo.txt"
]
},
"idsForDuplicatedDocs" : [
ObjectId("59d631d2c26584cd8b7b3337"),
ObjectId("59d631cbc26584cd8b7b3333")
],
"count" : 2
}
Where ...
The attribute _id.name is the value of the duplicated properties.original_text_source
The attribute idsForDuplicatedDocs contains the _id values for each of the documents which have a duplicated properties.original_text_source
"reviewAndRating": [
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd9745e2d66162a6dd1f0ef",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7518",
"_id": "5bd974612d66162a6dd1f0f0",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd974622d66162a6dd1f0f1",
"rating": "5"
}
]
I have a Collection in my database where most documents have an array-field. These arrays contain exactly 2 elements. Now i want to find all documents where all of those array elements are elements of my query array.
Example Documents:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] },
{ a:["1","4"] }
Query array:
["1","2","3"]
The query should find the first 3 documents, but not the last one, since there is no "4" in my query array.
Expected Result:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] }
Looking forward to a helpful answer :).
Since the size is static, you can just check that both elements are in [1,2,3];
db.test.find(
{ $and: [ { "a.0": {$in: ["1","2","3"] } },
{ "a.1": {$in: ["1","2","3"] } } ] },
{ _id: 0, a: 1 }
)
>>> { "a" : [ "1", "2" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "3" ] }
EDIT: Doing it dynamically is a bit more hairy, I can't think of a way without the aggregation framework. Just count matches as 0 and non matches as 1, and finally remove all groups that have a sum != 0;
db.test.aggregate(
{ $unwind: "$a" },
{ $group: { _id: "$_id",
a: { $push: "$a" },
fail: { $sum: {$cond: { if: { $or: [ { $eq:["$a", "1"] },
{ $eq:["$a", "2"] },
{ $eq:["$a", "3"] }]
},
then: 0,
else: 1 } } } } },
{ $match: { fail: 0 } },
{ $project:{ _id: 0, a: 1 } }
)
>>> { "a" : [ "1", "3" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "2" ] }
I also think, that it's impossible without the aggregation framework (if elements count is dynamic).
But I found out more universal way of doing that:
db.tests.aggregate({
$redact: {
$cond: {
if: {$eq: [ {$setIsSubset: [ '$a', [ "1", "2", "3" ] ]}]},
then: '$$KEEP',
else: '$$PRUNE'
}
}
})
I believe the answer to your problem is to use
$in
(from the docs:)
Consider the following example:
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
This query selects all documents in the inventory collection where the qty field value is either 5 or 15. Although you can express this query using the $or operator, choose the $in operator rather than the $or operator when performing equality checks on the same field.
You can also do more complex stuff using arrays. Checkout:
http://docs.mongodb.org/manual/reference/operator/query/in/