How to project an array element as a attribute in a document? - arrays

I am trying to project two elements of an array I got after joining two documents using $lookup. When I use the dot notation to access the array elements as below -
db.departments.aggregate([{
$lookup: {
from: 'employees',
localField: 'dep_id',
foreignField: 'department',
as: 'emps'
}
}, {
$project: {
_id: 0,
emp_id: 1,
salary: 1,
emp_name: '$emps.name',
username: '$emps.username'
}
}])
I get the following result -
emp_id:910579
salary:100000
emp_name:Array
0:"Stephen Wolf"
username:Array
0:"StepWolf"
I want the result as follows -
emp_id:910579
salary:100000
emp_name:"Stephen Wolf"
username:"StepWolf"
Does anybody have any suggestions? I want something that can convert array element to attribute, similar to the ObjectToArray function.

Query1
the bellow does field : [m1 m2 m2] => field : m1
takes the first member and makes it the value of the field
Test code here
aggregate(
[{"$set":
{"emp_name": {"$arrayElemAt": ["$emp_name", 0]},
"username": {"$arrayElemAt": ["$username", 0]}}}])
Query2
If you have MongoDB >=5 you can use $first also
Test code here
aggregate(
[{"$set":
{"emp_name": {"$first": "$emp_name"},
"username": {"$first": "$username"}}}])
In your case use query1 or query2 inside the project you already have, like :
{
$project: {
_id: 0,
emp_id: 1,
salary: 1,
"emp_name": {"$first": "$emp_name"},
"username": {"$first": "$username"}
}
}

Related

MongoDB - Total number of objects in array vs field

I need to get total count of objects in the "photo" array field, and compare it to the value in "mls_media_count" field, so that I can detect when the values do not match.
open_houses:null
open_houses_lock:false
photos:Array
0:Object
1:Object
2:Object
3:Object
4:Object
5:Object
6:Object
7:Object
8:Object
9:Object
mls_media_count: 8
I have tried using $unwind, $project, $size, but haven't had luck getting it to work.
Thanks in advance!
Query
$size can give the size of the array, and we can compare it with the media_count
first query keeps the documents where size is not correct
second query keeps the documents where size is correct
Playmongo
aggregate(
[{"$match":
{"$expr": {"$ne": [{"$size": "$photos"}, "$mls_media_count"]}}}])
Playmongo
aggregate(
[{"$match":
{"$expr": {"$eq": [{"$size": "$photos"}, "$mls_media_count"]}}}])
Thank you for response! This got me on right track and i ended up writing it like this:
[{
$match: {version: '5'
}
}, {
$project: {
item: -1,
Photos: {
$cond: {
'if': {
$isArray: '$photos'
},
then: {
$size: '$photos'
},
'else': 'NA'
}
},
mls_number: 1,
mls_media_count: 1,
'system.kw_photo_count': 1
}
}, {
$match: {
$expr: {
$ne: [
'$Photos',
'$mls_media_count'
]
}
}
}, {
$count: 'list_id'
}]

MongoDB - Pipeline $lookup with $group losing fields

