Mongoose $elemMatch How to query both $in and $nin - javascript

I'm using Mongoose in order to check if an element exist in my DB.
For that I'm using something like-
object.aggregate([
{$match: {
"data": {
$elemMatch: {
$and:[
{name: {$in: ["A", "B"]}},
{name: {$nin: ["X"]}}
]
}
}
}
}
])
The problem is text is array. So when I'm getting ["A" , "X"] I wish the result to be empty.
Do you have any ideas?
----------- Edit ------------
Object looks something like:
example1 = { // shouldn't find - not in $in
_id: "123",
data: [
{id:"1",name:"E"},
{id:"2",name:"R"},
{id:"3",name:"T"}
]
}
example2 = { // shouldn't find - in both $in $nin
_id: "456",
data: [
{id:"4",name:"A"},
{id:"5",name:"X"},
{id:"6",name:"Z"}
]
}
example3 = { // should find - only in $in
_id: "789",
data: [
{id:"7",name:"A"},
{id:"8",name:"L"},
{id:"9",name:"Z"}
]
}
----------- Edit 2 ------------
Thanks #Anthony For helping me find a better example to the issue.
I think this issue occurs within arrays of array, example:
Data Set
[
{
_id: "456",
data: [
{
field: [
{
id: "1",
name: "A"
},
{
id: "2",
name: "B"
},
{
id: "3",
name: "Z"
}
]
},
{
field: [
{
id: "4",
name: "A"
},
{
id: "5",
name: "X"
},
{
id: "6",
name: "Z"
}
]
}
]
}
]
My solution with element match gets 2 docs:
(enter link description here)
db.collection.aggregate([
{
$match: {
data: {
$elemMatch: {
"$and": [
{
"field.name": {
"$in": [
"A",
"B"
]
}
},
{
"field.name": {
"$nin": [
"X"
]
}
}
]
}
}
}
}
])
The other solution suggested returns 0 docs:
(enter link description here)
db.collection.find({
"$and": [
{
"data.field.name": {
"$in": [
"A",
"B"
]
}
},
{
"data.field.name": {
"$nin": [
"X"
]
}
}
]
})

You can try this with simple find query
db.collection.find({
"data": {
"$elemMatch": {
"$and": [
{ "field.name": { "$in": [ "A", "B" ] } },
{ "field.name": { "$nin": [ "X" ] } }
]
}
}
}, {
"data": {
"$elemMatch": {
"$and": [
{ "field.name": { "$in": [ "A", "B" ] } },
{ "field.name": { "$nin": [ "X" ] } }
]
}
}
})
Try it here

Related

MongoDB would like to enter and update a key in an object for the first time

This is the end result I would like to get.
{
'_id': '2022-08-06',
'users': [
{
'id': '345456',
point: 1,
},
],
};
I'm trying these, but it doesn't work. Thank you very much for your reply.
client.db(dbProject).collection('test').updateOne({ '_id': data.id, users: { $elemMatch: { id: data.id2 } } })
client.db(dbProject).collection('test').findOneAndUpdate(
{
'_id': data.id,
users: { $elemMatch: { id: data.id2 } },
},
{ $addToSet: { 'users': { id: data.id } }, $inc: { 'users.point': decimal(String(data.point)) } },
{
upsert: true,
})
You can achieve this by using the aggregation pipeline update syntax, like so:
client.db(dbProject).collection("test").updateOne(
{
"_id": data.id
},
[
{
$set: {
users: {
$ifNull: [
"$users",
[]
]
}
}
},
{
"$set": {
"users": {
$concatArrays: [
{
$map: {
input: "$users",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{
$eq: [
"$$this.id",
data.id2
]
},
{
point: {
$sum: [
"$$this.point",
1
]
}
},
{}
]
}
]
}
}
},
{
$cond: [
{
$in: [
data.id2,
"$users.id"
]
},
[],
[
{
id: data.id2,
point: 1
}
]
]
}
]
}
}
},
],
{
"upsert": true
})
Mongo Playground

Add field to each object of an array based on other field

I have the following Array of data:
{
_id: 5f5726ef7d475a61a95c5e0c,
attributes: [
{
values: [
{ name: '1' }
],
},
{
values: [
{ name: '2' }
]
}
],
attr1: [
{ name: "Study Code" },
{ name: "Patient Study" }
]
}
What I need is to add the correspondent value to each on of attr1 objects based on index. So the result would be:
{
_id: 5f5726ef7d475a61a95c5e0c,
attributes: [
{
values: [
{ name: '1' }
],
},
{
values: [
{ name: '2' }
]
},
],
attr1: [
{
name: "Study Code",
values: [{ name: "1" }]
},
{
name: "Patient Study",
values: [{ name: "2" }]
}
],
}
I wonder if that possible using aggregation $addFields in MongoDB
Query
query works if arrays same size
ziparray to make [[member1_1 member2_1], ....]
map to merge member1_1,member2_1 to a document
Playmongo
aggregate(
[{"$set": {"attr1": {"$zip": {"inputs": ["$attributes", "$attr1"]}}}},
{"$set":
{"attr1":
{"$map":
{"input": "$attr1",
"in":
{"$mergeObjects":
[{"$arrayElemAt": ["$$this", 1]},
{"$arrayElemAt": ["$$this", 0]}]}}}}}])
You can use $zip
db.collection.aggregate([
{
"$project": {
attributes: {
"$zip": {
"inputs": [
"$attributes",
"$attr1"
]
}
}
}
}
])
Here is the Mongo playground for your reference.

