JOLT prefix soup example with an array of objects as input - jolt

I am looking at the prefix soup example from jolt-demo.appspot, which seems to be almost exactly what I need however I made a minor modification on the input to have multiple objects in an array, then I changed the spec to have * wildcard to account for the array and the result is not exactly how I need it. I'm obviously missing something fundamental but I don't know what. Any help would be greatly appreciated.
Input JSON
[
{
"rating-primary": 1,
"rating-Price": 2,
"rating-Design": 4,
"rating-RatingDimension3": 1
},
{
"rating-primary": 7,
"rating-Price": 8,
"rating-Design": 9,
"rating-RatingDimension3": 10
}
]
Spec
[
{
"operation": "shift",
"spec": {
"*": {
"rating-primary": "Rating",
"rating-*": "SecondaryRatings.&(0,1)"
}
}
}
]
Expected Output
[
{
"Rating": 1,
"SecondaryRatings": {
"Primary": 1,
"Price": 2,
"Design": 4,
"RatingDimension3": 1
}
},
{
"Rating": 7,
"SecondaryRatings": {
"Primary": 1,
"Price": 2,
"Design": 4,
"RatingDimension3": 1
}
}
]
Actual Output
{
"Rating" : [ 1, 7 ],
"SecondaryRatings" : {
"Price" : [ 2, 8 ],
"Design" : [ 4, 9 ],
"RatingDimension3" : [ 1, 10 ]
}
}

