Sequelize include an attribute from junction table - javascript

I'm building a shop, where in the database i have orders and items. Here's the code for the models:
Item:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Item = sequelize.define('item', {
image: {
type: Sequelize.STRING,
allowNull: false
},
itemName: {
type: Sequelize.STRING,
allowNull: false,
field: 'item_name'
},
price: {
type: Sequelize.INTEGER,
allowNull: false
}
})
module.exports = Item
Order:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Order = sequelize.define('order', {
orderNumber: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
shop: {
type: Sequelize.INTEGER
},
location: {
type: Sequelize.STRING
}
})
module.exports = Order
They are related through belongs to many:
Item.belongsToMany(Order, {through: OrderItem})
Order.belongsToMany(Item, {through: OrderItem})
The OrderItem has an additional field, 'count', which i need to return:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var OrderItem = sequelize.define('OrderItem', {
count: Sequelize.INTEGER
})
module.exports = OrderItem
However, when i try to include the OrderItem model, it doesn't work. No errors, nothing. The query just doesn't return:
Order.findAll({
where: {
userId: userId
},
include: [{
model: Item,
include: [OrderItem]
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
How to get what i need from sequeilize?

You may try something like this:
Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']} // this may not be needed
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
Also, your models must have a right naming strategy. E.g. :
Item - must have itemId field instead of itemNumber,
Order - must have orderId as primary field
OrderItem's structure:
var OrderItem = sequelize.define('OrderItem', {
orderId: Sequelize.INTEGER,
itemId: Sequelize.INTEGER,
count: Sequelize.INTEGER
})
Another one is to use direct names of related fields and models when you use belongsToMany()
More here: http://docs.sequelizejs.com/en/latest/docs/associations/

Turns out that the OrderItem is already nested inside the Item object. However, this doesn't make a nice return format , so the question is still open.

I know this is year later, but yet..
You can use the property joinTableAttributes to get a junction table fields .
for example :
Order.findAll({
where: {
userId: userId
},
joinTableAttributes: ['count'],
include: [{
model: Item,
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})

I am using Graphql and I had the same problem with the formatting. I did a small map after getting the result.
For example:
let orders = await Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']}
}]
});
let ordersWithCount = orders.map((o) => {
let orderWithCount = {};
orderWithCount = o;
orderWithCount.count = o.OrderItem.count;
return orderWithCount;
});

Related

Sequelize: Create multiple self association

I have model "Subject" which has self relationship with it self "Successor" and "Predecessor". For that i required to create two foreign key "successorId" and "predecessorId".
I am able to create relation but don't how to add entry in foreign key.
Model Subject:
const { sequelize } = require('../config/databaseInit');
const { DataTypes, Model } = require('sequelize');
class Subject extends Model {}
Subject.init(
{
subjectCode: {
type: DataTypes.STRING(40),
unique: true,
},
subjectName: {
type: DataTypes.STRING(60),
allowNull: false,
},
...
...
...
successorId: {
type: DataTypes.INTEGER,
references: {
model: 'Subject',
key: 'id',
},
},
predecessorId: {
type: DataTypes.INTEGER,
references: {
model: 'Subject',
key: 'id',
},
},
},
{
sequelize: sequelize,
modelName: 'Subject',
tableName: 'Subject',
timestamps: true,
}
);
For example:
const sub1 = await Subject.create({....});
const sub2 = await Subject.create({....});
// None of them are working.
sub1.setSuccessorId(sub2);
sub1.setSuccessor(sub2);
sub1.update({ successorId: sub1.id });
you can identify your relation model 1-n/n-n/1-n
here link: https://sequelize.org/master/manual/assocs.html
example:
Subject.belongsTo(models.Class, { as: 'subject', foreignKey: 'subjectId' });

Dynamically push, pull, and set on mongoose schema update

I am trying to setup my patch api so that I can create a dynamic query to push, pull, and set data in my mongoose schema. I have plenty of values that I would change using set, but I also have an array of objects which would require me to call push when I need to insert and pull when I need to remove an item. I'm trying to find the best way to combine this into a dynamic structure.
Schema:
const StepSchema = new Schema({
position: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
due_date: {
type: Date
},
status: [{
label: {
type: String,
enum: ['Inactive', 'In Progress', 'Flagged', 'Complete'],
default: 'Inactive'
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
date: {
type: Date
}
}],
comments: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
},
body: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
}],
});
Api:
router.patch('/',
async (req, res) => {
let setQuery = req.body;
let pushQuery = {};
let pullQuery = {};
//remove id from set query
delete setQuery.id;
//if there is a comment
if(req.body.comment){
pushQuery.comments = req.body.comment
}
//if I need to remove a comment
if(req.body.remove_comment){
pullQuery.comments = {_id: req.body.remove_comment.id}
}
//Push new status into array
if(req.body.status) {
pushQuery.status = {
label: req.body.status,
user: req.user._id,
date: new Date()
};
delete setQuery.status;
}
//update step
await Step.findByIdAndUpdate(req.body.id, {$set: setQuery, $push: pushQuery, $pull: pushQuery})
.then(step => {
if(!step){
errors.noflow = "There was a problem updating the step";
return res.status(400).json(errors);
}
res.json(step)
})
.catch(err => {
console.log(err);
res.status(404).json(err);
});
});
I've been getting the following error when trying to push a new status into my document:
operationTime: Timestamp { bsontype: 'Timestamp', low: 1, high_:
1560978288 }, ok: 0, errmsg: 'Updating the path \'status\' would
create a conflict at \'status\'', code: 40, codeName:
'ConflictingUpdateOperators', '$clusterTime': { clusterTime:
Timestamp { bsontype: 'Timestamp', low: 1, high_: 1560978288 },
signature: { hash: [Object], keyId: [Object] } },
Oh, you're doing that $set and $push on a status. Your pushQuery is trying to have status be an array on the document, and your setQuery wants to set it to whatever it was on the actual body (I'm guessing the same object.
A quickfix would be to remove it from the set object:
delete setQuery.status
A reasonable and stable way to do this would be to actually only take the things from req.body which you really want for each of the stages. Example:
const { position, name, dueDate, status, comment, remove_comment } = req.body;
const setQuery = { position, name, dueDate };
const pushQuery = { status, comments: comment };
// ...
That way your queries are not conflicting in any way.

