'InvalidPipelineOperator' with $lookup when adding .toArray() - javascript

I'm new to MongoDB, and working with aggregation. Here goes:
I have two collections 'video_card' and 'vendors'. I am fetching a video card document from the collection with the following structure:
_id:ObjectId(x),
vendorList:
0: ID1;
1: ID2;
I am trying to do a join between this document and this vendor collection:
_id: id:ObjectId(y)
name: "Amazon"
My aggregate is as follows so far:
const products = await db
.collection("video_card")
.aggregate([
{
$project: {
mappedVendors: {
$map: {
input: "$vendorList",
as: "vendorName",
in: {
$lookup: {
from: "vendors",
localField: "vendorList",
foreignField: "name",
as: "VendorNames"
},
},
},
},
},
},
]);
This returns a cursor object. However, when I attach .toArray() to the end of this, I get a code:168 'InvalidPipelineOperator'. Why is this?
To clarify, my intent is to return the data with vendorList ids swapped with names.

To follow up, I realized that my foreignField in the $lookup was incorrect, and changed it to _id. I hope this helps other people learning aggregate functions.

Related

Use 'virtual' in an array of objects (Mongoose)

I have two schemas as the following:
const castListSchema = new Schema({
id: String,
roleInVideo: String
});
const videoSchema = new Schame({
castList: { type: [castListSchema], default: [] }
});
I want to get data by the following structure:
castList: [
{
details: { ... },
roleInVideo: 'director'
},
...
]
I want to use virtual not aggregate and etc...
Is it possible? How?
Yes, you can use the virtual function inside the array of objects.
Use the following code to use the virtual function for the array of objects.
videoSchema.virtual("castList.castData", {
ref: "new_assets",
localField: "castList.castListSchema",
foreignField: "_id",
justOne: true
});
In populate also use this "castList.castData" as key to retrieve the data.

How to join array of documents from array of _ids in mongoDB

I have a document inside of MongoDb that has an array of ObjectIds. Looks like this:
--Shops Collection--
listing_ids: [
ObjectId("6092f741ccb0d55ba444883e"),
ObjectId("6092f741ccb0d55ba444883f"),
ObjectId("6092f741ccb0d55ba4448840"),
ObjectId("6092f741ccb0d55ba4448841")
],
...
and I have a listing document like this:
--Listings Collection--
_id: ObjectId("6092f741ccb0d55ba444883e"),
...
How do I join the listings documents onto the shops document that contains the arrays? I have tried the "$lookup" operator from MonogoDb but can't get it to work. I'm always hit with a FailedToParse error. This is what the "$lookup" looks like:
const collection = client.db("development").collection("shops");
const shop = await collection.aggregate([
{
"$lookup": {
"from": "listings",
"localfield": "listing_ids",
"foreignField": "_id",
"as": "listings"
}}
]).toArray();
This is expected to return all of the shops documents with the corresponding listings documents in an array called listings
Turns out I almost had it right. I ended up using the MongoDB Atlas Aggregation tool and it helped me. This is the working pipeline:
.aggregate([
{
'$lookup': {
'from': 'listings',
'localField': 'listing_ids',
'foreignField': '_id',
'as': 'listings'
}
}, {
'$lookup': {
'from': 'listings',
'localField': 'highlightedListings',
'foreignField': '_id',
'as': 'highlightedListings'
}
}
]).toArray();

Mongoose for/await/of loop is `sort` required in Query?

