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");
Related
In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.
I have an array that have this format:
data{
[sequentialId]{guid:value1,name:value2}
}
I need to do something like db.data.find("data.?.name":"value1")
All the solutions I've seen don't work because they are expecting me to know the name of the attribute, but in this case the ID is sequential.
I've looked at the standard way of querying nested documents described here
https://docs.mongodb.com/manual/tutorial/query-array-of-documents/
I also looked at some options like $unwind but I can't get anything to work
here's a small sample
{"41":{"b":453081600,"f":1,"h":171,"s":4,"w":4,"wr":[2,0]},
"80":{"b":337132800,"f":2,"h":169,"s":4,"w":4,"wr":[0,0]},
"388":{"b":148694400,"f":1,"h":188,"l":{"c":[{"e":2001,"g":13,"m":292,"s":1992,"t":18},{"e":2006,"g":11,"m":197,"s":2001,"t":1},{"e":2009,"g":2,"m":111,"s":2006,"t":1790},{"e":2009,"g":0,"m":1,"s":2009,"t":1937},{"e":2010,"g":1,"m":14,"s":2010,"t":1},{"e":2011,"g":0,"m":8,"s":2010,"t":13}],"n":[{"e":2007,"g":1,"m":73,"s":1996,"t":1318}]},"s":2,"w":3,"wr":[0,0]}}
for example in this set of data I might need to query all the docs where "f"=1
The following query can get us the expected output:
Note: We are fetching the documents which have name is equal to a
db.collection.aggregate([
{
$addFields:{
"filtered":{
$filter:{
"input":"$data",
"as":"info",
"cond":{
$let:{
"vars":{
"array":{
$objectToArray:"$$info"
}
},
"in":{
$in:["a","$$array.v.name"]
}
}
}
}
}
}
},
{
$match:{
"filtered.0":{
$exists:true
}
}
},
{
$project:{
"filtered":0
}
}
]).pretty()
Data set:
{
"data":[
{
"41": {
"b": 453081600,
"name": "a"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
{
"data":[
{
"41": {
"b": 453081600,
"name": "b"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
Output:
{
"data":[
{
"41": {
"b": 453081600,
"name": "a"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
Query analysis:
We are creating a filtered array to hold only those records from data array which contains name equal to a inside any subdocument with an unknown key.
It's done by first converting each document of data into an array of key-value pairs. Now we can directly query the values without worrying about the keys.
Finally, filter those documents in which the size of filtered array is greater than zero.
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"
}
}
}
}
}
])
I am new to mongodb and still learning it so my question can be naive so please bear with it :)
I have only one json object in mongodb which looks like this.
json object
{
"URLStore": [
{
"description": "adf description",
"url": "www.adf.com"
},
{
"description": "pqr description",
"url": "www.pqr.com"
},
{
"description": "adf description",
"url": "www.adf.com"
}
]
}
I need to query description for url which matches given input. e.g here www.adf.com . I have a code which queries mongodb
mongodb query
BasicDBObject whereQuery = new BasicDBObject();
whereQuery.put("URLStore.url","www.pqr.com");
BasicDBObject fields=new BasicDBObject("URLStore.description", "");
cursor = collection.find(whereQuery,fields);
but the result is something like
{
"_id": {
"$oid": "554b4046e4b072dd9deaf277"
},
"URLStore": [
{
"description": "pqr description"
},
{
"description": "adf description"
},
{
"description": "adf description"
}
]
}
Actually only 1 description should have returned as matching objects with key www.pqr.com is only one. What is wrong with my query? m I missing something here ?
I have already tried question Retrieve only the queried element in an object array in MongoDB collection but using solution mentioned there will return only one object / first match
Use the following aggregation pipeline, should give you the desired results:
db.collection.aggregate([
{
"$match": {
"URLStore.url": "www.adf.com"
}
},
{
"$unwind": "$URLStore"
},
{
"$match": {
"URLStore.url": "www.adf.com"
}
},
{
"$group": {
"_id": {
"url": "$URLStore.url",
"description": "$URLStore.description"
}
}
},
{
"$project": {
"_id": 0,
"description": "$_id.description"
}
}
])
I have the following records:
Record 1:
{
"status": "active",
"users": [
{
"name": "foo",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Record 2:
{
"status": "active",
"users": [
{
"name": "bar",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Now, I wish to return only those records in which the user.name is "foo" and the user.status is "a" - That is I need to get Record 1 back since it satisfies my requirements.
How do I do the AND operation on the array within the record to achieve this?
I tried the following query:
{
"$and": [
{
"users.name": "foo"
},
{
"users.status": "a"
}
]
}
but I get both the records - I need only the first record (since in the users array, it has an entry where the name is "foo" AND the status is "a").
Any clues?
Ok, I found the answer out here
The solution would be to have a query as follows:
{
"users": {
"$elemMatch": {
"name": "foo",
"status": "a"
}
}
}
That did the trick!
Instead of individual field values you can also use whole objects in find queries. Try:
db.collection.find( {
users: {
"name":"foo",
"status":"a"
}
});
This should return all documents where the users array includes an object (or is an object) which looks as described. But note that this doesn't work when the object has additional fields.