Refactor Javascript Object Traversal

The code that I have does result in the desired output. However, in my opinion, using these nested for loops is a little ugly and I am trying to refactor my work.
My question is: do you have any suggestions on refactoring the code so that I can avoid the need of using these nested for loops?
I want to loop over a nested object and end up with a result of all unique keys, and an array of all values for that unique key.
{"ABC":["1","100","6"],"DEF":["10","2","5"],"GHI":["14","9"],"HGI":["4"]}
const data = {
something: [
{
innerSomething: {
list: [
{
title: "ABC",
amount: "1"
},
{
title: "DEF",
amount: "10"
},
{
title: "GHI",
amount: "14"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "100"
},
{
title: "DEF",
amount: "2"
},
{
title: "GHI",
amount: "9"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "6"
},
{
title: "DEF",
amount: "5"
},
{
title: "HGI",
amount: "4"
}
],
}
}
]
};
const results = {};
data.something.forEach((item) => {
item.innerSomething.list.forEach((list) => {
if (results[list.title]) {
// exists already, just push the amount
results[list.title].push(list.amount)
} else {
// Is unique so far, add it to the object
results[list.title] = [list.amount];
}
})
});
console.log(`results: ${JSON.stringify(results)}`);
// These results are the correct and desired output
// {"ABC":["1","100","6"],"DEF":["10","2","5"],"GHI":["14","9"],"HGI":["4"]}
Your implementation is ok, tbh.
The only thing I can suggest differently is to make use of the neat destructuring patterns and spread syntax, considering that the structure of your input object is very well known:
data.something.forEach(({ innerSomething: { list } }) =>
list.forEach(({ title, amount }) =>
results[title] = [...results[title] || [], amount]))
For what it's worth, here is how I'd write it.
const data = {
something: [
{
innerSomething: {
list: [
{
title: "ABC",
amount: "1"
},
{
title: "DEF",
amount: "10"
},
{
title: "GHI",
amount: "14"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "100"
},
{
title: "DEF",
amount: "2"
},
{
title: "GHI",
amount: "9"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "6"
},
{
title: "DEF",
amount: "5"
},
{
title: "GHI",
amount: "4"
}
],
}
}
]
};
const results = {};
for (let something of data.something) {
for (let item of something.innerSomething.list) {
const { title, amount } = item;
results[title] = results[title] || [];
results[title].push(amount);
}
}
console.log(`results: ${JSON.stringify(results)}`);

Restructuring Javascript Arrays