I only have 2 years exp with SQL databases and 0 with NoSQL database. I am trying to write a pipeline using MongoDB Compass aggregate pipeline tool that performs a lookup, group, sum, and sort. I am using MongoDB compass to try and accomplish this. Also, please share any resources that make learning this easier, I've not had much like finding good and easy-to-understand examples online with using the compass to accomplish these tasks. Thank you.
An example question I am trying to solve is:
What customer placed the highest number of orders?
Example Data is:
Customer Collection:
[
{ "_id": { "$oid": "6276ba2dd1dfd6f5bf4b4f53" },
"Id": "1",
"FirstName": "Maria",
"LastName": "Anders",
"City": "Berlin",
"Country": "Germany",
"Phone": "030-0074321"},
{ "_id": { "$oid": "6276ba2dd1dfd6f5bf4b4f54" },
"Id": "2",
"FirstName": "Ana",
"LastName": "Trujillo",
"City": "México D.F.",
"Country": "Mexico",
"Phone": "(5) 555-4729" }
]
Order Collection:
[
{ "_id": { "$oid": "6276ba9dd1dfd6f5bf4b501f" },
"Id": "1",
"OrderDate": "2012-07-04 00:00:00.000",
"OrderNumber": "542378",
"CustomerId": "85",
"TotalAmount": "440.00" },
{ "_id": { "$oid": "6276ba9dd1dfd6f5bf4b5020" },
"Id": "2",
"OrderDate": "2012-07-05 00:00:00.000",
"OrderNumber": "542379",
"CustomerId": "79",
"TotalAmount": "1863.40" }
]
I have spent all day looking at YouTube videos and MongoDB documentation but I am failing to comprehend a few things. One, at the time I do a $group function I lose all the fields not associated with the group and I would like to keep a few fields. I would like to have it returned the name of the customer with the highest order.
The pipeline I was using that gets me part of the way is the following:
[{
$lookup: {
from: 'Customer',
localField: 'CustomerId',
foreignField: 'Id',
as: 'CustomerInfo'
}}, {
$project: {
CustomerId: 1,
CustomerInfo: 1
}}, {
$group: {
_id: '$CustomerInfo.Id',
CustomerOrderNumber: {
$sum: 1
}
}}, {
$sort: {
CustomerOrderNumber: -1
}}]
Example data this returns in order:
Apologies for the bad formatting, still trying to get the hang of posting questions that are easy to understand and useful.
In $group stage, it only returns documents with _id and CustomerOrderNumber fields, so CustomerInfo field was missing.
$lookup
$project - From 1st stage, CustomerInfo returns as an array, hence getting the first document as a document field instead of an array field.
$group - Group by CustomerId, sum the documents as CustomerOrderNumber, and take the first document as CustomerInfo.
$project - Decorate the output documents.
$setWindowsFields - With $denseRank to rank the document position by CustomerOrderNumber (DESC). If there are documents with same CustomerOrderNumber, the ranking will treat them as same rank/position.
$match - Select documents with denseRankHighestOrder is 1 (highest).
db.Order.aggregate([
{
$lookup: {
from: "Customer",
localField: "CustomerId",
foreignField: "Id",
as: "CustomerInfo"
}
},
{
$project: {
CustomerId: 1,
CustomerInfo: {
$first: "$CustomerInfo"
}
}
},
{
$group: {
_id: "$CustomerInfo.Id",
CustomerOrderNumber: {
$sum: 1
},
CustomerInfo: {
$first: "$CustomerInfo"
}
}
},
{
$project: {
_id: 0,
CustomerId: "$_id",
CustomerOrderNumber: 1,
CustomerName: {
$concat: [
"$CustomerInfo.FirstName",
" ",
"$CustomerInfo.LastName"
]
}
}
},
{
$setWindowFields: {
sortBy: {
CustomerOrderNumber: -1
},
output: {
denseRankHighestOrder: {
$denseRank: {}
}
}
}
},
{
$match: {
denseRankHighestOrder: 1
}
}
])
Sample Mongo Playground
Note:
$sort stage able to sort the document by CustomerOrderNumber. But if you try to limit the documents such as "SELECT TOP n", the output result may be incorrect when there are multiple documents with the same CustomerOrderNumber/rank.
Example: SELECT TOP 1 Customer who has the highest CustomerOrderNumber but there are 3 customers who have the highest CustomerOrderNumber.

Update field with value from a nested field

I know I can use the pipeline aggregator within update to update the field of one value with the value from another field. However, my issue is when updated a field value based on the value of a nested field. The result of the update always issues the new field with brackets. I don't want brackets/array, I just want it to be a value. See code below
https://mongoplayground.net/p/7ZDP8CYtKK3
db={
"players": [
{
"_id": ObjectId("5fba17c1c4566e57fafdcd7e"),
"username": "moshe",
"health": 0,
"maxHealth": 200,
"Chapters": [
{"Cat A": 25,
"Cat B": 100,
"Cat C": 125}]
}
]
}
Here's the query I apply below
db.players.update(
{username: "moshe"},
[{"$set": {"health": "$Chapters.Cat A"}}]
)
The result yields
[{"Chapters": [
{"Cat A": 25,
"Cat B": 100,
"Cat C": 125}],
"_id": ObjectId("5fba17c1c4566e57fafdcd7e"),
"health": [25],
"maxHealth": 200,
"username": "moshe"
}]
What I want is for the update to health to appear without array brackets as so.... "health":25
Again this is an example based on a much much larger DB I'm working with.
You can use $arrayElemAt or $first(v4.4) operators to select the first element from an array,
db.players.update(
{ username: "moshe" },
[{
"$set": {
"health": {
"$arrayElemAt": ["$Chapters.Cat A", 0]
}
}
}]
)
Playground

MongoDB Array Query - Single out an array element

