How to join 3 tables using Sequelize? - node.js

I have 3 tables named:
customers
columns are: id, customer_full_name
transaction_details
columns are: id, customer_id, amount, merchant_id
merchants
columns are: id, merchant_full_name
transaction_details table contains two foreign keys of customer_id and merchant_id.
One customer may have multiple transactions. One merchant may have multiple transactions too.
Situation:
Merchant logins to the website to view the transaction details belong to this merchant. What I would like to display is a table with the following columns:
a. Transaction ID
b. Customer Name
c. Transaction Amount
My code as below:
Merchant.findAll({
where: {
id:req.session.userId,
},
include:[{
model:TransactionDetails,
required: false
}]
}).then(resultDetails => {
var results = resultDetails;
});
My code above does not give me the result that I want. How can I fix this ?

What you need is belongsToMany association in case you haven't defined it yet. Here is the example
const Customer = sequelize.define('customer', {
username: Sequelize.STRING,
});
const Merchant = sequelize.define('merchant', {
username: Sequelize.STRING,
});
Customer.belongsToMany(Merchant, { through: 'CustomerMerchant' });
Merchant.belongsToMany(Customer, { through: 'CustomerMerchant' });
sequelize.sync({ force: true })
.then(() => {
Customer.create({
username: 'customer1',
merchants: {
username: 'merchant1'
},
}, { include: [Merchant] }).then((result) => {
Merchant.findAll({
include: [{
model: Customer
}],
}).then((result2) => {
console.log('done', result2);
})
})
});
Now result2 has all the values. Customer data can be accessed at
result2[0].dataValues.customers[0].dataValues. CustomerMerchant data is available at result2[0].dataValues.customers[0].CustomerMerchant

Related

Race condition between two request in mongodb

This is eCommerce site and I'm using mongodb as a database, users can place order and each order can have multiple products. Product is a seperate table that contains quantityLeft of each product. There's a situation that when two concurrent requests comes and tries to buy the same product the ordered items in orders table exceeds the available quantity in product table.
Product Table
{
_id: '56e33c56ddec541556a61763',
name: 'Chocolate',
quantityLeft: 1
}
In product's table only 1 chocolate left if one request comes at a time it works fine. Request comes check the order.quantity and handle if there's enough product available.
But when 2 requests comes exactly the same time issue occurs both the request query the database to get the product and check the quantityLeft and found that only 1 chocolate is available and passes the check that enough quantity is still present in inventory and places the order. But in actual 2 orders are placed and quantity we have is only 1.
Order Table
{
_id: '60e33c56ddec541556a61595',
items: [{
_id: '56e33c56ddec541556a61763',
quantity: 1
}]
}
I tried to put both the queries to get the Product detail and place order in same transaction but it doesn't work. Something like this
const session = await mongoose.startSession({ defaultTransactionOptions: { readConcern: { level: 'local' }, writeConcern: { w: 1 } } })
await session.withTransaction(async () => {
const promiseArray = order.items.map((item) => Product.find({ _id: item._id }, { session })
const products = Promise.all(promiseArray)
const productById = {}
products.forEach((product) => {
productById[product._id] = product
})
order.items.forEach((item) => {
if (productById[item].quantityLeft < order.item) {
throw new Error('Not enough quantity')
}
})
await Order.create(order, {session})
}, { readConcern: { level: 'local' }, writeConcern: { w: 1 } });
I'm using nodejs (14.16), mongodb as database npm package is mongoose (5.9).

Sequelize insert additional fields into junction table

as described in sequelize documentation here,
Foo.belongsToMany(Bar, { through: Baz })
then there is a method to insert into junction table:
fooInstance.addBars([5,4])
It will insert into Baz junction table two fields: FooId,BarId.
But i need to insert another field value too. sth like this:
fooInstance.addBars([{BarId: 5, otherField:'xxx'}, ...]
How i can achieve that without manual insert?
See Advanced Many-to-Many guide.
const User_Profile = sequelize.define('User_Profile', {
selfGranted: DataTypes.BOOLEAN
}, { timestamps: false });
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });
With this, we can now track an extra information at the through table, namely the selfGranted boolean. For example, when calling the user.addProfile() we can pass values for the extra columns using the through option.
Example:
const amidala = await User.create({ username: 'p4dm3', points: 1000 });
const queen = await Profile.create({ name: 'Queen' });
await amidala.addProfile(queen, { through: { selfGranted: false } });

Sequelize - How to get entries from one table that are not assiciated by another table