EDIT: Thanks for the replies!
I am kind of new to working with Javascript (sorry for probably the bad code). I've mainly only worked with HTML and CSS. I have been trying to experiment with Javascript, but I've been stuck on this forever. The outcome is not what I want it to be.
My code:
I want to make the array easier to use, by putting the values under the same category, but my code's output is not exactly what I want it to be like.
var data = {
idName: "idName",
valueRanges: [{
range: "range",
majorDimension: "ROWS",
values: [
[
"ID",
"Category",
"Name"
],
[
"1",
"Category1",
"Name1"
],
[
"2",
"Category1",
"Name2"
],
[
"3",
"Category2",
"Name3"
],
[
"4",
"Category1",
"Name4"
]
]
}]
}
var rows = [];
let batchRowValues = data.valueRanges[0].values
for (let i = 1; i < batchRowValues.length; i++) {
let rowObject = {};
for (let j = 0; j < batchRowValues[i].length; j++) {
rowObject[batchRowValues[0][j]] = batchRowValues[i][j];
}
rows.push(rowObject);
}
var newArray = rows.reduce(function(acc, curr) {
var findIfNameExist = acc.findIndex(function(item) {
return item.Category === curr.Category;
})
if (findIfNameExist === -1) {
let obj = {
'Category': curr.Category,
'value': [curr]
}
acc.push(obj)
} else {
acc[findIfNameExist].value.push(curr)
}
return acc;
}, []);
console.log('------------')
console.log('input: ' + JSON.stringify(data, null, 2))
console.log('------------')
console.log('output: ' + JSON.stringify(newArray, null, 2))
My code's output:
[
{
Category: "Category1",
value: [
{
Category: "Category1",
ID: "1",
Name: "Name1"
},
{
Category: "Category1",
ID: "2",
Name: "Name2"
},
{
Category: "Category1",
ID: "4",
Name: "Name4"
}
]
},
{
Category: "Category2",
value: [
{
Category: "Category2",
ID: "3",
Name: "Name3"
}
]
}
]
How I want it to look:
[
{
Category: "Category1",
values: [
{
ID: "1",
Name: "Name1"
},
{
ID: "2",
Name: "Name2"
},
{
ID: "4",
Name: "Name4"
}
]
},
{
Category: "Category2",
values: [
{
ID: "3",
Name: "Name3"
},
]
},
]
I want to learn! I appreciate any help.
You can use reduce.
Here idea is
Create a object and use category as key
If a category is already found than push the desired object in it's value property. if not than we create a new one with suitable data.
I am using object here instead of array directly is because i can directly access element using key where as in array i need to loop through each time and check existence of value
var data = {idName: "idName",valueRanges: [{range: "range",majorDimension: "ROWS",values: [["ID","Category","Name"],["1","Category1","Name1"],["2","Category1","Name2"],["3","Category2","Name3"],["4","Category1","Name4"]]}]}
var rows = [];
let batchRowValues = data.valueRanges[0].values.slice(1,)
let op = batchRowValues.reduce((op,[ID,Category,Name]) => {
if( op[Category] ){
op[Category].value.push({ID,Name})
} else {
op[Category] = {
Category,
value: [{ID,Name}]
}
}
return op
},{})
console.log(Object.values(op))
Try (t={}, result in r)
data.valueRanges[0].values.slice(1).map( ([i,c,n])=>
(t[c]=t[c]||{Category:c,values:[]}, t[c].values.push({ID:i, Name:n})) );
let r= Object.values(t);
var data =
{
idName: "idName",
valueRanges: [
{
range: "range",
majorDimension: "ROWS",
values: [
[
"ID",
"Category",
"Name"
],
[
"1",
"Category1",
"Name1"
],
[
"2",
"Category1",
"Name2"
],
[
"3",
"Category2",
"Name3"
],
[
"4",
"Category1",
"Name4"
]
]
}
]
}
let t={};
data.valueRanges[0].values.slice(1).map( ([i,c,n])=>
(t[c]=t[c]||{Category:c,values:[]}, t[c].values.push({ID:i, Name:n})) );
let r= Object.values(t);
console.log(r);

MongoDB Aggregation $size on Nested Field

I'm trying to perform a tricky aggregation to return the size of a nested array within a document in the collection.
Here is how to re-create my sample data:
db.test.insert({
projects: [
{
_id: 1,
comments: [
'a',
'b',
'c'
]
},
{
_id: 2,
comments: [
'a',
'b'
]
},
{
_id: 3,
comments: []
}
]
})
The aggregation I would perform goes here:
db.test.aggregate([
// enter aggregation here
])
Here is the desired output:
[{
projects: [
{
_id: 1,
comment_count: 3
},
{
_id: 2,
comment_count: 2
},
{
_id: 3,
comment_count: 0
}
]
}]
I'm struggling with how to write this. If I try the following:
"projects.comment_count": {"$size": }
The result returns the size of the resulting array:
[{
projects: [
{
_id: 1,
comment_count: 3
},
{
_id: 2,
comment_count: 3
},
{
_id: 3,
comment_count: 3
}
]
}]
If I try to use the $map method like this:
"projects.comment_count": {
"$map": {
"input": "$projects",
"as": "project",
"in": {
"$size": "$$project.comments"
}
}
}
It will return an array that looks like this for each object in the array:
[{
projects: [
{
_id: 1,
comment_count: [3, 2, 0]
},
{
_id: 2,
comment_count: [3, 2, 0]
},
{
_id: 3,
comment_count: [3, 2, 0]
}
]
}]
Thanks in advance!
Here is an idea using $unwind, $group and then $push with $size. Finally $project to get rid of that _id:
db.collection.aggregate([
{
"$unwind": "$projects"
},
{
$group: {
_id: null,
"projects": {
$push: {
_id: "$projects._id",
comment_count: {
$size: "$projects.comments"
}
}
}
}
},
{
"$project": {
"_id": 0
}
}
])
You can see the result here
You need to specify each field inside the in argument of $map aggregation and finally use $size with the comments array.
Something like this
db.collection.aggregate([
{ "$project": {
"projects": {
"$map": {
"input": "$projects",
"in": {
"_id": "$$this._id",
"comment_count": {
"$size": "$$this.comments"
}
}
}
}
}}
])
Output
[
{
"projects": [
{
"_id": 1,
"comment_count": 3
},
{
"_id": 2,
"comment_count": 2
},
{
"_id": 3,
"comment_count": 0
}
]
}
]

Categories

Resources