From documentation of Mongoose there is the following example about iterating with async/await:
// Works without using `cursor()`
for await (const doc of Model.find([{ $sort: { name: 1 } }])) {
console.log(doc.name);
}
Since I am quite new in MongoDB, I want to understand if [{ $sort: { name: 1 } }] is required to get the query working or it is only an example.
My goal is to iterate all the documents in a Collection, so I suppose that following code should be fine:
// Works without using `cursor()`
for await (const doc of Model.find()) {
console.log(doc.name);
}
Is it correct?
In Mongodb 4.2 and older passing an array of objects to find will trigger an exception. So if you run:
Model.find([{ $sort: { name: 1 } }]).then(...)
you will get:
ObjectParameterError: Parameter "filter" to find() must be an object
However, in MongoDB 4.4, [{ $sort: { name: 1 } }] can, in fact, be passed to find. However, $sort does not actually go into find params, but rather should be used like so Model.find().sort({name: 1}).
That makes me believe that [{ $sort: { name: 1 } }] in the link you supplied is just a placeholder and servers no purpose. So running
for await (const doc of Model.find()) {
is perfectly fine.

How to filter only some rows of an array inside a document?

I have a MongoDB collection of documents, using a schema like this:
var schema = new Schema({
name: String,
images: [{
uri: string,
active: Boolean
}]
});
I'd like to get all documents (or filter using some criteria), but retrieve - in the images array - only the items with a specific property (in my case, it's {active: true}).
This is what I do now:
db.people.find( { 'images.active': true } )
But this retrieves only documents with at least one image which is active, which is not what I need.
I know of course I can filter in code after the find is returned, but I do not like wasting memory.
Is there a way I can filter array items in a document using mongoose?
Here is the aggregation you're looking for:
db.collection.aggregate([
{
$match: {}
},
{
$project: {
name: true,
images: {
$filter: {
input: "$images",
as: "images",
cond: {
$eq: [
"$$images.active",
true
]
}
}
}
}
}
])
https://mongoplayground.net/p/t_VxjfiBBMK

Async/await functionality for db.eval aggregate

I am trying to execute a db.collection.aggregate() query within a call to db.eval(). I am using eval() because I am making a dynamic number of lookups, so I generate the query by concatenating relevant strings. The query works perfectly when I manually remove the quotes from the string:
await db.collection('Products').aggregate([{
$lookup: {
from: 'Golomax',
localField: 'barcode',
foreignField: 'barcode',
as: 'Golomax'
}
}, {
$unwind: {
path: '$Golomax',
preserveNullAndEmptyArrays: true
}
}, {
$lookup: {
from: 'Masivos SA',
localField: 'barcode',
foreignField: 'barcode',
as: 'Masivos SA'
}
}, {
$unwind: {
path: '$Masivos SA',
preserveNullAndEmptyArrays: true
}
}, {
$out: 'Output'
}]).toArray();
Unfortunately, it does not work when I am using the string in a call to db.eval(). I put quotes around the code snippet above and set the string equal to the variable 'query' and tried this:
db.eval('async function(){' + query + ' return;}', function(err, result) {
console.log('the result is: ', result);
});
I've also tried removing the word "async," and this still has not worked. How do I ensure that the function will finish aggregating before returning? Thanks.
-- EDIT --
I just noticed that db.eval() is deprecated and planned for removal. The alternative is to "implement the equivalent queries/operations using the normal MongoDB query language and client driver API." How can I do this using a string query?
You don't need $eval for this. It sounds like you want to create a $lookup and $unwind for each item in an Array. That is exactly what map() is for. You can create the array of commands separate and then pass it to aggregate():
// Have a list of places
const thingsToUnwind = [
'Golomax',
'Masivos SA',
'Some Other Place',
'Yet Another Place'
];
const unwindables = thingsToUnwind
// Create a $lookup and $unwind for each place
.map(place => {
return [{
$lookup: {
from: place,
localField: 'barcode',
foreignField: 'barcode',
as: place
}
},
{
$unwind: {
path: `$${place}`,
preserveNullAndEmptyArrays: true
}
}
];
})
// Flatten the array of arrays
.reduce((acc, curr) => [...acc, ...curr], []);
// Add an $output node
unwindables.push({
$out: 'Output'
});
// Perform the aggregation
await db
.collection('Products')
.aggregate(unwindables)
.toArray();
I just solved my own problem using the Javascript eval() and removing the "await" at the beginning of the string. It executes perfectly now!

Categories