I figured it out. Here is the spec in case anyone else needs it.
[
{
"operation": "shift",
"spec": {
"*": {
"rating-*": "[&1].SecondaryRatings.&(0,1)",
"rating-primary": "[&1].Rating"
}
}
}
]
'''

Related

Create array if not exists or append item to existing

I need to add items to a specific subobject in my collection. Is is possible to do this using just the update method considering that the array may not exist in some objects?
Here's a sample structure:
[
{
"key": 1,
"someValue": "abc",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 1,
"value": 29
},
{
"paymentNumber": 2,
"value": 22
}
]
}
]
},
{
"key": 2,
"someValue": "xyz",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 3,
"value": 10
},
{
"paymentNumber": 4,
"value": 1
},
{
"paymentNumber": 5,
"value": 5
}
]
}
]
}
]
And here's the syntax I'm using:
db.collection.update({
"installments.payments.paymentNumber": 4
},
{
"$push": {
"installments.payments": [
{
"receipts": [
{
"receiptNumber": 1
}
]
}
]
}
})
This is causing this error:
fail to run update: multiple write errors: [{write errors: [{Cannot create field 'payments' in element {installments: [ { number: 1.0, payments: [ { paymentNumber: 3.0, value: 10.0 }, { paymentNumber: 4.0, value: 1.0 }, { paymentNumber: 5.0, value: 5.0 } ] } ]}}]}, {<nil>}]
Is it possible?
Here's the Mongo Playground link
And here's the expected result:
[
{
"key": 1,
"someValue": "abc",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 1,
"value": 29
},
{
"paymentNumber": 2,
"value": 22
}
]
}
]
},
{
"key": 2,
"someValue": "xyz",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 3,
"value": 10
},
{
"paymentNumber": 4,
"value": 1,
"receipts": [{
"receiptNumber": 1
}]
},
{
"paymentNumber": 5,
"value": 5
}
]
}
]
}
]
To update nested arrays, the filtered positional operator $[identifier] identifies the array elements that match the arrayFilters conditions for an update operation.
Try the following query to $push in nested array:
db.collection.update({
"installments.payments.paymentNumber": 4,
},
{
"$push": {
"installments.$.payments.$[payments].receipts": {
"receiptNumber": 1
}
}
},
{
"arrayFilters": [
{
"payments.paymentNumber": 4
}
],
multi: true
})
MongoDB Playground

Updating nested array of documents as object

I have a collection called 'A' with thousands of documents in it. Here grades is the array of sub documents which consist attribute questions which is array of number like below
{ "_id" : 1,
"grades" : [
{ type: "quiz", questions: [ 10, 8, 5 ] },
{ type: "quiz", questions: [ 8, 9, 6 ] },
{ type: "hw", questions: [ 5, 4, 3 ] },
{ type: "exam", questions: [ 25, 10, 23, 0 ] }
]
}
In order to add additional feature we have to modify the format of questions and make it array of documents which has two fields one is number (which will inherit the old data) and another one is attempts(by default it will be empty array and may have value 'P'- Pass or 'F'- Fail). After this transformation the document should look like below
{ "_id" : 1,
"grades" : [
{
type: "quiz",
questions:[
{ number: 10, attempts: ['F', 'P'] },
{ number: 8, attempts: [] },
{ number: 5, attempts: ['P'] }
]
},
{
type: "quiz",
questions:
[
{ number: 8, attempts: ['F', 'P'] },
{ number: 9, attempts: [] },
{ number: 6, attempts: ['P'] }
]
},
{
type: "hw",
questions:
[
{ number: 5, attempts: ['F', 'P'] },
{ number: 4, attempts: [] },
{ number: 3, attempts: ['P'] }
]
},
{
type: "exam",
questions:
[
{ number: 25, attempts: ['F', 'P'] },
{ number: 10, attempts: [] },
{ number: 23, attempts: ['P'] },
{ number: 0, attempts: ['P', 'P'] }
]
}
]
}
Appreciate for all the help
You can test yourself here: https://mongoplayground.net/p/ZEp_vk5hhf7
Brief explanation:
You have to use $map two times.
First you have to loop the $grades field, because he is an array.
As you are looping through $grades array, you have to loop through the $questions field, because he is an array too.
Query:
db.collection.aggregate([
{
"$project": {
"grades": {
"$map": {
"input": "$grades",
"as": "g",
"in": {
"type": "$$g.type",
"questions": {
"$map": {
"input": "$$g.questions",
"as": "q",
"in": {
"number": "$$q",
"attempts": []
}
}
}
}
}
}
}
}
])
Result:
[
{
"_id": 1,
"grades": [
{
"questions": [
{
"attempts": [],
"number": 10
},
{
"attempts": [],
"number": 8
},
{
"attempts": [],
"number": 5
}
],
"type": "quiz"
},
{
"questions": [
{
"attempts": [],
"number": 8
},
{
"attempts": [],
"number": 9
},
{
"attempts": [],
"number": 6
}
],
"type": "quiz"
},
{
"questions": [
{
"attempts": [],
"number": 5
},
{
"attempts": [],
"number": 4
},
{
"attempts": [],
"number": 3
}
],
"type": "hw"
},
{
"questions": [
{
"attempts": [],
"number": 25
},
{
"attempts": [],
"number": 10
},
{
"attempts": [],
"number": 23
},
{
"attempts": [],
"number": 0
}
],
"type": "exam"
}
]
}
]

Order results from MongoDB Query

I'm executing a MongoDB query in ExpressJS via Mongoose.
I have documents that look like the below
{
"name": "first",
"spellings": [
"here",
"is",
"you"
],
"keyStageLevel": 4,
"spellingLevel": 2,
"__v": 0
},
{
"name": "second",
"spellings": [
"her",
"is",
"another"
],
"keyStageLevel": 2,
"spellingLevel": 3,
"__v": 0
},
{
"name": "third",
"spellings": [
"snother",
"list"
],
"keyStageLevel": 2,
"spellingLevel": 4,
"__v": 0
}
I would like to have the result of my query returned so that
1) the keyStageLevel are in order and
2) within each keyStageLevel the spellingLevel are shown in order with the details of the document.
keyStageLevel 2
spellingLevel 3
name: "second",
"spellings": [
"her",
"is",
"another"
]
spellingLevel 4
name: "third",
"spellings": [
"snother",
"list"
]
keyStageLevel 4
spellingLevel 2
etc
My code currently runs
var spellings = await Spelling.aggregate([{"$group" : {_id:{keyStageLevel:"$keyStageLevel",spellingLevel:"$spellingLevel"}}} ]);
which retuns
[
{
"_id": {
"keyStageLevel": 2,
"spellingLevel": 4
}
},
{
"_id": {
"keyStageLevel": 2,
"spellingLevel": 3
}
},
{
"_id": {
"keyStageLevel": 5,
"spellingLevel": 1
}
},
{
"_id": {
"keyStageLevel": 4,
"spellingLevel": 2
}
}
]
Many thanks for any help.
What you are mostly after is using $group to accumulate the remaining document data under each "keyStageLevel" this is done using $push. If you want results in specific order then you always need to $sort, being both before and after feeding to a $group stage:
var spellings = await Spelling.aggregate([
{ "$sort": { "keyStageLevel": 1, "spellingLevel": 1 } },
{ "$group" : {
"_id": { "keyStageLevel": "$keyStageLevel" },
"data": {
"$push": {
"spellingLevel": "$spellingLevel",
"name": "$name",
"spellings": "$spellings"
}
}
}},
{ "$sort": { "_id": 1 } }
])
The first $sort ensures the items added via $push are accumulated in that order, and the final ensures that the "output" is actually sorted in the desired order, as $group will likely not always return the grouped keys in any specific order unless you instruct with such a stage.
This will give you output like:
{
"_id" : {
"keyStageLevel" : 2
},
"data" : [
{
"spellingLevel" : 3,
"name" : "second",
"spellings" : [
"her",
"is",
"another"
]
},
{
"spellingLevel" : 4,
"name" : "third",
"spellings" : [
"snother",
"list"
]
}
]
}
{
"_id" : {
"keyStageLevel" : 4
},
"data" : [
{
"spellingLevel" : 2,
"name" : "first",
"spellings" : [
"here",
"is",
"you"
]
}
]
}

mongo query: add a new field with ranking number based on another field

I am new to mongo queries. Currently I have a collection like this, which is used to create a d3 force-directed graph.
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763
},
{
"id": "jason#gmail.com",
"count": 1,
"nodeUpdatetime": 1525447783
}
],
"links": [{
"source": "bmw#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525312111
}, {
"source": "car#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525334013
}, {
"source": "bmw#gmail.com",
"target": "car#gmail.com",
"timestamp": 1525334118
}]
}
Using a mongo query, I would like to generate the output to something like this. Basically for the nested data under "nodes", add a new field called "topn" and rank them by count from 1 to 5. The remainder values are null. Can anyone help? Thank you!
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732,
"topn": 1
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null,
"topn": 2
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742,
"topn": 4
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847,
"topn": null
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758,
"topn": 5
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763,
"topn": 3
},
..............
The following should get you what you want:
db.collection.aggregate({
$unwind: "$nodes" // flatten the "nodes" array
}, {
$sort: { "nodes.count": -1 } // sort descending by "count"
}, {
$group: { // create the original structure again - just with sorted array elements
_id: "$_id",
nodes: { "$push": "$nodes" }
}
}, {
$addFields: {
"nodes": {
$zip: { // zip two arrays together
inputs: [
"$nodes", // the first one being the existing and now sorted "nodes" array
{ $range: [ 1, 6 ] } // and the second one being [ 1, 2, 3, 4, 5 ]
],
useLongestLength: true // do not stop after five elements but instead continue using a "null" value
}
}
}
}, {
$addFields: {
"nodes": {
$map: { // transform the "nodes" array
input: "$nodes",
as: "this",
in: {
$mergeObjects: [ // by merging two objects
{ $arrayElemAt: [ "$$this", 0] }, // the first sits at array position 0
{
topn: { $arrayElemAt: [ "$$this", 1] } // the second will be a new entity witha a "topn" field holding the second element in the array
}
]
}
}
}
}
})

How to multi update of a nested array in MondoDB?

I have documents in a MongoDB 'playground' collection following the below "schema" :
{
"_id": ObjectId("54423b40c92f9fffb486a6d4"),
"ProjectFileId": 1,
"SourceLanguageId": 2,
"TargetSegments": [
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
},
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
}
]
}
And the following update query:
db.playground.update({
$and: [
{
"TargetSegments.Colors": {
$exists: true
}
},
{
"ProjectFileId": 1
},
{
"SourceLanguageId": 2
},
{
"TargetSegments": {
$elemMatch: {
"LanguageId": 1
}
}
}
]
},
{
$set: {
"TargetSegments.$.Segment": null,
"TargetSegments.$.Colors": [],
"TargetSegments.$.Widths": [],
"TargetSegments.$.Heights": []
}
},
false, true)
After the execution of the query the result is:
{
"_id": ObjectId("54423b40c92f9fffb486a6d4"),
"ProjectFileId": 1,
"SourceLanguageId": 2,
"TargetSegments": [
{
"LanguageId": 1,
"Segment": null,
"Colors": [],
"Heights": [],
"Widths": []
},
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
}
]
}
As you can see, only the first element of the "TargetSegments" array is updated.
How can I update all the elements of the TargetSegments array in one update query?
Its because you are using $ operator: The positional $ operator identifies an element (not multi) in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
You can use below code to do it:
db.playground.find({
$and: [
{
"TargetSegments.Colors": {
$exists: true
}
},
{
"ProjectFileId": 1
},
{
"SourceLanguageId": 2
},
{
"TargetSegments": {
$elemMatch: {
"LanguageId": 1
}
}
}
]
}).forEach(function(item)
{
var targets = item.TargetSegments;
for(var index = 0; index < targets.length; index++)
{
var target = targets[index];
target.Segment = null,
target.Colors= [],
target.Widths= [],
target.Heights= []
}
db.playground.save(item);
});