Sequelize Many to Many Query Issue

So, I have an existing MySQL database that I'm trying to connect to with Sequelize in Node that has a products table, a categories table and a categories_products table. What I want to do is return products, with each product containing all of the categories it belongs to. Here's what I've got:
// Declare Product Model
const Product = sequelize.define('products', {
name: Sequelize.STRING,
description: Sequelize.STRING,
single_price: Sequelize.BOOLEAN,
oz_price: Sequelize.FLOAT,
half_price: Sequelize.FLOAT,
quarter_price: Sequelize.FLOAT,
eigth_price: Sequelize.FLOAT,
gram_price: Sequelize.FLOAT,
unit_price: Sequelize.FLOAT
},
{
underscored: true
});
// Declare Category Model
const Category = sequelize.define('categories', {
name: Sequelize.STRING,
parent_id: Sequelize.INTEGER,
picture_file_name: Sequelize.STRING
},
{
underscored: true
});
// Join Table
const ProductCategory = sequelize.define('categories_products', {
product_id: Sequelize.INTEGER,
category_id: Sequelize.INTEGER,
}, {
timestamps: false,
underscored: true
});
// Do this because there is no id column on ProductCategory table
ProductCategory.removeAttribute('id');
Category.hasMany(Category, { as: 'children', foreignKey: 'parent_id' });
ProductCategory.belongsTo(Product);
ProductCategory.belongsTo(Category);
Product.hasMany(ProductCategory);
Category.hasMany(ProductCategory);
Using this setup, I query as follows:
Product.findAll({
include: [{
model: ProductCategory,
include: [ Category ]
}],
where: { active: true },
limit: 10
}).then(prods => {
res.send(prods);
}).catch(err => {
res.status(500).send(err);
});
I get back my products and each one has an array of categories, BUT each product only shows a max of one category. I have products that should have many categories, but it only shows the first.
Am I missing something? Any help would be greatly appreciated.
I think you should use belongsToMany association here.
You can define association like this
Product.belongsToMany(Category, { through: ProductCategory, foreignKey: 'product_id' });
Category.belongsToMany(Product, { through: ProductCategory, foreignKey: 'category_id' });
and the query can be
Product.findAll({
include: [Category]
}).then((res) => {
console.log(res);
})
Though the questioner might have gotten the solution but I ran into this composite key table problem and this is the solution with code example. Notice the "through" keyword. That is what solves the association where you want to limit your findings to say a category as AbhinavD asked above. Your category id would go in the literal expression. Applies to findAll too.
const products = await Product.findAndCountAll({
include: [Category],
through: { where: { category_id: `${category_id}` } },
attributes: [
'product_id',
'name',
],
limit: limitPage,
offset: offsett,
});

How to insert all values from another table using sequelize

