Does MongoDB support optional filters? - mongodb

I have a case where I want to run a query that MUST match some field conditions but SHOULD match some others. If they don’t match however the query should still return the conditions that MUST.
For example let’s say in a collection I have 3 documents such as:
{ _id: 1, position: “Developer”, name: “Greg”, surname: “Smith” },
{ _id: 2, position: “QA”, name: “Andrew”, surname: “Samson” },
{ _id: 3, position: “Developer”, name: “Adam”, surname: “Mount” }
If I run a query with a condition { position: “Developer” } that is a MUST and { name: “Greg” } that is a SHOULD I should just get the record:
{ _id: 1, position: “Developer”, name: “Greg”, surname: “Smith” }
However, if I run the query again with { position: “Developer” } and { “name”: “Daniel” }, I should get all records that match the MUST condition, therefore I don't want the position condition to fail the query if it doesn't match. So return records:
{ _id: 1, position: “Developer”, name: “Greg”, surname: “Smith” },
{ _id: 3, position: “Developer”, name: “Adam”, surname: “Mount” }
Furthermore, if I have a query with MUST conditon { position: “Developer” } and SHOULD conditions { name: “Greg”, surname: “Garbrandt” } I should still get:
{ _id: 1, position: “Developer”, name: “Greg”, surname: “Smith” }
Not sure if there is a way to write the query to work like this or if there is a functionality that could do this outright.

You can use this one:
var name = "Daniel";
db.collection.aggregate([
{ $match: { position: "Developer" } },
{
$match: {
$expr: {
$cond: {
if: db.collection.findOne({ position: "Developer", name: name }),
then: { $eq: ["$name", name] },
else: true
}
}
}
}
])
Or this one:
db.collection.aggregate([
{ $match: { position: "Developer" } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{ $set: { cond: { $filter: { input: "$data", cond: { $eq: ["$$this.name", "Daniel"] } } } } },
{
$project: {
data: {
$cond: {
if: { $eq: ["$cond", []] },
then: "$data",
else: "$cond"
}
}
}
},
{ $unwind: "$data" },
{ $replaceWith: "$data" }
])

Related

MongoDB Aggregation: Filter array with _id as string by ObjectId

I have the following collections:
const movieSchema = new Schema({
title: String
...
})
const userSchema = new Schema({
firstName: String,
lastName: String,
movies: [
movie: {
type: Schema.Types.ObjectId,
ref: 'Movie'
},
status: String,
feeling: String
]
...
})
I am trying to match up the movie (with all its details) with the user status and feeling for that movie, with the aggregation:
Movie.aggregate([
{ $match: { _id: ObjectId(movieId) } },
{
$lookup: {
from: 'users',
as: 'user_status',
pipeline: [
{ $match: { _id: ObjectId(userId) } },
{
$project: {
_id: 0,
movies: 1
}
},
{ $unwind: '$movies' }
]
}
},
])
Which returns:
[
{
_id: 610b678702500b0646925542,
title: 'The Shawshank Redemption',
user_status: [
{
"movies": {
"_id": "610b678702500b0646925542",
"status": "watched",
"feeling": "love"
}
},
{
"movies": {
"_id": "610b678502500b0646923627",
"status": "watched",
"feeling": "like"
}
},
{
"movies": {
"_id": "610b678502500b0646923637",
"status": "watched",
"feeling": "like"
}
},
]
}
]
My desired result is to match the first movie in user_status to get the eventual final result:
[
{
_id: 610b678702500b0646925542,
title: 'The Shawshank Redemption',
status: "watched",
feeling: "love"
}
]
I thought the next step in my pipeline would be:
{
$addFields: {
user_status: {
$filter: {
input: '$user_status',
cond: {
$eq: ['$$this.movies._id', '$_id']
}
}
}
}
}
But it doesn't work - Not sure if this $addFields is correct, and one problem I know is that my first _id is an ObjectId and the second appears to be a string.
If I understand correctly, you can $filter the user in the already existing $lookup pipeline, which will make things more simple later:
db.movies.aggregate([
{$match: {_id: ObjectId(movieId)}},
{
$lookup: {
from: "users",
as: "user_status",
pipeline: [
{$match: {_id: ObjectId(userId)}},
{$project: {
movies: {
$first: {
$filter: {
input: "$movies",
cond: {$eq: ["$$this.movie", ObjectId(movieId)]}
}
}
}
}
}
]
}
},
{
$project: {
title: 1,
feeling: {$first: "$user_status.movies.feeling"},
status: {$first: "$user_status.movies.status"}
}
}
])
See how it works on the playground example

How to aggregate to have the _id as a object property

I have the following collection:
{
_id: 1,
type: "Feature",
properties: {
name: "Name 1",
<other properties here>
}
},
{
_id: 2,
type: "Feature",
properties: {
name: "Name 2",
<other properties here>
}
}
How can I write a MongoDB Aggregate query to return the following ?
{
data: {
type: "Feature",
properties: {
_id: 1,
name: "Name 1",
<other properties here>
}
}
},
{
data: {
type: "Feature",
properties: {
_id: 2,
name: "Name 2",
<other properties here>
}
}
}
So, the _id should become an attribute of properties and each document should be returned as an object of data.
Simple structure manipulation will suffice:
db.collection.aggregate([
{
$project: {
data: {
type: "$type",
properties: {
$mergeObjects: [
{_id: "$_id"},
"$properties"
]
}
}
}
}
])
try this
1- If you need only the _id to be inside the properties, and not in data object
db.collection.aggregate([
{
$group: {
_id: "$_id",
type: {
$first: "$type"
},
properties: {
$first: {
_id: "$_id",
name: "$properties.name"
}
}
}
},
{
$project: {
_id: 0,
data: "$$ROOT"
}
},
{
$project: {
"data._id": 0
}
}
])
check this Mongo Playground
2- If it's matter only to get the _id inside the the properties object, and you don't care if it's in data or not
you can use this
db.collection.aggregate([
{
$group: {
_id: "$_id",
type: {
$first: "$type"
},
properties: {
$first: {
_id: "$_id",
name: "$properties.name"
}
}
}
},
{
$project: {
_id: 0,
data: "$$ROOT"
}
}
])
check this Mongo Playground 2
I was unable to utilize your dataset as it was not RFC 8259 formatted.
However assuming your dataset is properly formatted like below. The issued command would be near close provided the fields matched
{
"data":{
"Feature":{
"properties":{
"_id1":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
],
"_id2":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
],
"_id3":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
]
}
}
}
}
I would then use the command:
db.orders.aggregate([
{ $match: { properties: "_id1" } },
{ $group: { _id1: "object_of_data1", "object_of_data2"
"object_of_data3" } } }
])
For further reading please see: Aggregation Commands Comparison