I used Sequelize to define three Entities for users rating posts:
var User = sequelize.define('User', {
email: {
type: Sequelize.STRING,
primaryKey: true
}
});
var Post = sequelize.define('Post', {
link: {
type: Sequelize.STRING,
primaryKey: true
}
});
var Rating = sequelize.define('Rating', {
result: Sequelize.STRING
});
Rating.belongsTo(Post);
Post.hasMany(Rating);
Rating.belongsTo(User);
User.hasMany(Rating);
A user can rate several posts. Each rating belongs to exactly one user and one post.
Now I'd like to query for a given user all posts that are not already rated by this user. I tried a thousand ways but without success. Any idea how to achieve this in Sequelize? Thanks a lot!
There are two methods either use raw query in Sequelize or via Sequelize query as well -
Raw -
return Db.sequelize.query("SELECT * FROM Post P WHERE (P.id NOT IN (SELECT postId FROM Ratings R WHERE R.userId="+userId+")) ",{ type: Sequelize.QueryTypes.SELECT });
Sequelize -
return Post.findAll({
where: {
Sequelize.literal("(posts.id NOT IN (SELECT R.postId FROM Rating R WHERE R.userId="+userId+"))")
}
});

How to add dynamic additional attributes into a junction table with ManyToMany association in Sequelize

I created a many-to-many association by sequelize in my koa app. But I had no idea on how to create additional attributes in the junction table. Thanks.
I referred to the official doc of sequelize but didn't find a solution. In brief:
"an order can have many items"
"an item can exist in many orders"
Then I created OrderItems as junction table.
But I have trouble in inserting value into the junction table
// definitions
const Item = sequelize.define('item', itemSchema);
const Order = sequelize.define('order', orderSchema);
// junction table
const OrderItems = sequelize.define('order_item', {
item_quantity: { type: Sequelize.INTEGER } // number of a certain item in a certain order.
});
// association
Item.belongsToMany(Order, { through: OrderItems, foreignKey: 'item_id' });
Order.belongsToMany(Item, { through: OrderItems, foreignKey: 'order_id' });
// insert value
const itemVals = [{ name: 'foo', price: 6 }, { name: 'bar', price: 7 }];
const orderVals = [
{
date: '2019-01-06',
items: [{ name: 'foo', item_quantity: 12 }]
},
{
date: '2019-01-07',
items: [{ name: 'foo', item_quantity: 14 }]
}
]
items = Item.bulkCreate(itemVals)
orders = Order.bulkCreate(orderVals)
//Questions here: create entries in junction table
for (let order of orders) {
const itemsInOrder = Item.findAll({
where: {
name: {
[Op.in]: order.items.map(item => item.name)
}
}
})
order.addItems(itemsInOrder, {
through: {
item_quantity: 'How to solve here?'
}
})
}
// my current manual solution:
// need to configure column names in junction table manually.
// Just like what we do in native SQL.
const junctionValList =[]
for (let orderVal of orderVals) {
orderVal.id = (await Order.findOne(/* get order id */)).dataValues.id
for (let itemVal of orderVal.items) {
itemVal.id = (await Item.findOne(/* get item id similarly */)).dataValues.id
const entyInJunctionTable = {
item_id: itemVal.id,
order_id: orderVal.id,
item_quantity: itemVal.item_quantity
}
junctionValList.push(entyInJunctionTable)
}
}
OrderItems.bulkCreate(junctionValList).then(/* */).catch(/* */)
In case that this script it's for seeding purpose you can do something like this:
/*
Create an array in which all promises will be stored.
We use it like this because async/await are not allowed inside of 'for', 'map' etc.
*/
const promises = orderVals.map((orderVal) => {
// 1. Create the order
return Order.create({ date: orderVal.date, /* + other properties */ }).then((order) => {
// 2. For each item mentioned in 'orderVal.items'...
return orderVal.items.map((orderedItem) => {
// ...get the DB instance
return Item.findOne({ where: { name: orderedItem.name } }).then((item) => {
// 3. Associate it with current order
return order.addItem(item.id, { through: { item_quantity: orderedItem.item_quantity } });
});
});
});
});
await Promise.all(promises);
But it's not an efficient way to do it in general. First of all, there are a lot of nested functions. But the biggest problem is that you associate items with the orders, based on their name and it's possible that in the future you will have multiple items with the same name.
You should try to use an item id, this way you will be sure about the outcome and also the script it will be much shorter.

Sequelize nested were when the subject of the include is possibly null

In my models I have a User which belongsTo({ model: Company, as: 'company' }) (and conversely Company hasMany({ model: User, as: 'members' }))
I have constructed a query as follows
const query = {
where: { onboardingState: 'pending' },
include: [
{
model: Company,
where: { suspendedAt: { [Op.eq]: null } }
}
]
}
const users = await User.findAll(query)
This works fine if the user has an associated company. But there is an edge case (admin users) where the user does not have an associated company, and in that case the findAll query returns []
How do I structure my query to only require company.suspendedAt == null if the company exists, and to ignore the company where clause if the company does not exist?

Resources