Mongodb project inside array of object with array of object - mongodb

MongoDB
I have data like:-
[
{
review: [
{
title: "Title1",
professor: [
{
id: "1",
accept: false
},
{
id: "2",
accept: false
}
]
}
]
},
{
review: [
{
title: "Title2",
professor: [
{
id: "3",
accept: false
},
{
id: "2",
accept: false
}
]
}
]
}
]
I want title and professor array with filter of specific id (like "1")
My code:-
for match i use below code and it works perfectly fine
$match
{
review: {
$elemMatch: {
professor: {
$elemMatch: {
id: "1"
}
}
}
}
}
for project i use below code
$project
{
review: {
title:1,
professor:{
$filter:{
input: "$review.professor", (I try only professor but give null)
as: "professor",
cond: {$eq:["$$professor.id", "1" ]}
}
}
}
}
Problem is that professor show array but comes with empty data

Playground
Unwind stage could help you.
db.collection.aggregate([
{
"$match": {
review: {
$elemMatch: {
professor: {
$elemMatch: {
id: "1"
}
}
}
}
}
},
{
"$unwind": "$review"
},
{
$project: {
review: {
title: 1,
professor: {
$filter: {
input: "$review.professor",
as: "professor",
cond: {
$eq: [
"$$professor.id",
"1"
]
}
}
}
}
}
}
])

Related

Mongoose return all documents that contain a value wherever inside a property

Lets say my documents look like this :
_id: "6285e9a7aff93ead37ec50ad",
date: "2022-04-28T10:51:37.923Z",
devices: {
tablets: [
{
brand: "samsung",
model: "s20"
},
{
brand: "samsung",
model: "s21"
},
{
brand: "apple",
model: "ipad_mini"
},
],
phones: [
{
brand: "samsung",
model: "galaxy_s20"
},
{
brand: "samsung",
model: "galaxy_s20_lite"
}
],
laptops: []
}
}
how would i query and return all documents that contain at least an "apple" value in "brand" property wherever inside the "devices" property ?
When you have more dynamic keys, you can use
db.collection.aggregate([
{
$project: {
"d": {
"$objectToArray": "$devices"
}
}
},
{
"$match": {
"d.v.brand": "apple"
}
}
])
You need to restructure the data if required. It returns if there is atleast one match.
playground
db.collection.aggregate([
{
$project: {
"d": {
"$objectToArray": "$devices"
}
}
},
{
"$match": {
"d.v.brand": "apple"
}
},
{
"$project": {
"devices": { //To reshape back
"$arrayToObject": "$d"
}
}
}
])
Playground

nested condition in mongodb aggregation

I have a collection product which looks like this.
{
should_show: {
type: String,
enum: ['always', 'never', 'on_date'],
default: 'always',
},
show_start_date: { type: Date },
show_end_date: { type: Date },
categories: [{ type: ObjectId }],
price: number,
}
find condition like this works fine when I find many products.
const condition = {
'$and': [
{ '$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.find(condition)
But I changed my business logic to use Model.aggregate() instead of `Model.find()
(because I have to use allowdiskuse option)
const condition = {
'$and': [
{ '$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.aggregate([
{ $match: condition },
])
And returned results is always empty array []
I changed my condition simpler like this
const condition = {
'$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ],
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:50.161Z },
show_end_date: { '$gt': 2022-01-03T08:56:50.161Z }
}
]
}
Products.aggregate([
{ $match: condition },
])
but still returns empty array.
How should I fix this problem?
Self Answer:
I have to pass ObjectId by explicit ObjectId type, not string.
const condition = {
'$and': [
{ '$and': [{
// like this:
categories: Mongoose.Types.ObjectId('61bdd930fb1dfb1f65a21f13'),
}] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.aggregate([
{ $match: condition },
])

how to convert mongodb fields to subdocument fields?

sorry it may be silly question, mongodb contains thousands of documents .
hard to be changed manually
original format
{"name" : "aaaa",
"price" : 111,
"ing1" : "abcd",
"ing1Conc" : 50 ,
"ing2" : "wxyz",
"ing2conc": 100}
needed to be converted to
{"name" : "aaaa",
"price" : 111,
"content":[
{ "ing1" : "abcd", "ing1Conc" : 50},
{ "ing2" : "wxyz", "ing2conc": 100}
]
}
The trivial solution would be this one:
db.collection.aggregate([
{
$project: {
name: 1,
price: 1,
content: [
{ ing1: "$ing1", ing1Conc: "$ing1Conc" },
{ ing2: "$ing1", ing2conc: "$ing2conc" }
]
}
}
])
A more generic solution would be this one:
db.collection.aggrega
{
$project: {
name: 1,
price: 1,
data: {
$filter: {
input: { $objectToArray: "$$ROOT" },
cond: { $regexMatch: { input: "$$this.k", regex: "^ing\\d+" } }
}
}
}
},
{ $unwind: "$data" },
{ $set: { i: { $regexFind: { input: "$data.k", regex: "\\d+" } } } },
{ $set: { i: "$i.match" } },
{
$group: {
_id: {
name: "$name",
price: "$price",
i: "$i"
},
content: { $push: "$data" }
}
},
{ $sort: { "_id.i": 1 } },
{ $set: { content: { $arrayToObject: "$content" } } },
{
$group: {
_id: { name: "$_id.name", price: "$_id.price" },
content: { $push: "$content" }
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: [ "$$ROOT", "$_id" ] } } },
{ $unset: "_id" }
])
Mongo Playground
However, I think this structure is still bad. I would suggest something like
content: {
ing: [ "abcd", "wxyz"],
conc: [ 50, 100 ]
}
content: [
{ ing: "abcd", conc: 50 },
{ ing: "wxyz", conc :100 }
]
content: [
{ idx: 1, ing: "abcd", conc: 50 },
{ idx: 2, ing: "wxyz", conc: 100 }
]

How match nested fields in mongo by using the match clause?

I am trying to match nested fields in a document in mongo but my query is not working.
Configuration:
[
{
linenumber: "car",
type: "004",
nested: {
info: {
subinfo: "B"
}
},
},
{
linenumber: "car",
type: "005",
nested: {
info: {
subinfo: "G"
}
},
}
]
query:
db.collection.aggregate([
{
$match: {
$and: [
{
linenumber: "car"
},
{
nested: {
info: {
subinfo: {
$in: [
"B",
"G"
]
}
}
}
}
]
}
},
{
$group: {
_id: null,
types: {
$addToSet: "$type"
}
}
}
])
I am using the mongoplayground to test. Here is the link:
https://mongoplayground.net/p/nND59iO1339
I am getting no documents found.
You are almost there. You need to use dot notation.
play
db.collection.aggregate([
{
$match: {
$and: [
{
linenumber: "car"
},
{
"nested.info.subinfo": { //Dot notation
$in: [
"B",
"G"
]
}
}
]
}
},
{
$group: {
_id: null,
types: {
$addToSet: "$type"
}
}
}
])

How to use $addFields in mongo to add elements to just existing documents?

I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
Playground