I am using some assignment destructuring in my MongoDB/Node backend in order to handle some post-processing. I'm just trying to understand how this destructuring works, and if, in the case of an array of multiple elements and nested arrays, if I can input the element I want to target.
Take for instance this code:
services: [
,
{
history: [...preSaveData]
}
]
} = preSaveDocObj;
My assumption is that the "," in "services" for the above code will default to looking at the first element in the array. Correct?
Now, if I have a document structure that looks like this (see below), and I know I want to target the "services" element where "service" is equal to "typeTwo", how would I do that?:
{
_id: 4d39fe8b23dac43194a7f571,
name: {
first: "Jane",
last: "Smith"
}
services: [
{
service: "typeOne",
history: [
{ _id: 121,
completed: true,
title: "rookie"
},
{ _id: 122,
completed: false,
title: "novice"
}
]
},
{
service: "typeTwo",
history: [
{ _id: 135,
completed: true,
title: "rookie"
},
{ _id: 136,
completed: false,
title: "novice"
}
]
}
]
}
How can I edit this code (see below) to specifically target the "services" array where "service" is equal to "typeTwo"?
services: [
,
{
history: [...preSaveData]
}
]
} = preSaveDocObj;
Don't overdestructure, just find:
const { history: [...preSavedData] } = doc.services.find(it => it.serice === "typeTwo");
Related
I have a collection of documents, which I need to first narrow down by set criteria, then sort alphabetically by string value inside those documents — let's say that's a "search result". I then need to find document that matches a given _id and then pick a document next to it (before or after) from the above "search result".
Background:
I use mongoose to query my database via Node.js.
I have a set of "special sections" in my blog that are comprised of all the articles that must have three particular conditions associated within the keys in the document. I can get the list of articles belonging to said section like so:
const specialSectionListQuery = Article.find({
tag: { $ne: "excluded" },
[`collections.cameras`]: { $exists: true },
status: "published",
})
To finish creating the "special section," I must sort the documents alphabetically via their title attribute:
.sort({ [`collections.cameras.as.title`]: "asc" })
Now I want to add a link to "next article within the same special section" at the bottom of such articles. I know _id and any other value needed from the current article. The above query gives me an ordered list of documents within the section so I can easily find it within that list specialSectionListQuery.findOne({ _id: "xxx" }).exec().
However, I need to find the next article within the above list. How do I do that?
My attempts thus far:
I tried to create article list via aggregation, which led me nowhere (I simply made my app do exactly the same thing — make a list for a "special sectin"):
Article.aggregate([
{
$match: {
tag: { $ne: "excluded" },
[`collections.cameras`]: { $exists: true },
status: "published",
},
},
{
$sort: {
[`collections.cameras.as.title`]: 1,
},
}
]).exec()
But I can't for the life of me figure out how to iterate to the next document in the list properly.
I have thought of saving the list in Node.js memory (variable) and then finding what I need via JavaScript but that can't be scalable.
I have considered creating a new collection and saving the above list there but that would require me to either 1) do it every time a document is altered/added/deleted via Node.js — which is a lot of code and it may break if I interact with database another way 2) rebuild the colleciton every time I run the query, but that feels like it'll lack in performance.
Please help and thank you!
P.S.:
Example collection which should cover most of the cases I'm looking to solve for:
[
{
_id: 1,
name: "Canon",
collections: { cameras: { as: { title: "Half-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 2,
name: "Pentax",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 3,
name: "Kodak",
collections: { film: { as: { title: "35mm Film" } } },
tag: "included",
status: "published"
},
{
_id: 4,
name: "Ricoh",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 5,
name: "Minolta",
collections: { cameras: { as: { title: "Half-Frame Review" } } },
tag: "excluded",
status: "published"
},
{
_id: 4,
name: "FED",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "draft"
}
]
One thing you can try is to extend your $sort by adding _id so that it always returns documents in deterministic order:
{
$sort: {
"collections.cameras.as.title": 1,
_id: 1
}
},
{
$limit: 1
}
Once your first query returns the document with _id: 2 and collections.cameras.as.title: Full-Frame, you can use below query to get subsequent document:
{
$match: {
$and: [
{
tag: { $ne: "excluded" },
"collections.cameras": { $exists: true },
status: "published",
},
{
$or: [
{
$and: [
{ "collections.cameras.as.title": { $eq: "Full-Frame" } },
{ "_id": { $gt: 2 } }
]
},
{ "collections.cameras.as.title": { $gt: "Full-Frame" } }
]
}
]
}
},
{
$sort: {
"collections.cameras.as.title": 1,
_id: 1
}
},
{
$limit: 1
}
In this case due to deterministic $sort you can exclude previously found document by adding additional filtering criteria and the order should be preserved.
Mongo Playground
I have an array of objects call "extra" with different properties: some objects have "plus" and some haven't.
I want to create inside this "extra" array, 2 different arrays one called "cheap" with all the object that don't have the "plus" property and one called "exp" with only the objects with the "plus" property.
I think I can use the $reduce method in mongodb aggregate with $concatArrays and check with $cond if the property plus exists or not.
Something like that:
Data example:
{
extra: [
{
description: "laces",
type: "exterior",
plus: '200'
},
{
description: "sole",
type: "interior"
},
{
description: "logo",
type: "exterior"
},
{
description: "stud",
type: "exterior",
plus: '450'
}
],
}
{
$project: {
extra: {
$reduce: {
input: ['$extra'],
initialValue: {cheap: [], exp: []},
$cond: {
if: {$eq: ['$$this.plus', null]},
then: {
in: {
cheap: {
$concatArrays: ['$$value.cheap', '$$this'],
},
},
},
else: {
in: {
exp: {
$concatArrays: ['$$value.exp', '$$this'],
},
},
},
},
},
},
},
}
It doesn't work...I tried many ways or writing the $cond part without luck.
I can't figure it out.
Thank you all.
K.
Apart from some minor syntax issues you've had another problem is your understand of the $ne operator.
In this case you expect a missing value to be equal to null, this is not how Mongo works. so for a document:
{ name: "my name" }
The aggregation query:
{ $cond: { $eq: ["$missingField", null] } }
Will not give true as you expect as missing is not equal to null. I took the liberty to fix the syntax issues you've had, this working pipeline is the way to go:
db.collection.aggregate([
{
$project: {
extra: {
$reduce: {
input: "$extra",
initialValue: {
cheap: [],
exp: []
},
in: {
cheap: {
"$concatArrays": [
"$$value.cheap",
{
$cond: [
"$$this.plus",
[],
[
"$$this"
],
]
}
]
},
exp: {
"$concatArrays": [
"$$value.exp",
{
$cond: [
"$$this.plus",
[
"$$this"
],
[]
]
}
]
}
}
},
},
},
}
])
Mongo Playground
One thing to note is that $cond evaluates the plus field, meaning if the field does exist with a null value or a 0 value then it will consider this document matched for the cheap array. This is something to consider and change in case these are possible.
Consider the array which I get as response from API call. what if the position of any one of the attributes gets changed and I want to get the attribute salary/id/idProperty i.e., If I am not sure about attribute position how can get its value without hard coding the array with index?
var arr = [
{
id: "alex",
idProperty: {
args: [
{
name: "alex_name"
},
{
salary: "16L"
}
]
}
},
{
id: "john",
idProperty: {
args: [
{
name: "john_name"
},
{
salary: "14L"
}
]
}
}
];
My problem is reading properties of nested object, which is inside other nested object.
GraphQL
type Mapping {
id: ID!
partnerSegmentId: ID!
ctSegmentId: CtSegment!
}
type PartnerSegment {
id: ID!
name: String!
platformId: Int!
partner: Partner!
}
type Partner {
id: ID!
name: String!
}
Once I try to query it like:
{
allMappings {
partnerSegmentId {
id
name
partner {
id
}
}
}
}
I recieve:
{
"data": {
"allMappings": [
null
]
},
"errors": [
{
"message": "Cannot return null for non-nullable field Partner.name.",
"locations": [
{
"line": 8,
"column": 9
}
],
"path": [
"allMappings",
0,
"partnerSegmentId",
"partner",
"name"
]
}
]
}
Mapping schema
const mappingSchema = new mongoose.Schema(
{
partnerSegmentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'PartnerSegment',
required: [true, 'Mapping must have partner segment id.']
},
ctSegmentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'CtSegment',
required: [true, 'Mapping must have CT segment id.']
}
},
{ timestamps: true }
);
I tried to read separately Partner, PartnerSegment and Mapping models. All works fine. Any idea where i should search source of the problem? I've checked mongodb docs and ids looks okay. I suppose it's fault of my model.
If you would like to take a closer look it's project repo.
SOLUTION:
Garbage Id in the return value was caused by not working populate in the nested entity. The way I managed to solve the problem:
const allMappings = () =>
Mapping.find({})
.populate('user')
.populate('ctSegment')
.populate({
path: 'partnerSegment',
populate: {
path: 'partner'
}
})
.exec();
Sample data:
{
_id: '1234',
games: [{
name: 'World of Warcraft',
lastLogin: ISODate(),
subscriptions: ['56', '78']
}, {
name: 'Starcraft II',
lastLogin: ISODate()
}]
}
Essentially, I want to find everyone that does not have a "subscriptions" field for a given game. I can't quite figure out a good way to do it.
Players.update({
'games.name': 'World of Warcraft',
'games.$.subscriptions': { $exists: false }
}, {
$set: { 'games.$.subscriptions': GLOBAL_SUB }
});
How do you query an array of elements for an attribute and the existence of a field?
Use $elemMatch when you want to match multiple terms on the same array element:
Players.update({
games: { $elemMatch: {
name: 'World of Warcraft',
subscriptions: { $exists: false }
}}
}, {
$set: { 'games.$.subscriptions': GLOBAL_SUB }
});