Twig set key on multidimensional array - twig

i want to set the keys for an array but have some troubles:
{% set status = [
{10 : { 'status': 'unchanged', 'color': 'red'}},
{20 : { 'status': 'changed', 'farbe': 'green'}}
] %}
gives me:
array(2) {
[0]=> array(1) { [10]=> array(2) { ["status"]=> string(9) "unchanged" ["color"]=> string(3) "red" } }
[1]=> array(1) { [20]=> array(2) { ["status"]=> string(7) "changed" ["farbe"]=> string(5) "green" } } }
but i want to get:
array(2) {
[10]=> array(2) { ["status"]=> string(9) "unchanged" ["color"]=> string(3) "red" }
[20]=> array(2) { ["status"]=> string(7) "changed" ["farbe"]=> string(5) "green" } }
Is there a way to achieve this?

{% set status = { 10 : { .... }, 20 : { ..... }, } %}

Related

Filter documents with a property that can be or true or null MongoDB

I have this query:
collection.aggregate([
{ $match:{ "card.discovery": true } }
{ $sample: { size: 4 } }
]);
And it works because I need to get 4 random entries from that collection, but card.discovery can be true or null, and this obviously only returning documents with card.discovery set to true.
How can I filter the documents so that card.discovery can be or true or null?
You can take advantage of the $in operator:
{ $match: { "card.discovery": { $in: [ true, null ] } } }
Mongo Playground
Try this $or option
collection
[{
"_id" : 1.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 2.0,
"card" : {
"discovery" : null
}
}
{
"_id" : 3.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 4.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 5.0,
"card" : {
"discovery" : null
}
}
{
"_id" : 6.0,
"card" : {
"discovery" : false
}
}
{
"_id" : 7.0,
"card" : {
"discovery" : false
}
}]
Query
db1.aggregate([
{ $match: { $or: [{ "card.discovery": true }, { "card.discovery": null }] }, },
{ $sample: { size: 4 } }
]).toArray();
Output
[
{
"_id": 4,
"card": {
"discovery": true
}
},
{
"_id": 1,
"card": {
"discovery": true
}
},
{
"_id": 3,
"card": {
"discovery": true
}
},
{
"_id": 5,
"card": {
"discovery": null
}
}
]
}]
You can use the $in operator:
collection.aggregate([
{ $match: { "card.discovery": { $in: [null, true] } } },
{ $sample: { size: 4 } }
]);
Alternatively, you can use the $or operator:
collection.aggregate([
{ $match: { $or: ["{card.discovery": true }, {"card.discovery": null}] } },
{ $sample: { size: 4 } }
]);

node api to fetch subdocuments using mongoose

