How to project sub-document values in an array using MongoDB? - mongodb

I am creating an application that needs to support multiple languages in the content. I am using the following document structure; each field that needs translation is an object with each language and associated string.
{
"greeting": {
"en": "Hello",
"fr": "Bonjou"
},
"places": [
{
"name": {
"en": "CN Tower",
"fr": "La Tour CN"
}
},
{
"name": {
"en": "Skydome",
"fr": "Le Skydome"
}
}
]
}
I want to be able to aggregate the result into a single selected language to produce the following result.
{
"greeting": "Hello",
"places": [
{
"name": "CN Tower"
},
{
"name": "Skydome"
}
]
}
Mapping the greeting field was simple, requiring only a $greeting.en. However, sub-documents in an array are a lot trickier to perform the path syntax with. Instead, I've had to use a $map to achieve the result I needed.
db.collection.aggregate([{
$project: {
greeting: "$greeting.en",
places: {
$map: {
input: "$places",
as: "place",
in: { name: "$$place.name.en"
}
}
}
}
}])
Is there a way to achieve this without using $map and instead using the path syntax?

Related

How to query based on nested object's key and value?

"session": {
"number": 123,
"words": {
"1": {
"id": 10,
"name": "Hello"
},
"2": {
"id": 13,
"name": "Hi"
},
"3": {
"id": 135,
"name": "Hey"
}
}
}
We have data in MongoDB database in the given structure which I am not able to modify.
I can get the object based on query session.number is 123, but now the challenging is we know the nested name is either Hi, Hey or Hello but the incremental key inside object words are unpredictable, is it possible to query base on the "name" field?
You can use $objectToArray and convert the words property to an array then query it however you want, you can use the following:
collection.aggregate([
{
$project: {
words: { $objectToArray: "$session.words" }
}
},
{
$match: {
'words.v': 'hi'
}
}
]);
You can read the docs for more info.

Cannot create an array of nested object using a project operation

I'm forced to use the aggregation framework and the project operation of Spring Data MongoDb.
What I'd like to do is creating an array of object as a result of a project operation.
For example, considering this intermediate aggregation result:
[
{
"content": {
"processes": [
{
"id": "101a",
"title": "delivery"
},
{
"id": "101b",
"title": "feedback"
}
]
}
}
]
What I want to obtain is this:
[
{
"results":
{
"titles": [
{
"id": "101a",
"value": "delivery"
},
{
"id": "101b",
"value": "feedback"
}
]
}
}
]
This was just an example, I don't want to simply "rename" some fields.
What I want is the possibility to create an array of objects.
If I try something like this:
projectionOperation
.and("$content.processes.id").as("results.titles.id")
.and("$content.processes.title").as("results.titles.value");
I obtain this:
[
{
"results":
{
"titles": {
"id": ["101a", "101b"]
"value": ["delivery", "feedback"]
}
}
}
}
]
With this projection the array is created, but not "in the proper position".
However, If I use the nested operator, I haven't figure out a way to specify that I want to create an array instead of an object.
With this projection:
projectionOperation.and("results.titles")
.nested(
bind("id", "process.id")
.and("value", "process.title")
);
I can create a proper nested object but not into an array:
"results.titles": {
"id": "101b",
"value": "feedback"
}
You can try below aggregation code.
ProjectionOperation po = Aggregation.project().and(
VariableOperators.mapItemsOf("content.processes").as("rt")
.andApply(
new AggregationExpression() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return new Document("id", "$$rt.id").append("value", "$$rt.title");
}
}
)
).as("result");

MongoDb aggregation project onto collection

I've a problem with a huge MongoDb aggregation pipeline. I've many constraint and I've simplified the problem a lot. Hence, don't discuss the goal for this query.
I've a mongo aggregation that gives something similar to this:
[
{
"content": {
"processes": [
{
"id": "101a",
"title": "delivery"
},
{
"id": "101b",
"title": "feedback"
}
]
}
}
]
To this intermediate result I'm forced to apply a project operation in order to obtain something similar to this:
[
{
"results":
{
"titles": [
{
"id": "101a",
"value": "delivery"
},
{
"id": "101b",
"value": "feedback"
}
]
}
}
]
enter code here
But applying this projections:
"results.titles.id": "$content.processes.id",
"results.titles.value": "$content.processes.title"
I obtain this:
[
{
"results":
{
"titles": {
"id": ["101a", "101b"]
"value": ["delivery", "feedback"]
}
}
}
}
]
Collection are created but not in the proper position.
Is it possible to exploit some operator inside the project operation in order to tell mongo to create an array in a parent position?
Something like this:
"results.titles.$[x].value" : "$content.processes.value"
You can use the dot notation to project entire array:
db.col.aggregate([
{
$project: {
"results.titles": "$content.processes"
}
}
])
and if you need to rename title to value then you have to apply $map operator:
db.col.aggregate([
{
$project: {
"results.titles": {
$map: {
input: "$content.processes",
as: "process",
in: {
id: "$$process.id",
value: "$$process.title"
}
}
}
}
}
])

