I have the following Array of data:
{
_id: 5f5726ef7d475a61a95c5e0c,
attributes: [
{
values: [
{ name: '1' }
],
},
{
values: [
{ name: '2' }
]
}
],
attr1: [
{ name: "Study Code" },
{ name: "Patient Study" }
]
}
What I need is to add the correspondent value to each on of attr1 objects based on index. So the result would be:
{
_id: 5f5726ef7d475a61a95c5e0c,
attributes: [
{
values: [
{ name: '1' }
],
},
{
values: [
{ name: '2' }
]
},
],
attr1: [
{
name: "Study Code",
values: [{ name: "1" }]
},
{
name: "Patient Study",
values: [{ name: "2" }]
}
],
}
I wonder if that possible using aggregation $addFields in MongoDB
Query
query works if arrays same size
ziparray to make [[member1_1 member2_1], ....]
map to merge member1_1,member2_1 to a document
Playmongo
aggregate(
[{"$set": {"attr1": {"$zip": {"inputs": ["$attributes", "$attr1"]}}}},
{"$set":
{"attr1":
{"$map":
{"input": "$attr1",
"in":
{"$mergeObjects":
[{"$arrayElemAt": ["$$this", 1]},
{"$arrayElemAt": ["$$this", 0]}]}}}}}])
You can use $zip
db.collection.aggregate([
{
"$project": {
attributes: {
"$zip": {
"inputs": [
"$attributes",
"$attr1"
]
}
}
}
}
])
Here is the Mongo playground for your reference.
Related
I have this problem where I need to check if a specific item exists by its id in the "Items" field, if it exists then I need to add new indices, I already did the adding new indices part but I don't know how I can add an item new when it does not exist.
Document to modify
{
UserID: 100,
UserAddress: '0x000000',
Items: [
{
item_id: 1000,
item_type: "NULL",
item_indexes: [ "0x11111", "0x22222", "0x33333" ]
},
{
item_id: 2000,
item_type: "NULL",
item_indexes: [ "0x44444", "0x55555", "0x66666" ]
}
],
}
Code to add new indexes
this.raw.bulkWrite(objects.map(object => {
return {
updateOne: {
filter: {
UserID: userId
},
update: {
$addToSet: {
"Items.$[t].item_indexes": {
$each: object.indexesAsArray
}
},
},
upsert: true,
arrayFilters: [{
"t.item_id": object.itemId
}]
}
} as any
}))
What I expect is to be able to insert new items that do not exist in the 'Items' field, but if it exists then add new indexes, I clarify that it must be in the same query
Example
Document to modify
{
UserID: 100,
UserAddress: '0x000000',
Items: [
{
item_id: 1000,
item_type: "NULL",
item_indexes: [ "0x11111", "0x22222", "0x33333" ]
}
],
}
expected result
{
UserID: 100,
UserAddress: '0x000000',
Items: [
{
item_id: 1000,
item_type: "NULL",
item_indexes: [ "0x11111", "0x22222", "0x33333", "0x88888", "0x99999" ]
},
{
item_id: 2000,
item_type: "NULL",
item_indexes: [ "0x44444" ]
}
],
}
In the following function I am trying to dynamically build a mongo $or query condition. The priceRanges is received as a parameter to the function. I iterate over priceRanges as follows to build the $or statement for my projection:
let $or = [];
for(let filter of priceRanges) {
$or.push( { $gte: [ "$price", +filter.low ] }, { $lte: [ "$price", +filter.high ] })
}
The $or array now contains the following values:
console.log('$or', $or)
$or [
{ '$gte': [ '$price', 100 ] }, { '$lte': [ '$price', 200 ] },
{ '$gte': [ '$price', 200 ] }, { '$lte': [ '$price', 300 ] },
{ '$gte': [ '$price', 300 ] }, { '$lte': [ '$price', 400 ] }
]
Here I build the project statement:
let $project = {
name:1,
producttype:1,
brand:1,
model:1,
price:1,
list_price:1,
description:1,
rating:1,
sku:1,
feature:1,
image:1,
images: 1,
specifications:1,
};
I append the $or condition to the projection:
$project.priceRange = {$or: $or};
The $or statement looks like this:
{ '$or':
[ { '$gte': [Array] },{ '$lte': [Array] },
{ '$gte': [Array] }, { '$lte': [Array] },
{ '$gte': [Array] }, { '$lte': [Array] } ] }
I create an array of my projection statement:
aggregateArray.push({$project: $project});
console.log(aggregateArray) looks like this:
aggregateArray [ { '$project':
{ name: 1,
producttype: 1,
brand: 1,
model: 1,
price: 1,
list_price: 1,
description: 1,
rating: 1,
sku: 1,
feature: 1,
image: 1,
images: 1,
specifications: 1,
priceRange: [Object] } },
{ '$skip': 1 },
{ '$limit': 4 } ]
I execute the projection as follows:
let products = await Product.aggregate(aggregateArray);
When executed, the $or statement doesn't seem to have any effect. The result contains random prices and not the ranges specified.
The problem here is that javascript array's push methods takes an array of values as a parameter so $or.push( { $gte: [ "$price", +filter.low ] }, { $lte: [ "$price", +filter.high ] }) pushes two separate filtering conditions. Therefore price equal to 50 will also be included in your result since it matches second condition (lower than 200). To fix that you need to combine those pairs using $and so your final filtering condition should look like this:
var $or = [
{ $and: [ { '$gte': [ '$price', 100 ] }, { '$lte': [ '$price', 200 ] } ] },
{ $and: [ { '$gte': [ '$price', 200 ] }, { '$lte': [ '$price', 300 ] } ] },
{ $and: [ { '$gte': [ '$price', 300 ] }, { '$lte': [ '$price', 400 ] } ] }
]
Then if you need it for filtering it should be using with $expr inside of $match stage.
db.col.aggregate([
{
$match: { $expr: { $or: $or } }
},
// other aggregation stages
])
I'm trying to perform a tricky aggregation to return the size of a nested array within a document in the collection.
Here is how to re-create my sample data:
db.test.insert({
projects: [
{
_id: 1,
comments: [
'a',
'b',
'c'
]
},
{
_id: 2,
comments: [
'a',
'b'
]
},
{
_id: 3,
comments: []
}
]
})
The aggregation I would perform goes here:
db.test.aggregate([
// enter aggregation here
])
Here is the desired output:
[{
projects: [
{
_id: 1,
comment_count: 3
},
{
_id: 2,
comment_count: 2
},
{
_id: 3,
comment_count: 0
}
]
}]
I'm struggling with how to write this. If I try the following:
"projects.comment_count": {"$size": }
The result returns the size of the resulting array:
[{
projects: [
{
_id: 1,
comment_count: 3
},
{
_id: 2,
comment_count: 3
},
{
_id: 3,
comment_count: 3
}
]
}]
If I try to use the $map method like this:
"projects.comment_count": {
"$map": {
"input": "$projects",
"as": "project",
"in": {
"$size": "$$project.comments"
}
}
}
It will return an array that looks like this for each object in the array:
[{
projects: [
{
_id: 1,
comment_count: [3, 2, 0]
},
{
_id: 2,
comment_count: [3, 2, 0]
},
{
_id: 3,
comment_count: [3, 2, 0]
}
]
}]
Thanks in advance!
Here is an idea using $unwind, $group and then $push with $size. Finally $project to get rid of that _id:
db.collection.aggregate([
{
"$unwind": "$projects"
},
{
$group: {
_id: null,
"projects": {
$push: {
_id: "$projects._id",
comment_count: {
$size: "$projects.comments"
}
}
}
}
},
{
"$project": {
"_id": 0
}
}
])
You can see the result here
You need to specify each field inside the in argument of $map aggregation and finally use $size with the comments array.
Something like this
db.collection.aggregate([
{ "$project": {
"projects": {
"$map": {
"input": "$projects",
"in": {
"_id": "$$this._id",
"comment_count": {
"$size": "$$this.comments"
}
}
}
}
}}
])
Output
[
{
"projects": [
{
"_id": 1,
"comment_count": 3
},
{
"_id": 2,
"comment_count": 2
},
{
"_id": 3,
"comment_count": 0
}
]
}
]
I'm using Mongoose in order to check if an element exist in my DB.
For that I'm using something like-
object.aggregate([
{$match: {
"data": {
$elemMatch: {
$and:[
{name: {$in: ["A", "B"]}},
{name: {$nin: ["X"]}}
]
}
}
}
}
])
The problem is text is array. So when I'm getting ["A" , "X"] I wish the result to be empty.
Do you have any ideas?
----------- Edit ------------
Object looks something like:
example1 = { // shouldn't find - not in $in
_id: "123",
data: [
{id:"1",name:"E"},
{id:"2",name:"R"},
{id:"3",name:"T"}
]
}
example2 = { // shouldn't find - in both $in $nin
_id: "456",
data: [
{id:"4",name:"A"},
{id:"5",name:"X"},
{id:"6",name:"Z"}
]
}
example3 = { // should find - only in $in
_id: "789",
data: [
{id:"7",name:"A"},
{id:"8",name:"L"},
{id:"9",name:"Z"}
]
}
----------- Edit 2 ------------
Thanks #Anthony For helping me find a better example to the issue.
I think this issue occurs within arrays of array, example:
Data Set
[
{
_id: "456",
data: [
{
field: [
{
id: "1",
name: "A"
},
{
id: "2",
name: "B"
},
{
id: "3",
name: "Z"
}
]
},
{
field: [
{
id: "4",
name: "A"
},
{
id: "5",
name: "X"
},
{
id: "6",
name: "Z"
}
]
}
]
}
]
My solution with element match gets 2 docs:
(enter link description here)
db.collection.aggregate([
{
$match: {
data: {
$elemMatch: {
"$and": [
{
"field.name": {
"$in": [
"A",
"B"
]
}
},
{
"field.name": {
"$nin": [
"X"
]
}
}
]
}
}
}
}
])
The other solution suggested returns 0 docs:
(enter link description here)
db.collection.find({
"$and": [
{
"data.field.name": {
"$in": [
"A",
"B"
]
}
},
{
"data.field.name": {
"$nin": [
"X"
]
}
}
]
})
You can try this with simple find query
db.collection.find({
"data": {
"$elemMatch": {
"$and": [
{ "field.name": { "$in": [ "A", "B" ] } },
{ "field.name": { "$nin": [ "X" ] } }
]
}
}
}, {
"data": {
"$elemMatch": {
"$and": [
{ "field.name": { "$in": [ "A", "B" ] } },
{ "field.name": { "$nin": [ "X" ] } }
]
}
}
})
Try it here
I have a collection with different entries like this
foods{
{
name: 'rice'
type: 'Brazilian'
},
{
name: 'meat'
type: 'Spanish'
}
,
{
name: 'Cake'
type: 'Brazilian'
}
How do I select an get a specific amount foods of each type of food
for example it should return a collection(array) of 4 foods for each type in mongodb
[
[
{
name: 'rice'
type: 'Brazilian'
},
{
name: 'meat'
type: 'Brazilian'
}
{
name: 'pizza'
type: 'Brazilian'
},
{
name: 'bread'
type: 'Brazilian'
}
],
[
{
name: 'beans'
type: 'spanish'
},
{
name: 'fish'
type: 'spanish'
}
{
name: 'chocolare'
type: 'spanish'
},
{
name: 'ham'
type: 'spanish'
}
]
]
Use aggregation to get the desired result. In your aggregation pipeline, the $group operator takes center stage in creating the desired result since you can group the documents from the collection getting into the pipeline by the type key. Once grouped then you can use one of the accumulator operators $push to create the array. Something like the following:
var pipeline = [
{
"$group": {
"_id": "$type",
"docs": {
"$push": { "name": "$name", "type": "$type" }
}
}
}
]
var result = db.foods.aggregate(pipeline).map(function (doc){ return doc.docs });
printjson(result);