My MongoDB stores documents having the following structure:
{
"application_detail":{},
"curl_detail":{
"Curl1":{
"key1":"value1",
"key2":"value2"
},
"Curl2":{
"key1":"value1",
"key2":"value2"
},
"Curl3":{
"key1":"value1",
"key2":"value2"
},
"Curl4":{
"key1":"value1",
"key2":"value2"
},
/*total number of curls are unknown*/
}
}
How can I fetch key1 for all the curls present under curl_detail from mongoDB using express and mongoose?
Expected Output:
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
NOTE: Total number of curls are unknown.
I would like to propose you changing data structrure from
"curl_detail": {
"Curl1": {
"key1": "value1",
"key2": "value2"
},
"Curl2": {
"key1": "value1",
"key2": "value2"
}
}
to
"curl_detail": [{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value1",
"key2": "value2"
}
]
Storing data structure in array will allow you to find some data in an easier way. Also there's no need to have object with properties like Curl1, Curl2 etc.
For the new data structure there are already some answers with the explanation:
1) Mongo find value with unknown parent key
2) Query MongoDB by value when parent key is unknown
3) MongoDB: Find document given field values in an object with an unknown key
UPDATE
If there's no possibility to change the data structure please see this solution (trick with use of $arrayToObject / $objectToArray to determine the name of nested key e.g. "Curl1"):
db.collection.aggregate([{
$addFields: {
curl_detail: {
$arrayToObject: {
$map: {
input: {
$objectToArray: "$curl_detail"
},
as: "details",
in: {
k: "$$details.k",
v: {
key1: "$$details.v.key1"
}
}
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: 1
}
}
])
This outputs the following:
[
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
]
You can check it through Mongo Playground
You can use projection for the query you are looking for:
collection.find({}, {"curl_detail":1}, (findError, results) => {
if(findError) {
//handle the error
return;
}
// here is the expected result
console.log();
});
If this result also works for you:
[
{
"curl_detail": {
"Curl1": "value1",
"Curl2": "value1",
"Curl3": "value1",
"Curl4": "value1"
}
}
]
You can use the following aggregation using the $objectToArray and $arrayToObject
db.collection.aggregate([
{
$addFields: {
curl_detail: {
$map: {
"input": {
"$objectToArray": "$curl_detail"
},
"as": "el",
"in": {
"k": "$$el.k",
"v": "$$el.v.key1",
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: "$curl_detail"
}
}
}
])
Playground
You can use this aggregation with mongoose like this, let's say your model is MyModel:
MyModel.aggregate([
{
$addFields: {
curl_detail: {
$map: {
input: {
$objectToArray: '$curl_detail'
},
as: 'el',
in: {
k: '$$el.k',
v: '$$el.v.key1'
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: '$curl_detail'
}
}
}
]).then(res => {
console.log(res);
});

MongoDB $addToSet to deep nested array of object

Below is my data structure.
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"name" : "Michael",
"payments" : [
{
"month": "2018/09"
"amount": "20"
}
]
},
]
}
I want to push below object to Michael's payments
{
"month": "2018/09",
"amount": "5000"
}
In this case, What I want to is overwrite object, because month: "2018/09" already exist. Like below :
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"name" : "Michale",
"payments" : [
{
"month": "2018/09"
"amount": "5000"
}
]
},
]
}
And, In case when I want to push object that not exist same month in payments, I want to add this object to payments.
{
"month": "2018/10",
"amount": "2000"
}
So the expected result is
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"payments" : [
{
"month": "2018/09"
"amount": "5000"
},
{
"month": "2018/10"
"amount": "2000"
}
]
},
]
}
I tried like below, but it's not working. My code generate duplicated new month object every time I tried. How can I do this properly?
Rooms.update(
{
_id: "room1",
"members._id": "member1",
"members.$.payments": {
$not: {
$elemMatch: {
month: req.body.month
}
}
}
},
{
$addToSet: {
"members.$.payments": {
month: req.body.month,
amount: req.body.value
}
}
},
{ multi: true }, function (err, result) {
console.log(result)
}
)
You can use below command to add without duplicity either in months or amount
Rooms.update(
{
_id: "room1",
"members._id": "member1"
},
{
$addToSet: {
"members.$.payments": {
month: req.body.month,
amount: req.body.value
}
}
},function (err, result) {
console.log(result)
}
)
So I heard I have to determine duplication myself, so below is my code... it's writing now.,,
So Finally this is my code
Clubs.findOne({
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid)
}, function(err, club){
let member = club.members.filter(el => {
if(el._id.equals(req.params.member_uid)) return el
})
let duplicated = false;
member[0].payments.map(el => {
if(el.month === req.body.month) duplicated = true
})
if(duplicated){
Clubs.update(
{
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid),
},
{
$set: {
["members.$.payments."+index+".amount"] : req.body.value
}
},
function (err, result, third) {
if (err) throw err
console.log('result')
console.log(result)
res.json({})
}
)
} else {
Clubs.update(
{
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid),
},
{
$push: {
"members.$.payments" : {
month : req.body.month,
amount: req.body.value
}
}
},
function (err, result, third) {
if (err) throw err
console.log('result')
console.log(result)
res.json({})
}
)
}
})
Perhaps consider changing the structure of your nested array to an object? So change this
{
"payments": [{
"month": "2018/09"
"amount": "5000"
},
{
"month": "2018/10"
"amount": "2000"
}
]
}
to this:
{
"payments": {
"2018/09": "5000",
"2018/10": "2000"
}
}
Then you can do a simple update:
Rooms.update({
_id: "room1",
"members._id": "member1",
"members.payments": {
$exists: true
}
}, {
$set: {
"members.payments." + req.body.month: req.body.value
}
},
)

How to get group with groupby get the result with dynamic status in mongoDB

