MongoDB $and query - javascript

I want a query in MongoDB that does the following:
A document returned profession.organization property can be the string 'bank'.
A document returned profession.city property can be the string 'NY'.
However, when the document has 'bank' for 'profession.organization' AND 'NY' for 'profession.city' the document must be excluded.
To sum up a document can have city 'NY' or organization 'bank' but must be excluded if it has these properties at the same time.
What I have tried so far.
targetObj = await db.db(MDBC.db).collection(MDBC.pC)
.aggregate([{
$match: {
$and: [
{ 'profession.organization': { $ne: 'bank' } },
{ 'profession.city': { $ne: 'NY' } }
]
}
}, { $sample: { size: 1 } }]).next();

Try this one:
targetObj = await db.db(MDBC.db).collection(MDBC.pC)
.aggregate([
{
$match: {
$and: [
{
$or: [
{ "profession.organization": "bank" },
{ "profession.city": "NY" }
]
},
{
$or: [
{
"profession.organization": "bank",
"profession.city": { $ne: "NY" }
},
{
"profession.organization": { $ne: "bank" },
"profession.city": "NY"
}
]
}
]
}
},
{ $sample: {size: 1} } ]).next();
MongoPlayground

I think the query which is used in the accepted answer could be much shorter.
db.collection.aggregate([
{
$match: {
$or: [
{
"profession.organization": "bank",
"profession.city": {
$ne: "NY"
}
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": "NY"
}
]
}
},
{
$sample: {
size: 1
}
}
])
and one more thing I want to say is if you also want to include documents that don't have either of those two properties then you should use this:
db.collection.aggregate([
{
$match: {
$or: [
{
"profession.organization": "bank",
"profession.city": {
$ne: "NY"
}
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": "NY"
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": {
$ne: "NY"
}
}
]
}
},
{
$sample: {
size: 1
}
}
])
this will also include documents like-
{
"profession": {
"organization": "someBank",
"city": "notNA"
}
}

Unfortunately, at the time of writing, there is no matching operator in MongoDB to help out with this. But you can achieve a similar result by combining the $or and $ne operators. Try something like this for your query:
const query = {
$or: [
{
// Include all non-banks outside of NY.
'profession.city': { $ne: 'NY' },
'profession.organization': { $ne: 'bank' }
},
{
// Include all non-banks in NY.
'profession.city': 'NY',
'profession.organization': { $ne: 'bank' }
},
{
// Include all banks outside of NY.
'profession.city': { $ne: 'NY' },
'profession.organization': 'bank'
}
]
};

Related

How to return nested document with Mongoose?

If I have this collection
[
{
"_id": "637cbf94b4741277c3b53c6c",
"text": "outter",
"username": "test1",
"address": [
{
"text": "inner",
"username": "test2",
"_id": "637cbf94b4741277c3b53c6e"
}
],
"__v": 0
}
]
and would like to search for the nested document by _id and return all of the nested document. If I do
db.collection.find({
_id: "637cbf94b4741277c3b53c6c"
},
{
address: {
$eq: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
I get
query failed: (Location16020) Expression $eq takes exactly 2 arguments. 1 were passed in.
Playground link
Question
Can anyone see what I am doing wrong?
use $elemMatch and also you have extra unneeded brackets. try
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
Edit: if you only want to return the address add projection like this
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
},
{
_id: 0,
address: 1
})
One option is to use find:
db.collection.find({},
{
_id: 0,
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
See how it works on the playground example
The other option is to use aggregation pipeline:
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"637cbf94b4741277c3b53c62",
"$address._id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$address",
cond: {
$eq: [
"$$this._id",
"637cbf94b4741277c3b53c6e"
]
}
}
}
}
}
}
])
See how it works on the playground example

MongDB Aggregate two values with same name