Aggregate documents where objects in array matches multiple conditions

I have a collection with documents similar to such:
{
"_id": ObjectId("xxxxx"),
"item": [
{ "property": ["attr1", "+1"] },
{ "property": ["attr2", "-1"] }
]
}
{
"_id": ObjectId("xxxxy"),
"item": [
{ "property": ["attr1", "-1"] },
{ "property": ["attr2", "0"] }
]
}
{
"_id": ObjectId("xxxxz"),
"item": [
{ "property": ["attr1", "0"] },
{ "property": ["attr2", "+1"] }
]
}
Preferably using an aggregation pipeline, is there any way to match the document if and only if any one of the properties match more than one condition?
For example, I want a query where one object in the array matches both of these conditions:
("item.property": "attr1") AND ("item.property": /^\+/)
That is, a single property where it contains "attr1" and an element that starts with "+".
However, using my current query that looks like this:
collection.aggregate(
{ $match:
{ $and:
[
{ "item.property": "attr1" },
{ "item.property": /^\+/ }
]
}
}
This would match both the first and last document because both contain a property with "attr1" and an element that stats with "+". However, I do not want this query to match the last document, since the element that starts with "+" does not belong to the same object in the array.
Is there any way to achieve this behavior using the aggregation framework?
You can use the below query with $elemMatch to match the array's both values.
Something like
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property.0": "attr1",
"property.1": /^\+/
}
}
}
});
Also, you can use $all operator if you don't want to reference array index.
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property": {
"$all": [
"attr1",
/^\+/
]
}
}
}
}
});

Are there restrictions on MongoDB collections property names?

I've a document structure wich contains a property named shares which is an array of objects.
Now I tried to match all documents where shared contains the matching _account string with dot notation (shares._account).
It's not working but it seems it's because of the _ char in front of property _account.
So if I put the string to search for inside the name property in that object everything works fine with dot notation.
Are there any limitations on property names?
Thought an _ is allowed because the id has it also in mongodb and for me it's a kind of convention to daclare bindings.
Example:
// Collection Item example
{
"_account": { "$oid" : "526fd2a571e1e13b4100000c" },
"_id": { "$oid" : "5279456932db6adb60000003" },
"name": "shared.jpg",
"path": "/upload/24795-4ui95s.jpg",
"preview": "/img/thumbs/24795-4ui95s.jpg",
"shared": false,
"shares": [
{
"name": "526fcb177675f27140000001",
"_account": "526fcb177675f27140000001"
},
{
"name": "tim",
"_account": "526fd29871e1e13b4100000b"
}
],
"tags": [
"grüngelb",
"farbe"
],
"type": "image/jpeg"
},
I tried to get the item with following query:
// Query example
{
"$or": [
{
"$and": [
{
"type": {
"$in": ["image/jpeg"]
}
}, {
"shares._account": "526fcb177675f27140000001" // Not working
//"shares.name": "526fcb177675f27140000001" // Working
}
]
}
]
}
Apart from the fact that $and can be omitted and $or is pointless "image/jpeg" != "image/jpg":
db.foo.find({
"type": {"$in": ["image/jpeg"]},
"shares._account": "526fcb177675f27140000002"
})
Or if you really want old one:
db.foo.find({
"$or": [
{
"$and": [
{
"type": {
"$in": ["image/jpeg"]
}
}, {
"shares._account": "526fcb177675f27140000002"
}
]
}
]
}
Both will return example document.
Your current query has some unnecessarily complicated constructs:
you don't need the $or and $and clauses ("and" is the default behaviour)
you are matching a single value using $in
The query won't find match the sample document because your query doesn't match the data:
the type field you are looking for is "image/jpg" but your sample document has "image/jpeg"
the shares._account value you are looking for is "526fcb177675f27140000001" but your sample document doesn't include this.
A simplified query that should work is:
db.shares.find(
{
"type": "image/jpeg",
"shares._account": "526fcb177675f27140000002"
}
)