I have the collection like below
{
"_id" : ObjectId("5b6538704ba0292b6c197770"),
"Name":"Name1",
"Status":"Good",
},
{
"_id" : ObjectId("5b6538704ba0292b6c197773"),
"Name":"Name2",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name3",
"Status":"Bad"
},{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name1",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197775"),
"Name":"Name1",
"Status":"Good"
}
I have used the query to get the status wise count like below
db.Students.aggregate( [
{ $group: { _id: {
"Name": "$Name","Status":"$Status"}, StatusCount: { $sum: 1 } } }
, { "$project": { _id: 0, Name: "$_id.Name",Status : "$_id.Status", StatusCount:1 } }
] );
The result was
{
"Name":"Name1",
"StatusCount" : 2,
"Status" : "Good"
},
{
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
}, {
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
},
{
"Name":"Name1",
"StatusCount" : 1,
"Status" : "Bad"
}
The result what I am approaching is like
{
"Name":"Name1",
"Good" : 2,
"Bad" :1
},
{
"Name":"Name2",
"Good" : 0,
"Bad" :1
}
The result I am expecting to have the status of field names and count as its values. I have tried to do this but I could not make it happen. The status, for now, is only two like Good or Bad but may increase in real dataset.
By using the $arrayToObject operator and a final $replaceRoot pipeline step which has a $mergeObjects operator you will get your desired result.
You would need to run the following aggregate pipeline on MongoDB Server 3.4.4 or newer:
const pipeline = [
{ '$group': {
'_id': {
'Name': '$Name',
'Status': '$Status'
},
'StatusCount': { '$sum': 1 }
} },
{ '$group': {
'_id': '$_id.Name',
'counts': {
'$push': {
'k': '$_id.Status',
'v': '$StatusCount'
}
}
} },
{ '$replaceRoot': {
'newRoot': { '$mergeObjects': [
{ '$arrayToObject': '$counts' },
{ 'Name': '$_id' }
] }
} }
];
db.Students.aggregate(pipeline);

Aggregation with nodejs driver - grouping by whether a field is empty

The docs are simple as:
[
{'id': '1', 'type': 'a', 'startedAt': '2017-06-11'},
{'id': '2', 'type': 'b', 'startedAt': ''},
{'id': '3', 'type': 'b', 'startedAt': '2017-06-11'}
]
And the expected aggregated result:
[
{'type': 'a', 'started': true, 'count': 1},
{'type': 'b', 'started': true, 'count': 1},
{'type': 'b', 'started': false, 'count': 1}
]
How to get above result with mongodb nodejs driver?
I've tried like below, but it didn't work ('started' was always null):
db.collection('docs').group(
{'type': '$type', 'started': {
$cond: [{$eq: ['$startedAt': '']}, false, true ]
}},
{},
{'total': 0},
'function(curr, result) {result.total++}'
)
You use .aggregate() here and not .group(), which is a different function altogether:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ "$startedAt", "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
The $gt operator returns true when the condition is met. In this case any content in a string is "greater than" an empty string.
If the field is actually "not present at all" then we can adapt with $ifNull. This gives a default have if the property does not actually exist, or otherwise evaluates to null.
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
This would produce:
{ "_id" : { "type" : "b", "started" : true }, "count" : 1 }
{ "_id" : { "type" : "b", "started" : false }, "count" : 1 }
{ "_id" : { "type" : "a", "started" : true }, "count" : 1 }
You can optionally $project afterwards to change the fields from being within _id in the results, but you really should not since this means an additional pass through results, when you can just as easily access the values anyway.
So just .map() on the result:
console.log(
results.map(function(r) {
return { type: r._id.type, started: r._id.started, count: r.count }
})
);
But with $project:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"tcount": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"type": "$_id.type",
"started": "$_id.started",
"count": "$tcount"
}}
],function(err, results) {
console.log(results);
})
Resulting in your desired format
{ "type" : "b", "started" : true, "count" : 1 }
{ "type" : "b", "started" : false, "count" : 1 }
{ "type" : "a", "started" : true, "count" : 1 }
For reference, the correct usage with .group() would be:
db.collection('docs').group(
function(doc) {
return {
"type": doc.type,
"started": (
(doc.hasOwnProperty('startedAt') ? doc.startedAt : "") > ""
)
}
},
[],
{ "count": 0 },
function(curr,result) {
result.count += 1
},
function(err,results) {
console.log(results);
}
);
Which returns:
[
{ "type" : "a", "started" : true, "count" : 1 },
{ "type" : "b", "started" : false, "count" : 1 },
{ "type" : "b", "started" : true, "count" : 1 }
]
But you really should no use that since .group() relies on JavaScript evaluation that runs much slower than what you can do with .aggregate()

Resources