//table main_lookup
const main_lookup=DB.connection.define('main_lookup',{
main_lookup_name: {
type :Sequelize.STRING,
primaryKey: true,
allowNull: false
},
value:{
type:Sequelize.JSON,
allowNull:false,
}
});
//table 2
const school_lookup= DB.connection.define('school_lookup',{
school_id : {
type :Sequelize.STRING,
allowNull: false,
references: {
model: schools,
key: 'school_id'
}
},
lookup_name: {
type :Sequelize.STRING,
unique: true,
allowNull: false
},
value: {
type: Sequelize.JSON,
allowNull: false,
}
});
i need to send data from main_lookup table to school lookup data
school_id // that is given by me
lookup_name // that is copy from main_lookup
value // that is copy from main_look_up
example
main lookup // table1
main_lookup_name value
---------------- -----
language ['english','tamil']
subject ['social','maths']
the solution is similar to the content following this
school_lookup // table2 //needed
school_id lookup value
--------- ------ -----
cit language ['english','tamil']
cit subject ['social','maths']
i need help in sequelize with simple ways
You can use raw query to do the same.
var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password');
sequelize.query("insert into <table1> select * from <table2>", {
type:Sequelize.QueryTypes.SELECT
}).then(function(results) {
console.log(results) // or do whatever you want
})
I think you are using sequelize like how people use microsoft excel or microsoft access. You can simply make relation in sequelize.
here is the code
//school-model
const School=Sequelize.define('school',{
name: {
type :Sequelize.STRING,
allowNull: false
}
});
//subject
const Subject= Sequelize.define('subject',{
type: {
type :Sequelize.STRING,
unique: true,
allowNull: false
},
value: {
type: Sequelize.ARRAY,
allowNull: false,
}
});
//database
const School = require('./path_to_schoolmodel');
const Subject = require('./path_to_subjectmodel');
Subject.belongsTo(School);
School.hasMany(Subject);
School model
id name
-- ---------------------
1 'fullstack academy'
2 'app academy'
Subject model
id type value schoolID
-- --------- -------------------- ------------------
1 language ['english','tamil'] 2
2 subject ['social','maths'] 1
if you setup model relationship you should be able to see you data like this.
Then when you use Sequelize model querying on your server side.
const School = require('./path_to_schoolmodel');
const Subject = require('./path_to_subjectmodel');
School.findAll({
include:[ Subject ]
})
.then(console.log)
.catch(console.error)
your console.log should return something like
[
{
id: 1,
name: 'fullstack academy'
subjects: [{
id: 2,
type: 'subject',
value: [
'socials', 'maths'
]
}]
},
{
id: 2,
name: 'app academy'
subjects: [{
id: 1,
type: 'language',
value: [
'english', 'tamil'
]
}]
},
]

Sequelize WHERE NOT EXISTS()

I have a many to many relations that looks like this:
var Genres = db.define('Movie', {
name: {
type: Sequelize.STRING(100),
allowNull: false
},
description: {
type:Sequelize.STRING(),
allowNull: true
},
thumbnail: {
type: Sequelize.BLOB(),
allowNull: true
},
urlposter: {
type: Sequelize.STRING(245),
allowNull: true
}
});
var Users = db.define('User', {
username: {
type: Sequelize.STRING(25),
allowNull: false
},
password: {
type: Sequelize.STRING(25),
allowNull: false
}
});
Movies.belongsToMany(Users, {through: UM, foreignKey: 'Id_Movies'});
Users.belongsToMany(Movies, {through: UM, foreignKey: 'Id_Users'});
what I will do is return all Movies that have no link to a specific user
this is my SQL query i want to achive:
SELECT m.*
FROM Movies m
WHERE NOT EXISTS(SELECT NULL
FROM Users_Movies sc
WHERE sc.Id_Movies = m.id AND sc.Id_Users = 1)
This is the closest I've got but this just return all movies that have a link u user with ID 1
Movies.findAll({
include: [
// {
// model:Genres,
// through:{attributes:[]}
// },
{
model:Users,
through:{attributes:[]},
where: {id: 1},
required: true,
}],
})
.then(function(movies){
done(null, movies);
})
.catch(function(error){
done(error, null);
});
But I want to invert it.
right now my only option is to make 2 queries one with all movies and one with all movies that have a link and loop through the list and remove them from the first list.. this is not pretty code.
I know that I'm late on the matter, but I think that using the literal 'users.id IS NULL' in the query's 'where' will do the trick:
Movie.findAll({
where: sequelize.literal("users.id IS NULL"),
include: [
{
model: Users,
through: { attributes: [] }
}],
})
.then(function (movies) {
done(null, movies);
})
.catch(function (error) {
done(error, null);
});
This solutions is based on the following Github issue: https://github.com/sequelize/sequelize/issues/4099
Use rawQuery
const projects = await sequelize.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
});

Categories