SUM column in bookshelfjs relationship - node.js

I want to sum a column in a Bookshelfjs relationship. I have my query set up as
return this.hasMany('MutualFundPortfolio').query().sum('balance');
But I am having this error TypeError: Cannot read property 'parentFk' of undefined any body has any clue how solve this? It seems Bookshelf doesn't support sum
const moment = require('moment');
const Bookshelf = require('../bookshelf');
require('./wishlist');
require('./kyc');
require('./wallet');
const User = Bookshelf.Model.extend({
tableName: 'users',
hasTimestamps: true,
hidden: ['code', 'password'],
toJSON(...args) {
const attrs = Bookshelf.Model.prototype.toJSON.apply(this, args);
attrs.created_at = moment(this.get('created_at')).add(1, 'hour').format('YYYY-MM-DD HH:mm:ss');
attrs.updated_at = moment(this.get('updated_at')).add(1, 'hour').format('YYYY-MM-DD HH:mm:ss');
return attrs;
},
local_wallet() {
return this.hasMany('LocalWallet').query((qb) => {
qb.orderBy('id', 'DESC').limit(1);
});
},
mutual_fund_portfolio() {
return this.hasMany('MutualFundPortfolio').query().sum('balance');
},
global_wallet() {
return this.hasMany('GlobalWallet').query((qb) => {
qb.orderBy('id', 'DESC').limit(1);
});
},
local_gift_card_wallet() {
return this.hasMany('LocalGiftCardWallet').query((qb) => {
qb.orderBy('id', 'DESC').limit(1);
});
},
global_gift_card_wallet() {
return this.hasMany('GlobalGiftCardWallet').query((qb) => {
qb.orderBy('id', 'DESC').limit(1);
});
}
});
module.exports = Bookshelf.model('User', User);
Above is the full user model. I am then getting the value as
return User.where({ id })
.orderBy('id', 'DESC')
.fetch({
withRelated: [
'mutual_fund_portfolio',
'local_wallet',
'global_wallet',
'local_gift_card_wallet',
'global_gift_card_wallet'
]
})
The mutual_fund_portfolio comes out as an empty array.

hasMany performs a simple SQL join on a key. I believe the TypeError: Cannot read property 'parentFk' of undefined error refers to the fact that the table you are referencing here MutualFundPortfolio does not share a key with the table in the model you are using here.
It's not visible above sample but I'm assuming it's something like:
const User = bookshelf.model('User', {
tableName: 'users',
books() {
return this.hasMany('MutualFundPortfolio').query().sum('balance');
}
})
In my hypothetical example the users table has a primary key id column userId that is also in MutualFundPortfolio as a foreign key. My guess is that the error is because MutualFundPortfolio does not have that column/foreign key.

Related

Mongoose Set object property in array of objects