i have the following aggregate function in my code to count how many times a value is found in the db:
let data: any = await this.dataModel.aggregate(
[
{
$match: {
field: new ObjectID(fieldID),
},
},
{
$group: {
_id: "$value",
total_for_value: { $sum: 1 },
},
},
]
);
This works correctly, however my data setup is a bit different. I have two types of value fields. Some like this:
{
"_id" : ObjectId("123"),
"value" : "MALE"
}
and some like this:
{
"_id" : ObjectId("456"),
"value" : {
"value" : "MALE",
}
}
Is there a way to group the ones where the _id and the _id.value are the same? At the moment it counts them separately.
db.collection.aggregate([
{
"$addFields": {
"key2": {
"$cond": {
"if": {
$and: [
{
"$eq": [
{
"$type": "$key"
},
"object"
]
}
]
},
"then": "$key.value",
"else": "$key"
}
}
}
},
{
"$group": {
"_id": "$key2",
"data": {
$push: "$$ROOT"
}
}
}
])
This would do the job if _id.value is an object.
Playground

Mongo get value from 2 collections same time

I'm trying to get data from 2 collections, and return one array with merge data of both collection.
The best solution for me was :
const bothValues = await ValueA.aggregate([
{ $unionWith: { coll: 'valueB' } },
{ $sort: { rank: -1, _id: -1 } },
{
$match: {
isAvailable: true,
},
},
{ $skip: skip },
{ $limit: 30 },
]);
which work perfectly. But.. $unionWith was not implemented my MongoDB version (4.0.X) so I can't use it.
const bothValues = await ValueA.aggregate(
[
{ $limit: 1 },
{
$lookup: {
from: 'valueB',
pipeline: [{ $limit: 15 }],
as: 'valueB',
},
},
{
$lookup: {
from: 'ValueA',
pipeline: [{ $limit: 15 }, { $sort: { rank: -1, _id: -1 } }],
as: 'ValueA',
},
},
{
$project:
{
Union: { $concatArrays: ['$valueB', '$ValueA'] },
},
},
{ $unwind: '$Union' },
{ $replaceRoot: { newRoot: '$Union' } },
],
);
but now, I got 2 problems :
I can't use a $skip, which is important, where use it ?
How to use $match ?
Thanks
Query
your query made with some changes to work like the first query
match in both pipelines, sort in both, (limit limitN+skipN)
(this way we make sure that we always have enough documents even if all are taken from valueA or valueB)
Take sorted 70 from each, so in all ways we will have the 70 needed in the final sort/skip/limit after the union.
concat,unwind,replace-root like in your query
sort again (to sort the union now), skip, limit
no matter we always have enough documents to skip
this example query is made for skip=40 and limit=30 so in the first 2 pipelines we limit=70
db.ValueA.aggregate([
{
"$limit": 1
},
{
"$lookup": {
"from": "valueB",
"pipeline": [
{
"$match": {
"isAvailable": true
}
},
{
"$sort": {
"rank": -1,
"_id": -1
}
},
{
"$limit": 70
}
],
"as": "valueB"
}
},
{
"$lookup": {
"from": "valueA",
"pipeline": [
{
"$match": {
"isAvailable": true
}
},
{
"$sort": {
"rank": -1,
"_id": -1
}
},
{
"$limit": 70
}
],
"as": "valueA"
}
},
{
"$project": {
"union": {
"$concatArrays": [
"$valueA",
"$valueB"
]
}
}
},
{
"$unwind": {
"path": "$union"
}
},
{
"$replaceRoot": {
"newRoot": "$union"
}
},
{
"$sort": {
"rank": -1,
"_id": -1
}
},
{
"$skip": 40
},
{
"$limit": 30
}
])

MongoDB Aggregate sum nested number array