I am having trouble with querying a MongoDB collection with an array inside.
Here is the structure of my collection that I am querying. This is one record:
{
"_id": "abc123def4567890",
"profile_id": "abc123def4567890",
"image_count": 2,
"images": [
{
"image_id": "ABC123456789",
"image_url": "images/something.jpg",
"geo_loc": "-0.1234,11.234567890",
"title": "A Title",
"shot_time": "01:23:33",
"shot_date": "11/22/2222",
"shot_type": "scenery",
"conditions": "cloudy",
"iso": 16,
"f": 2.4,
"ss": "1/545",
"focal": 6.0,
"equipment": "",
"instructions": "",
"upload_date": 1234567890,
"update_date": 1234567890
},
{
"image_id": "ABC123456789",
"image_url": "images/something.jpg",
"geo_loc": "-0.1234,11.234567890",
"title": "A Title",
"shot_time": "01:23:33",
"shot_date": "11/22/2222",
"shot_type": "portrait",
"conditions": "cloudy",
"iso": "16",
"f": "2.4",
"ss": "1/545",
"focal": "6.0",
"equipment": "",
"instructions": "",
"upload_date": 1234567890,
"update_date": 1234567890
}
]
}
Forgive the formatting, I didn't know how else to show this.
As you can see, it's a profile with a series of images within an array called 'images' and there are 2 images. Each of the 'images' array items contain an object of attributes for the image (url, title, type, etc).
All I want to do is to return the object element whose attributes match certain criteria:
Select object from images which has shot_type = "scenery"
I tried to make it as simple as possible so i started with:
find( { "images.shot_type": "scenery" } )
This returns the entire record and both the images within. So I tried projection but I could not isolate the single object within the array (in this case object at position 0) and return it.
I think the answer lies with projection but I am unsure.
I have gone through the MongoDB documents for hours now and can't find inspiration. I have read about $elemMatch, $, and the other array operators, nothing seems to allow you to single out an array item based on data within. I have been through this page too https://docs.mongodb.com/manual/tutorial/query-arrays/ Still can't work it out.
Can anyone provide help?
Have I made an error by using '$push' to populate my images field (making it an array) instead of using '$set' which would have made it into an embedded document? Would this have made a difference?
Using aggregation:
db.collection.aggregate({
$project: {
_id: 0,
"result": {
$filter: {
input: "$images",
as: "img",
cond: {
$eq: [
"$$img.shot_type",
"scenery"
]
}
}
}
}
})
Playground
You can use $elemMatch in this way (simplified query):
db.collection.find({
"profile_id": "1",
},
{
"images": {
"$elemMatch": {
"shot_type": 1
}
}
})
You can use two objects into find query. The first will filter all document and will only get those whose profile_id is 1. You can omit this stage and use only { } if you wnat to search into the entire collection.
Then, the other object uses $elemMatch to get only the element whose shot_type is 1.
Check an example here

Mongo project child object but with fewer props

I have a simple document like this in my Collection named 'Partners'
[{
PartnerName: 'Company A',
MarketExpertese: [{Sector: {Code: 1, TotalYears: 1, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 2, TotalYears: 2, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 3, TotalYears: 3, TotalMonths: 20, TotalClients: 10}}]
}]
The result of desired projection would be:
[{
PartnerName: 'Company A',
MarketExpertese: [{SectorCode: 1, TotalYears: 1},
{SectorCode: 2, TotalYears: 2},
{SectorCode: 3, TotalYears: 3}]
}]
I tried the projection with Map but didn´t work. If I would need just an array of SectorCode (simple array, ex. {[1, 2, 3]} ) I could do, but as I need a prop with 2 values (sectorCode and TotalYears) my map fails...
The closet I could get was this Mongo Playground
we need to use $unwind to get a stream of documents regarding the array MarketExpertese, each document will have an element from that array
then use the $project stage to format the output as we need
then use $group to group the documents we have unwind in first step
the query may look something like this
db.collection.aggregate([
{
"$unwind": "$MarketExpertese" // First we need to use unwind to get a stream of documents regarding this array, each document will have only one element from that array
},
{
"$project": {
PartnerName: 1,
Taste: {
SectorCode: "$MarketExpertese.Sector.Code",
TotalYears: "$MarketExpertese.Sector.TotalYears"
}
}
},
{
"$group": { // then group the results
"_id": "$_id",
PartenerName: {
"$first": "$PartnerName"
},
Taste: {
"$addToSet": "$Taste"
}
}
}
])
and here is a working example on Mongo Playground
Update
you can use $map to do the same functionality
db.collection.aggregate([
{
$project: {
"_id": 0,
"PartnerName": 1,
"Teste": {
"$map": {
"input": "$MarketExpertese",
"as": "elem",
"in": {
SectorCode: "$$elem.Sector.Code",
TotalYears: "$$elem.Sector.TotalYears",
}
}
}
}
}
])
and you can test it here Mongo Playground 2

Resources