Querying nested document structure in mongoDb

I am trying to query below document structure
{
_id: '7ja9sjkadja',
parent: {
parentName: 'Super',
firstGenerations: [
{
name: 'First Generation',
secondGenerations: [
{
name: 'Second Generation 1',
value: 'Married'
},
{
name: 'Second Generation 2',
value: 'Single'
},
{
name: 'Second Generation 3',
value: 'Single'
}
]
}
]
}
}
Expected output:
{
firstGenerationName: 'First Generation',
totalCount: 3
values: [
{
categoryName: 'Married (1)',
count: 1,
firstGenerationName: 'First Generation'
},
{
categoryName: 'Single (2)',
count: 2,
firstGenerationName: 'First Generation'
}
]
}
Query tried by me:
db.generations.aggregrate([
{ $project: { 'firstGenerations': '$parent.firstGenerations'} },
{ $unwind: '$firstGenerations'},
{ $unwind: '$firstGenerations.secondGenerations'}
{
$group: {
_id: '$_id',
count: { 'sum': '$secondGenerations.value' },
firstGenerationName: { $first: '$firstGenerations.name'}
}
}
])
I am able to unwind properly but not able to club group functionality by taking one value from parent array and count from second array.
Any help will be appreciated
Basically you need to run $group twice to get inner counts first:
db.collection.aggregate([
{
$unwind: "$parent"
},
{
$unwind: "$parent.firstGenerations"
},
{
$unwind: "$parent.firstGenerations.secondGenerations"
},
{
$group: {
_id: {
fgName: "$parent.firstGenerations.name",
sgValue: "$parent.firstGenerations.secondGenerations.value"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.fgName",
values: {
$push: {
categoryName: { $concat: [ "$_id.sgValue", " (", { $toString: "$count" }, ")" ] },
count: "$count",
firstGenerationName: "$_id.fgName"
}
}
}
},
{
$project: {
_id: 0,
firstGenerationName: "$_id",
values: 1,
totalCount: { $sum: "$values.count" }
}
}
])
Mongo Playground

Mongo DB complex query

I need some help with MongoDB.
I Have Ex Schema and Database as below:
`
Tb:
User:
{
name: { type: String }
image: { type: String }
city: { type: String }
address: [{
index: { type: Number }
district_id: { type: Schema.ObjectId, ref: 'District' }
}]
}
Tb2:
District:{
name: { type: String }
code: { type: String }}
Example Database:
User: [{
_id: '1234'
name: 'Jacky',
image: '',
city: 'Da Nang',
address: [
{
index: 1,
district_id: '12345'
},
{
index: 2,
district_id: '123456'
},
{
index: 3,
district_id: '1234567'
}
]
}]
District: [
{
_id: '12345',
name: 'Hai Chau',
code: 12
},
{
_id: '123455',
name: 'Lien CHieu',
code: 13
},
{
_id: '1234567',
name: 'Cam Le',
code: 14
},
{
_id: '12345678',
name: 'Son Tra',
code: 15
}
]
How can i select User by both of two options (disctrict.name && index) like as district.name = 'Lien Chieu' && address.index > 1.
You can try this below query :
db.District.aggregate([
/** filter required doc from district Coll */
{ $match: { name: "Lien CHieu" } },
{
$lookup: {
from: "User",
let: { id: "$_id" },
pipeline: [
/** Basic filter to get matched doc from User Coll */
{ $match: { $expr: { $in: ["$$id", "$address.district_id"] } } },
/** check for criteria */
{
$addFields: {
mappingField: {
$map: {
input: "$address", as: "each", in: {
$and: [{ $gt: ["$$each.index", 1] }, { $eq: ["$$id", "$$each.district_id"] }]
}
}
}
}
},
/** filter required doc from above which matches our criteria & remove added field */
{ $match: { mappingField: true } }, { $project: { mappingField: 0 } }
],
as: "userdata"
}
}
])
Test : MongoDB-Playground

Grouping and counting across documents?

I have a collection with documents similar to the following format:
{
departure:{name: "abe"},
arrival:{name: "tom"}
},
{
departure:{name: "bob"},
arrival:{name: "abe"}
}
And to get output like so:
{
name: "abe",
departureCount: 1,
arrivalCount: 1
},
{
name: "bob",
departureCount: 1,
arrivalCount: 0
},
{
name: "tom",
departureCount: 0,
arrivalCount: 1
}
I'm able to get the counts individually by doing a query for the specific data like so:
db.sched.aggregate([
{
"$group":{
_id: "$departure.name",
departureCount: {$sum: 1}
}
}
])
But I haven't figured out how to merge the arrival and departure name into one document along with counts for both. Any suggestions on how to accomplish this?
You should use a $map to split your doc into 2, then $unwind and $group..
[
{
$project: {
dep: '$departure.name',
arr: '$arrival.name'
}
},
{
$project: {
f: {
$map: {
input: {
$literal: ['dep', 'arr']
},
as: 'el',
in : {
type: '$$el',
name: {
$cond: [{
$eq: ['$$el', 'dep']
}, '$dep', '$arr']
}
}
}
}
}
},
{
$unwind: '$f'
}, {
$group: {
_id: {
'name': '$f.name'
},
departureCount: {
$sum: {
$cond: [{
$eq: ['$f.type', 'dep']
}, 1, 0]
}
},
arrivalCount: {
$sum: {
$cond: [{
$eq: ['$f.type', 'arr']
}, 1, 0]
}
}
}
}, {
$project: {
_id: 0,
name: '$_id.name',
departureCount: 1,
arrivalCount: 1
}
}
]