I have the following document structure
{
"_id": "60b7b7c784bd6c2a1ca57f29",
"user": "607c58578bac8c21acfeeae1",
"exercises": [
{
"executed_reps": [8,7],
"_id": "60b7b7c784bd6c2a1ca57f2a",
"exercise_name": "Push up"
},
{
"executed_reps": [5,5],
"_id": "60b7b7c784bd6c2a1ca57f2b",
"exercise_name": "Pull up"
}
],
}
In aggregation, I am trying to sum all the executed_reps so the end value in this example should be 25 (8+7+5+5).
Here is the code I have so far:
const exerciseStats = await UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$group: {
_id: null,
totalReps: {
$sum: {
$reduce: {
input: '$exercises.executed_reps',
initialValue: '',
in: { $add: '$$this' },
},
},
},
},
},
]);
This gives a result of 5 for totalReps. What am I doing wrong?
Well 10 minutes later I found the solution myself:
UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$project: {
total: { $sum: '$exercises.executed_reps' },
},
},
{
$group: {
_id: null,
totalExercises: { $sum: '$total' },
},
},])
Had to use $project first. :)
You can do it like this:
const exerciseStats = await UserWorkoutaggregate([
{
"$addFields": {
"total": {
"$sum": {
"$map": {
"input": "$exercises",
"as": "exercise",
"in": {
"$sum": "$$exercise.executed_reps"
}
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/5_fsPgSh8EP

Adding property to mulitple documents in mongoDB

I have this data structure:
{
"_id": "5ebd08794bcc8d2fd893f4a7",
"username": "johan#gmail.com",
"password": "123",
"decks": [{
"cards": [{
"_id": "5ebd08794bcc8d2fd893f4a9",
"planeetnaam": "Venus",
"kleur": "Grijs"
},
{
"_id": "5ebd08794bcc8d2fd893f4aa",
"planeetnaam": "Neptunus",
"kleur": "Paars"
}
],
"_id": "5ebd08794bcc8d2fd893f4a8",
"name": "Planeten"
},
{
"cards": [{
"_id": "5ebd08794bcc8d2fd893f4ac",
"diernaam": "Hond",
"poten": "4"
},
{
"_id": "5ebd08794bcc8d2fd893f4ad",
"diernaam": "Kangoeroe",
"poten": "2"
}
],
"_id": "5ebd08794bcc8d2fd893f4ab",
"name": "Dieren"
}
],
"__v": 0
}
Now i want to add a new property to all the cards in deck with deckname: "Planeten". How do i do this with a mongoose query?
The cards array of deck "Planeten" should look like this after the query
"cards": [{
"_id": "5ebd08794bcc8d2fd893f4a9",
"planeetnaam": "Venus",
"kleur": "Grijs",
"newProp": null
},
{
"_id": "5ebd08794bcc8d2fd893f4aa",
"planeetnaam": "Neptunus",
"kleur": "Paars",
"newProp": null
}
],
EDIT:
This works in Robo3T:
db.getCollection('users').findOneAndUpdate(
{ '_id': ObjectId("5eba9ee0abfaf237f81fb104") },
{ $set: { 'decks.$[deck].cards.$[].newProp': null } },
{ arrayFilters: [{ 'deck._id': ObjectId("5eba9ee0abfaf237f81fb108") } ] }
)
But the server query doesnt edit any data:
User.findOneAndUpdate(
{ '_id': req.session.userid },
{ $set: { 'decks.$[deck].cards.$[].newProp': null } },
{ arrayFilters: [{ 'deck._id': req.params.deckid } ] }, function(err, user){
res.send('test');
})
Thanks in advance
you can use array update operators
the query may look something like that
db.collection.updateOne(
{ _id: <ObjectId> }, // the filter part
{ $set: { 'decks.$[deck].cards.$[].newProp': null } },
{ arrayFilters: [{ 'deck.name': 'Planeten' }] }
)
$[deck] refers to each element in the decks array
$[] is used to update all the elements in the cards array
your function may look something like that
User.updateOne(
{ '_id': req.session.userid },
{ $set: { 'decks.$[deck].cards.$[].newProp': null } },
{ arrayFilters: [{ 'deck.name': 'Planeten' }] })
.then(function (user) {
if (!user) {
res.status(404).send('Er ging helaas iets fout')
} else {
res.status(201).send("Card is toegevoegd");
}
})
hope it helps

Categories