I am trying to select only one field from a mongo document and print the value for it. I found this answer https://stackoverflow.com/a/25589150 which showed how we can achieve this. Below I have tried doing the same yet the entire document ends up getting printed.
const mongoHost =
'somemongourl'
const mongodb = require('mongodb');
const { MongoClient } = mongodb;
MongoClient.connect(
mongoHost,
{ useNewUrlParser: true },
async (error, client) => {
if (error) {
return console.log('Unable to connect to database!');
}
const db = client.db('cartDatabase');
const values = await db
.collection('cart')
.find({ customer_key: 'c_1' }, { customer_key: 1, _id: 0 })
.toArray();
console.log(values);
}
);
This is the output for example I got :-
[
{
_id: new ObjectId("611b7d1a848f7e6daba69014"),
customer_key: 'c_1',
products: [ [Object] ],
coupon: '',
discount: 0,
vat: 0,
cart_total: 999.5,
cart_subtotal: 999.5
}
]
This is what I was expecting -
[
{
customer_key: 'c_1'
}
]
The standard Node.js MongoDB driver requires a top-level projection property for the options parameter if you wish to project your documents. This would result in the second parameter of your find() call looking like this:
{ projection: { customer_key: 1, _id: 0 } }
This is indicated in the Node.js MongoDB driver API documentation, which is notably not a 1-to-1 match with the MongoDB shell API.
As of the time of this answer, you could find the collection.find() reference here. This reference shows the following method signature (again as of when this answer was written):
find(filter: Filter<WithId<TSchema>>, options?: FindOptions<Document>)
Following the FindOptions parameter takes us to this reference page, which details the various top-level options properties available for the find() method. Among these is the projection property in question.
In short, don't use the normal MongoDB documentation as a reference for your programming language's MongoDB driver API. There will often be disconnects between the two.
Related
I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)
I am using sails JS with Mongo DB.
My model is:
module.exports = {
attributes: {
title:{type:"string",required:true},
content:{type:"string",required:true},
date:{type:"string",required:true},
filename:{type:"string",required:true},
},
};
My Controller is:
fetchposts:function(req,res){
console.log("in fetch posts")
mysort={$id:-1}
Cyberblog.find().sort(mysort).limit(5).exec(function(err, result) {
if (err || !result) {
message="no records fetched";
console.log(message);
res.redirect('/newpost');
}
else{
console.log(result)
}
I am facing an error saying that
"Warning: The sort clause in the provided criteria is specified as a dictionary (plain JS object),
meaning that it is presumably using Mongo-Esque semantics (something like { fullName: -1, rank: 1 }).
But as of Sails v1/Waterline 0.13, this is no longer the recommended usage. Instead, please use either
a string like 'fullName DESC', or an array-like [ { fullName: 'DESC' } ].
(Since I get what you mean, tolerating & remapping this usage for now...)
and I am unable to fetch any records. It is showing no records fetched.
So I have one warning on Sort and no records coming from DB. Please help me resolve the issue.
Sort clause allow send string:
var users = await User.find({ name: 'Jake'})
.sort('age ASC');
return res.json(users);
Or an array:
var users = await User.find({ name: 'Finn'})
.sort([
{ age: 'ASC' },
{ createdAt: 'ASC' },
]);
return res.json(users);
Check this out in the documentation:
https://sailsjs.com/documentation/reference/waterline-orm/queries/sort
This question already has answers here:
projection not working with find query
(2 answers)
Closed 4 years ago.
I know this is probably an obvious question, but I am new to Mongo, and cannot find what I am doing wrong in looking through the documentation and examples... I am trying to query a list of records in mongo pulling out a specific field value from each of them, and I currently have.
const express = require('express');
const MongoClient = require('mongodb').MongoClient;
// Connection URL
const url = 'mongodb://localhost:27017';
// Database Name
const dbName = 'TestDB';
let db = null;
let books = null;
// Use connect method to connect to the server
MongoClient.connect(url, function(err, client) {
if (err) {
console.error("Connection Failed")
}
db = client.db(dbName);
books = db.collection('books')
books.find( {}, { Name:1 } ).toArray().then(console.log)
});
But it prints out
[ { _id: 5b5fae79252d63309c908522,
Name: 'TestBook',
chapters: { '1': [Object] } } ]
Instead of
[ {Name: 'TestBook'} ]
I think you need to provide projection in the options parameter to get only the field name:
books.find({}, { projection: { Name:1 }})
.toArray()
.then(console.log)
Actually, find takes 2 parameters, the first one is the criteria for query and the second one includes some options. You can check the supported fields for options here: http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#find
Updated: For mongodb package version < 3.0. Use fields instead of projection: books.find({}, { fields: { Name:1 }})
I am trying to return only one field sessions from a document.
I'm using the current query (it returns the entire document):
yield users.findOne({
'_id': id // var id holds object id ObjectId("560ae1dc53cb3222679430f1")
}, {
'_id': 0, // <--- being ignored
'sessions': 1 // <--- being ignored
});
I tried in mongo shell and this works as it should:
db.users.find({"_id":ObjectId("560ae1dc53cb3222679430f1")},{"sessions":1,"_id":0}).pretty() // <--- works
I'm currently using co-monk which is based off of mongoskin. So it should work.
Not made clear in the documentation, but there is an explicit key name syntax to the "options" object :
yield users.findOne({ '_id': id }, { 'fields': { '_id': 0, 'sessions': 1 }});
So it works a bit differently to the MongoDB shell API. The same applies for other options such as sort.
The accepted answer didn't work in my case, so I went digging through the docs and this is what I found for the fields option: Deprecated Use options.projection instead, and got the respective warning in the console. Chaining in .project() as with .find() didn't work for .findOne() in my case, so it has to be in the options using projection:
yield users.findOne({
'_id': id
}, { projection: { //projection rather than fields
'_id': 0,
'sessions': 1 }
});
Here it is: mongodb.github.io/node-mongodb-native
2022 update
You have to use projection in latest update.
Reference:
https://www.mongodb.com/docs/drivers/node/current/quick-reference/
Here is a sample code for this question.
const ObjectId = require('mongodb').ObjectId;
var query = { _id: ObjectId('your search object id here') };
var options = { projection: { sessions: 1, _id: 0 } };
users.findOne(query, options);
I'm trying to:
Find a document according to a search criteria,
If found, update some attributes
If not insert a document with some attributes.
I'm using a Bulk.unOrderedOperation as I'm also performing a single insert. And I want to do everything in one operation againast DB.
However something it's causing nothing is being inserted for the update/upsert operation.
This is the insert document:
var lineUpPointsRoundRecord = {
lineupId: lineup.id, // String
totalPoints: roundPoints, // Number
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: lineup.matchDate.round, // Number
date: new Date()
}
This is the upsert document:
var lineUpPointsGeneralRecord = {
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: 0,
signupPoints: home.signupPoints, // String
lfPoints: roundPoints+home.signupPoints, // Number
roundPoints: [roundPoints] // Number
};
This is how I'm trying to upsert/update:
var batch = collection.initializeUnorderedBulkOp();
batch.insert(lineUpPointsRoundRecord);
batch.find({team: lineUpPointsRoundRecord.teamId, round: 0}).
upsert().
update({
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints}
});
batch.execute(function (err, result) {
return cb(err,result);
});
Why wouldn't it be upserting/updating?
Note
That is JS code using waterline ORM which also uses mongodb native driver.
Your syntax here is basically correct, but your general execution was wrong and you should have "seperated" the "upsert" action from the other modifications. These will otherwise "clash" and produce an error when an "upsert" occurs:
LineupPointsRecord.native(function (err,collection) {
var bulk = collection.initializeOrderedBulkOp();
// Match and update only. Do not attempt upsert
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).updateOne({
"$inc": { "lfPoints": roundPoints },
"$push": { "roundPoints": roundPoints }
});
// Attempt upsert with $setOnInsert only
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).upsert().updateOne({
"$setOnInsert": lineUpPointsGeneralRecord
});
bulk.execute(function (err,updateResult) {
sails.log.debug(err,updateResult);
});
});
Make sure your sails-mongo is a latest version supporting the Bulk operations properly be the inclusion of a recent node native driver. The most recent supports the v2 driver, which is fine for this.
I recommend use bulkWrite exemplary code with bulk upsert of many documents:
In this case you will create documents with unique md5. If document exists then will be updated but no new document is created like in classical insertMany.
const collection = context.services.get("mongodb-atlas").db("master").collection("fb_posts");
return collection.bulkWrite(
posts.map(p => {
return { updateOne:
{
filter: { md5: p.md5 },
update: {$set: p},
upsert : true
}
}
}
),
{ ordered : false }
);
https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/
Typically I have always set upsert as a property on update. Also update should be able to find the record itself so no need to find it individually.
Depending on the environment the $ may or may not be necessary.
batch.update(
{team: lineUpPointsRoundRecord.teamId, round: 0},
{
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints},
$upsert: true
});