Project one field from specific object element in an array mongodb - mongodb

I have a collection:
{
"_id": 1,
"_deleted": false,
"customFields": [{
"fieldName": "sapID",
"value": ""
}, {
"fieldName": "salesTerritory",
"value": ""
}, {
"fieldName": "clientType",
"value": "Corporate"
}],
}
How can I project(aggregate) only the value field of the element with fieldName = "clientType":
db.collection.aggregate(
{
$project:{value:<code>}
}
)
I tried $filter but it does not work

db.collection.aggregate([
{
$project: {
"customFields.value": 1
}
}
])
mongoplayground
db.collection.aggregate([
{
$set: {
customFields: {
$map: {
input: "$customFields",
as: "c",
in: {
$cond: {
if: { "$eq": [ "$$c.fieldName", "clientType" ] },
then: { value: "$$c.value" },
else: "$$c"
}
}
}
}
}
}
])
mongoplayground

What about this?
db.collection.aggregate([
{
$project: {
customFields: {
$filter: {
input: "$customFields",
$cond: { $eq: ["$$this.fieldName", "clientType"] }
}
}
}
}
])
Mongo Playground

Related

Get value of a specific field using aggregation in MongoDB

My document:
[
{
"_id": "5f969419d40c1580f2d4aa36",
"users": {
"foo#bar.com": "baz",
"foo2#bar.com": "baz2"
}
},
{
"_id": "5f9694d4d40c1580f2d4aa38",
"users": {
"baz#test.com": "foo"
}
}
]
If i use this aggregate, i get two users. Ok. But how can i get only the value of "foo#bar.com"?
Test in https://mongoplayground.net/p/3kW2Rw6fSjh
db.collection.aggregate([
{
"$project": {
"users": {
"$objectToArray": "$users"
}
}
},
{
"$match": {
"users.k": "foo#bar.com"
}
},
{
"$project": {
"users": {
"$arrayToObject": "$users"
}
}
}
])
You can add a $filter stage after the $match stage:
{
$set: {
users: {
$filter: {
input: "$users",
cond: {
$eq: [
"$$this.k",
"foo2#bar.com"
]
}
}
}
}
},
See how it works on the playground example

Mongodb loop through every distinct values and select tags using aggregate (facet)