I have a document consisting of a Post. Each Post has an array of Comments, which are an object each. So my document looks like this
Now, I want to be able to update the message property in a given Comment object.
So I'll be using the $set method, but how would I be able to select the specific object. Currently, my unfinished method looks like this
export const editComment = async (req, res) => {
const { id } = req.body;
const post = await Post.findById(req.params.id);
const _id = post.id;
const postComments = post.comments.map((comment) => comment._id);
const commentIndex = postComments.indexOf(id.id);
const message = post.comments[commentIndex].message;
try {
await Post.updateOne(
{ _id },
{
$set: {
// Action to update the comment
},
},
{ new: true }
);
res.status(200).json({ message: post });
} catch (error) {
res.status(400).json({ error: error.message });
}
}
I figured selecting the right index of the comment was a good start, but how would I, in the $set method, select the correct Comment object, and then update the message property?
You have to find the right data in database and update its required property. You can do it by the following method
exports.updateMessage= async (_id,objectId, newMessage) => {
return await TableName.updateOne({_id: _id},{{comments: {$elemMatch: {_id: objectId}$set:{message:newMessage}}});
};
Post.updateOne(_id,
{
$set: {
"comments.$[elem].message": "the value you want to set for message"
}
},
{
arrayFilters: [
{
"elem._id": 1 // _id of comment object you want to edit
}
]
})

Sequelize upsert or create without PK

I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();

How to get data from parent table in sequelize

I am using Sequelize dependency
I want to get data from the parent table, I have a table tblroute which is associated with routedetails .tblroute's id is the foreign key in routedetails
router.get('/bookingdetails', passengerMiddleware, (req, res) => {
tblBookingdetail.sync().then(() => {
tblBookingdetail.findAll({
where: {
passengerId: req.passenger.id
},
include: [
{
model: tblRoute,
as: 'route',
include: [
{
model: tblRouteDetails,
as: 'routedetails'
}
]
},
]
}).then(data => {
if (data && data.length > 0) {
return res.json(baseResponse.sendSuccess('data', data));
} else {
return res.json(baseResponse.sendError('data not found'));
}
});
})
})
it gives following error
Unhandled rejection SequelizeEagerLoadingError: tblRouteDetail is not associated to tblRoute!
Because you are trying to fetch result set without having a relationship define inside model tblRouteDetail.
Open your model tblRouteDetail and add a relationship between both table like below.
tblRoute.associate = function associate({ tblRouteDetail }) {
return tblRoute.hasOne(tblRouteDetail, {
foreignKey : 'tblroute_id'
});
};
It might help you to understand.

MongoDB - find one and add a new property

Background: Im developing an app that shows analytics for inventory management.
It gets an office EXCEL file uploaded, and as the file uploads the app convert it to an array of JSONs. Then, it comapers each json object with the objects in the DB, change its quantity according to the XLS file, and add a timestamp to the stamps array which contain the changes in qunatity.
For example:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":100,
"Warehouse":4,
"stamps":[]
}
after the XLS upload, lets say we sold 10 units, it should look like that:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":90,
"Warehouse":4,
"stamps":[{"1548147562": -10}]
}
Right now i cant find the right commands for mongoDB to do it, Im developing in Node.js and Angular, Would love to read some ideas.
for (let i = 0; i < products.length; i++) {
ProductsDatabase.findOneAndUpdate(
{"_id": products[i]['id']},
//CHANGE QUANTITY AND ADD A STAMP
...
}
You would need two operations here. The first will be to get an array of documents from the db that match the ones in the JSON array. From the list you compare the 'product_quantity' keys and if there is a change, create a new array of objects with the product id and change in quantity.
The second operation will be an update which uses this new array with the change in quantity for each matching product.
Armed with this new array of updated product properties, it would be ideal to use a bulk update for this as looping through the list and sending
each update request to the server can be computationally costly.
Consider using the bulkWrite method which is on the model. This accepts an array of write operations and executes each of them of which a typical update operation
for your use case would have the following structure
{ updateOne :
{
"filter" : <document>,
"update" : <document>,
"upsert" : <boolean>,
"collation": <document>,
"arrayFilters": [ <filterdocument1>, ... ]
}
}
So your operations would follow this pattern:
(async () => {
let bulkOperations = []
const ids = products.map(({ id }) => id)
const matchedProducts = await ProductDatabase.find({
'_id': { '$in': ids }
}).lean().exec()
for(let product in products) {
const [matchedProduct, ...rest] = matchedProducts.filter(p => p._id === product.id)
const { _id, product_quantity } = matchedProduct
const changeInQuantity = product.product_quantity - product_quantity
if (changeInQuantity !== 0) {
const stamps = { [(new Date()).getTime()] : changeInQuantity }
bulkOperations.push({
'updateOne': {
'filter': { _id },
'update': {
'$inc': { 'product_quantity': changeInQuantity },
'$push': { stamps }
}
}
})
}
}
const bulkResult = await ProductDatabase.bulkWrite(bulkOperations)
console.log(bulkResult)
})()
You can use mongoose's findOneAndUpdate to update the existing value of a document.
"use strict";
const ids = products.map(x => x._id);
let operations = products.map(xlProductData => {
return ProductsDatabase.find({
_id: {
$in: ids
}
}).then(products => {
return products.map(productData => {
return ProductsDatabase.findOneAndUpdate({
_id: xlProductData.id // or product._id
}, {
sku: xlProductData.sku,
product_name: xlProductData.product_name,
product_cost: xlProductData.product_cost,
product_price: xlProductData.product_price,
Warehouse: xlProductData.Warehouse,
product_quantity: productData.product_quantity - xlProductData.product_quantity,
$push: {
stamps: {
[new Date().getTime()]: -1 * xlProductData.product_quantity
}
},
updated_at: new Date()
}, {
upsert: false,
returnNewDocument: true
});
});
});
});
Promise.all(operations).then(() => {
console.log('All good');
}).catch(err => {
console.log('err ', err);
});

Multiple insertion with addition data with pg-promise

I have a large dataset that I want to insert into a postgres db, I can achieve this using pg-promise like this
function batchUpload (req, res, next) {
var data = req.body.data;
var cs = pgp.helpers.ColumnSet(['firstname', 'lastname', 'email'], { table: 'customer' });
var query = pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
// success;
})
.catch(error => {
// error;
return next(error);
});
}
The dataset is an array of objects like this:
[
{
firstname : 'Lola',
lastname : 'Solo',
email: 'mail#solo.com',
},
{
firstname : 'hello',
lastname : 'world',
email: 'mail#example.com',
},
{
firstname : 'mami',
lastname : 'water',
email: 'mami#example.com',
}
]
The challenge is I have a column added_at which isn't included in the dataset and cannot be null. How do I add a timestamp for each record insertion to the query.
As per the ColumnConfig syntax:
const col = {
name: 'added_at',
def: () => new Date() // default to the current Date/Time
};
const cs = pgp.helpers.ColumnSet(['firstname', 'lastname', 'email', col], { table: 'customer' });
Alternatively, you can define it in a number of other ways, as ColumnConfig is very flexible.
Example:
const col = {
name: 'added_at',
mod: ':raw', // use raw-text modifier, to inject the string directly
def: 'now()' // use now() for the column
};
or you can use property init to set the value dynamically:
const col = {
name: 'added_at',
mod: ':raw', // use raw-text modifier, to inject the string directly
init: () => {
return 'now()';
}
};
See the ColumnConfig syntax for details.
P.S. I'm the author of pg-promise.

Resources