I have collection like this:
{
"labels": [{
"description": "Dog"
}, {
"description": "Red"
}, {
"description": "XXX"
}]
}
{
"labels": [{
"description": "Cat"
}, {
"description": "XXX"
}, {
"description": "Yellow"
}]
}
{
"labels": [{
"description": "Dog"
}, {
"description": "Red"
}, {
"description": "Yellow"
}]
}
{
"labels": [{
"description": "Bird"
}, {
"description": "XXX"
}, {
"description": "XXX"
}]
}
I want to filter for example only "Red" and "Yellow" colors from ALL elements and output document like this:
// because "Dog" appears 2 times so total = 2
{
description: "Dog",
total: 2,
colors: [
{ "_id": "Red", total: 2 },
{ "_id": "Yellow", total: 1 }
]
}
{
description: "Cat",
total: 1,
colors: [
{ "_id": "Yellow", total: 1 }
]
}
{
description: "Bird",
total: 1,
colors: []
}
{
description: "Red",
total: 2,
colors: [
{ _id: "Yellow", total: 1 }
]
}
{
description: "XXX",
total: 4,
colors: [
{ _id: "Yellow", total: 1 }
]
}
I can do this by using collection.distinct('labels.description') and then iterating through every single element + make a separate collection.count({ 'labels.description': 'Dog' }) like this:
for (...)
db.collection.aggregate([
{
"$match": {
"labels.description": valueFromLoop // (e.g. Dog)
}
},
{ $unwind : "$labels" },
{
"$group": {
"_id": "$labels.description",
"count": { "$sum": 1 }
}
},
{
"$match": {
"$or": [
{ "_id": "Red" },
{ "_id": "Yellow" }
]
}
},
{
"$sort": {
"count": -1
}
}
])
I want to do this in a single aggregation or mapReduce so that I could easily output it to new collection using $out instead of using Bulk operations separately, however I don't know if it's possible.
Try this:
let filter = ["Red", "Yellow"];
db.testcollection.aggregate([
{
$addFields: { bkp: "$labels" }
},
{ $unwind: "$labels" },
{
$addFields: {
bkp: {
$filter: {
input: "$bkp",
as: "item",
cond: {
$and: [
{ $ne: ["$$item.description", "$labels.description"] },
{ $in: ["$$item.description", filter] }
]
}
}
}
}
},
{
$unwind: {
path: "$bkp",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: {
key1: "$labels.description",
key2: { $ifNull: ["$bkp.description", false] }
},
total: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.key1",
description: { $first: "$_id.key1" },
total: {
$sum: {
$cond: {
if: { $first: [["$_id.key2"]] },
then: 1,
else: "$total"
}
}
},
colors: {
$push: {
$cond: {
if: { $first: [["$_id.key2"]] },
then: {
_id: "$_id.key2",
total: "$total"
},
else: "$$REMOVE"
}
}
}
}
},
{ $project: { _id: 0 } }
]);
For some reason with code from both answers it does not count all tags properly.
I'm posting what works:
db.collection.aggregate([
{
$project: {
labels: 1,
result: {
$filter: {
input: "$labels",
as: "label",
cond: {
$or: [
{ $eq: ["$$label.description", "Blue"] },
{ $eq: ["$$label.description", "Red"] },
{ $eq: ["$$label.description", "Black-and-white"] },
{ $eq: ["$$label.description", "Purple"] },
{ $eq: ["$$label.description", "Orange"] },
{ $eq: ["$$label.description", "Yellow"] },
{ $eq: ["$$label.description", "Green"] },
{ $eq: ["$$label.description", "Teal"] }
]
}
}
}
}
},
{
$unwind: "$labels"
},
{
"$group": {
_id: "$labels.description",
x: {
$push: "$result.description"
},
total: { "$sum": 1 }
}
},
{
$project: {
x: {
$reduce: {
input: '$x',
initialValue: [],
in: {$concatArrays: ['$$value', '$$this']}
}
},
total: 1
}
},
{
$project: {
x: 1,
y: { $setUnion: "$x" },
total: 1
}
},
{
$project: {
_id: 0,
description: "$_id",
"colors": {
$map: {
input: "$y",
as: "item",
in: {
_id: "$$item",
count: {
$size: {
$filter: {
input: "$x",
as: "itemx",
cond: {
$eq: ["$$item", "$$itemx"]
}
}
}
}
}
}
},
total: 1
}
},
{
$out: "backgrounds_meta"
}
])
db.test2.aggregate([
{
$project: {
labels:1,
colours: {
$filter: {
input: "$labels",
as: "label",
cond: {
$or: [
{$eq:["Yellow","$$label.description"]},
{$eq:["Red", "$$label.description"]}
]
}
}
}
}
},
{$unwind:"$labels"},
{$group:{
_id: "$labels.description",
total: {$sum:1},
colours: {$addToSet:"$colours.description"}
}},
{
$project:{
_id:0,
description:"$_id",
total:1,
colours: {
$reduce:{
input: "$colours",
initialValue: [],
in: {$concatArrays: ["$$value", "$$this"]}
}
}
}
},
{
$unwind: {
path:"$colours",preserveNullAndEmptyArrays: true
}
},
{
$group:{
_id:{
description:"$description",
total:"$total",
colour:"$colours"
},
count: {
$sum: {$cond:[{$ifNull:["$colours",false]},1,0]}
}
}
},
{
$group:{
_id:{
description:"$_id.description",
total:"$_id.total"
},
colours: {
$push: {
$cond: [{$gt:["$count",0]},
{
"_id":"$_id.colour",
total:"$count"
},
"$$REMOVE"
]
}
}
}
},
{
$project: {
_id:0,
description: "$_id.description",
total: "$_id.total",
colours: 1
}
}
]);
**Edit In your answer, you are missing the Yellows for Red and Dog because you are taking the first item from $result with $arrayElemAt: ["$result.description", 0].
If description is a colour, do you also want to include the counts for itself in colours?
Never mind, you've updated the answer

How to convert an array to object in mongodb query?

I am new to mongodb and wanted to convert my array to object using pipeline. For example,
{
field1: [1,2,3,4,5],
field2: [‘a’,’b’,’c’,’d’,’e’],
}
I want the above document to be converted to,
{
fields: [
{
field1: 1,
field2: ‘a’
},
......
{
field1: 5,
field2: ‘e’
}
]
}
Any idea how I can achieve this?
You can use $unwind to separate your arrays.
And then format your new list with $project without forgetting to remove the duplicates created by the $unwind.
db.collection.aggregate({
"$unwind": {
path: "$field1",
includeArrayIndex: "field1_index"
}
},
{
"$unwind": {
"path": "$field2",
"includeArrayIndex": "field2_index"
}
},
{
"$project": {
"fields": {
"field1": "$field1",
"field2": "$field2"
},
"diff": {
$cmp: [
"$field1_index",
"$field2_index"
]
}
}
},
{
"$match": {
"diff": 0
}
},
{
$group: {
_id: "$_id",
fields: {
$push: "$fields"
}
}
})
Try it here
You can use $zip and $map and $reduce to achieve this:
db.collection.aggregate([
{
"$addFields": {
fields: {
$reduce: {
input: {
$zip: {
inputs: [
{
$map: {
input: "$field1",
as: "f1",
in: {
field1: "$$f1"
}
}
},
{
$map: {
input: "$field2",
as: "f2",
in: {
field2: "$$f2"
}
}
}
]
}
},
initialValue: [],
in: {
"$concatArrays": [
[
{
"$mergeObjects": "$$this"
}
],
"$$value"
]
}
}
}
}
}
])
MongoPlayground
Make sure both field1 and field2 are of equal length or you will lose some data.

How to filter string arrays within documents in mongodb?

I have multiple mongodb documents which looks like this
{
"_id": "001",
"car_description": "Audi",
"sales": [
"India/Mumbai",
"India/Delhi",
"India/Chennai",
"India/Kolkata",
"US/NYC",
"US/SF"]
},
{
"_id": "002",
"car_description": "BMW",
"sales": [
"India/Mumbai",
"India/Delhi",
"India/Chennai",
"India/Kolkata",
"US/NYC",
"US/SF"]
}
I am trying to get the car_description and sales which happened in India.
Final output should be something like this.
{
{
"car_description": "Audi",
"sales": [
"India/Mumbai",
"India/Delhi",
"India/Chennai",
"India/Kolkata"]
},
{
"car_description": "BMW",
"sales": [
"India/Mumbai",
"India/Delhi",
"India/Chennai",
"India/Kolkata"]
}
}
EDIT : I tried using this but this does not filter out the sales. Instead it gives an error saying "Unrecognized expression '$regexMatch"
db.collection.aggregate([
{
"$project": {
"car_description": 1,
"sales": 1,
"sales": {
"$filter": {
"input": "$sales",
"as": "sale",
"cond": {
$regexMatch: {
input: "$$sale",
regex: "India",
options: "i"
}
}
}
}
}
}
])
Check out this snippet to see if it fulfills your need:
db.sales.find({
"sales": {
$elemMatch: {
$regex: "^India\/",
$options: "i"
}
}
},
{
"car_description": 1,
"sales": 1
})
https://mongoplayground.net/p/61sqeMXU_yg
EDIT: If you'd like to filter out all regions which are not matched by the regex expression you could try the following aggregate query:
MongoDB >= 4.1.11
db.sales.aggregate([
{
$match: {
"sales": {
$elemMatch: {
$regex: "^India\/",
$options: "i"
}
}
}
},
{
$set: {
sales: {
$filter: {
input: "$sales",
as: "sale",
cond: {
$regexMatch: {
input: "$$sale",
regex: "^India\/",
options: "i"
}
}
}
}
}
}
])
https://mongoplayground.net/p/NGDSVQeXtRI
Older MongoDB releases
db.collection.aggregate([
{
$match: {
"sales": {
$elemMatch: {
$regex: "^India\/",
$options: "i"
}
}
}
},
{
$set: {
sales: {
$filter: {
input: "$sales",
as: "sale",
cond: {
$eq: [
{
$toLower: {
$arrayElemAt: [
{
$split: [
"$$sale",
"/"
]
},
0
]
}
},
"india"
]
}
}
}
}
}
])
https://mongoplayground.net/p/19TARSVTaMN

Mongo Query to fetch distinct nested documents

I need to fetch distinct nested documents.
Please find the sample document:
{
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z"),
"HList":[
{
"productId": 123,
"name": "Dubai",
"tsh": true
}
],
"PList":[
{
"productId": 123,
"name": "Dubai",
"tsh": false
},
{
"productId": 234,
"name": "India",
"tsh": true
}
],
"CList":[
{
"productId": 234,
"name": "India",
"tsh": false
}
]
}
Expected result is:
{
"produts":[
{
"productId": 123,
"name": "Dubai"
},
{
"productId": 234,
"name": "India"
}
]
}
I tried with this query:
db.property.aggregate([
{
$match: {
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z")
}
},
{
"$project": {
"_id": 0,
"unique": {
"$filter": {
"input": {
"$setDifference": [
{
"$concatArrays": [
"$HList.productId",
"$PList.productId",
"$CList.productId"
]
},
[]
]
},
"cond": {
"$ne": [ "$$this", "" ]
}
}
}
}
}
]);
Is $setDifference aggregation is correct choice here?
My query returns only unique product ids but i need a productId with name.
Could someone help me to solve this?
Thanks in advance
You can use $projectfirst to get rid of tsh field and then run $setUnion which ignores duplicated entries:
db.collection.aggregate([
{
$project: {
"HList.tsh": 0,
"PList.tsh": 0,
"CList.tsh": 0,
}
},
{
$project: {
products: {
$setUnion: [ "$HList", "$PList", "$CList" ]
}
}
}
])
Mongo Playground
The following two aggregations return the expected and same result (you can use any of the two):
db.collection.aggregate( [
{
$project: {
_id: 0,
products: {
$reduce: {
input: { $setUnion: [ "$HList", "$PList", "$CList" ] },
initialValue: [],
in: {
$setUnion: [ "$$value", [ { productId: "$$this.productId", name: "$$this.name" } ] ]
}
}
}
}
}
] )
This one is little verbose:
db.collection.aggregate( [
{
$project: { list: { $setUnion: [ "$HList", "$PList", "$CList" ] } }
},
{
$unwind: "$list"
},
{
$group: {
_id: null,
products: { $addToSet: { "productId": "$list.productId", "name": "$list.name" } }
}
},
{
$project: { _id: 0 }
}
] )
db.collection.aggregate([
{
$match: {
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z")
}
},
{
$project: {
products: {
$filter: {
input: { "$setUnion" : ["$CList", "$HList", "$PList"] },
as: 'product',
cond: {}
}
}
}
},
{
$project: {
"_id":0,
"products.tsh": 1,
"products.name": 1,
}
},
])