Sequelize ordering bug in multi join - mysql

I have Users table, that related to many Stations and each user has relation with Info table, that contains fields firstName and lastName. I grab it like this:
const joinUserInfo: any = {
model: userInfoModel,
where: { deletedAt: null },
required: true,
};
const joinStations = {
model: stationModel,
required: true,
};
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
});
Than I have a method to build order. It looks like this:
private createAdminsListOrdering(query) {
const validOrderingItems = [];
if (query.fullName) {
validOrderingItems.push(['info', 'lastName', query.fullName]);
validOrderingItems.push(['info', 'firstName', query.fullName]);
}
if (query.email) {
validOrderingItems.push(['email', query.email]);
}
if (query.station) {
validOrderingItems.push(['stations', 'name', query.station]);
}
return validOrderingItems.push(['createdAt', 'DESC']);
}
In cases with email or stations all works correct. But with info it say that didn't see and info column. When I remove stations from join like this:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo],
paranoid: false,
order,
});
My ordering by firstName and lastName working correct. What i do wrong? Thank you.
Users related with Stations as many-to-many:
#BelongsToMany(
() => stationModel,
() => userStationModel,
)
stations?: stationModel[];

The problem was in pagination... I use custom function to create pagination:
export const paginatedQuery = ({ page = 0, pageSize = 0 }) => {
if (!pageSize) {
return {};
}
const limit = pageSize;
const offset = page * pageSize;
return { offset, limit };
};
And full my request was:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
});
And, when I debug request with logging i notice, that my Info table is in subquery. Then I began to googling about subqueries in sequelize and found flag subQuery, that force to not use subqueries. After this my request works fine:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
subQuery: false,
});
Hope it will helps someone.

Related

Is it possible to get relations with take and skip

I have an entity named: UserEntity.And i have relations here.Following/Followers.Image:
enter image description here
const user = await this.userRepository.findOne({
where: {
id: anotherUserId || userId,
// following: { isDeleted: false }
},
select: {
following: {
id: true,
firstName: true,
lastName: true,
about: true,
organizationName: true,
profileAvatar: true,
isDeleted: true
}
},
relations: {
following: true,
}
})
also i have query where i get relations.How can i take a certain amount?(f.e take 5 followers)Is it possible?Thank u
You should use sub-query for this purpose.
const user = await this.userRepository
.createQueryBuilder('user')
.where({ id: anotherUserId || userId })
.leftJoinAndSelect(
qb => qb
.select()
.from(YourUserRelationTable, 'r')
.where({ follower: userId })
.orderBy({ 'r.createdAt': 'DESC' })
.limit(5),
'followerRelation',
'followerRelation.id = user.id'
)
.getOne()

How do I find the most recent "created_date" from the Ticket table for each user?

I'm trying to set up a route where I can see the most recent "created_at" value for the user's cart. I will be using this information to set up a timer. Ex. If your cart is untouched for 15 minutes we empty it.
How can I query both the user and their highest value "created_at" from mySQL on the ticket table?
Here's what I've got so far:
router.get('/cart', async (req, res) => {
try {
const userData = await User.findByPk(req.session.user_id, {
attributes: {
exclude: ["password"]
},
include: [{model: Ticket,
include: [Seat, {model: Showing, include: Production}]
}],
});
const ticketData = await Ticket.findOne({
attributes: [
[sequelize.fn('MAX', sequelize.col('created_at'))]],
group: ['id'],
// raw: true,
});
const user = userData.get({ plain: true })
res.render('cart', {
...user,
logged_in: true,
})
} catch (err) {
res.status(500).json(err)
}
});

Problem ordering with including model in sequelize v6 [Solved] - It wasn't sequelize error

I'm trying to add order clause to method function but got this unknown column error.
Error: Unknown column 'Product.createdAt' in 'order clause'
So I looked up the SQL and found that the problem is column name in order clause that sequelize
generated.
I'll be very thankful for any idea to solve this problem.
System
windows 10
nodejs v13.11.0
sequelize [ CLI: 6.2.0, ORM: 6.3.4 ]
Router
...
const models = require('./models')
const { Product, ViewItem, Category, ... } = models
...
//Middleware apply the req.filter, generated filter object will be like this
const req = {
...req,
filter : {
order : [
[{ model : Product, as : 'Product'}, 'rating', 'desc'],
['createdAt', 'desc']
],
...
}
}
const products = await ViewItem.listAndApply({
subquery : false,
include: [
{ model : Brand, as : 'Brand', required: true },
{
model : Category,
as : 'Category',
required: true,
attributes : [ 'id', 'level', 'name' ],
where : req.filters.toJson({ namespace : 'Category' })
},
{ model : ProductImage, as : 'ProductImage', required: false },
{ model : ProductStatus, as : 'ProductStatus', required: false },
{ model : ProductType, as : 'ProductType', required: false },
{ model : ProductRating, as : 'ProductRating', required : true }
],
where : req.filters.toJson(),
limit : limit,
offset : offset,
order : req.filters.order
...
models/ViewItem.js
ViewItem.listAndApply = async function (options) {
const {
ProductView,
Product,
} = require('.')
const payload = lodash.cloneDeep(options)
let {
include,
} = payload
if (!include) include = []
.
.
.
const items = await ViewItem.findAll({
subquery: false,
include: [
.
.
.
{
model: Product,
as: 'Product',
required: true,
where: product.where,
include: include
}
],
where: where,
limit: limit,
order: order
})
//Please don't mind the applyView(), this function do nothing with sequelize level.
return items.map(item => applyView(item))
}
This SQL is broken (What I got from sequelize)
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product`.`rating` ASC,
`ViewItem`.`createdAt` DESC;
This SQL works fine (What I want to generate)
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product.rating` ASC,
`ViewItem`.`createdAt` DESC;
What I tried to solve this problem.
// SQL broken with syntax error, col name return is " ``. "
req.filter.order.push([sequelize.literal('Product.rating'), 'desc'])
// also broken with same error above
req.filter.order.push([sequelize.col('Product.rating'), 'desc'])
// same
I'm
Any idea for resolving this error?
Model Code added
models/Product.js
'use strict'
module.exports = (sequelize, DataTypes) => {
const Product = sequelize.define('Product', {
name: DataTypes.STRING,
sku: DataTypes.STRING,
supplier: DataTypes.STRING,
manufacturer: DataTypes.STRING,
thumbnail: DataTypes.STRING,
wholeSalePrice: DataTypes.INTEGER,
retailPrice: DataTypes.INTEGER,
originalPrice: DataTypes.INTEGER,
discount: DataTypes.FLOAT,
description: DataTypes.STRING,
domestic: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
underAge: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
display: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
views: {
type: DataTypes.INTEGER,
defaultValue: 0
},
reviews : {
type : DataTypes.INTEGER,
defaultValue : 0
},
jjim : {
type: DataTypes.INTEGER,
defaultValue: 0
},
sold: {
type: DataTypes.INTEGER,
defaultValue: 0
},
rating: {
type: DataTypes.FLOAT,
defaultValue: 0
}
}, { timestamps: true, paranoid: true })
Product.associate = function(models) {
// associations can be defined here
Product.belongsToMany(models.Category, { as: 'Category', through: 'ProductCategory', onDelete: 'CASCADE' })
Product.belongsToMany(models.ProductView, { as: 'ProductView', through: 'ProductViewMap', onDelete: 'CASCADE' })
Product.belongsTo(models.Brand, { as: 'Brand' })
Product.hasMany(models.ProductImage, { as: 'ProductImage', foreignKey: 'ProductId' , onDelete: 'CASCADE' })
Product.hasMany(models.ProductItem, { as: 'ProductItem', foreignKey: 'ProductId', onDelete: 'CASCADE' })
Product.belongsTo(models.ProductStatus, { as: 'ProductStatus' })
Product.belongsTo(models.ProductType, { as: 'ProductType' })
Product.hasOne(models.ProductRating, { foreignKey : 'ProductId', as : 'ProductRating' })
Product.hasMany(models.UserJjim, { as : 'Jjims'})
}
Product.afterCreate(async (product, option) =>{
const {
sequelize,
Product,
ProductViewMap,
ViewItem,
ProductRating
} = require('.')
const { id, name, thumbnail } = product
const DEFAULT_VIEW = 1
// await ProductViewMap.create({ ProductId : id, ProductViewId : DEFAULT_VIEW })
const item = await ViewItem.create({ ProductId : id, ProductViewId : DEFAULT_VIEW })
console.log(item)
await ProductRating.create({ ProductId : id })
})
Product.prototype.findActiveItem = async function(){
const { ViewItem, View } = require('.')
return ViewItem.findOne({
subquery : false,
include : [
{ model : View, as : 'View', required : true }
],
where : {
display : true,
ProductId : this.id
}
})
}
Product.prototype.findActiveView = async function(){
const { ViewItem, View } = require('.')
return View.findOne({
subquery: false,
include : [
{
model : View,
as : 'View',
required : true,
include : [
{
model : ViewItem,
as : 'ViewItem',
required : true,
where : {
display : true
}
}
]
}
]
})
}
return Product
}
models/ViewItem.js
'use strict'
const lodash = require('lodash')
module.exports = (sequelize, DataTypes) => {
const ViewItem = sequelize.define('ViewItem', {
name: DataTypes.STRING,
thumbnail: DataTypes.STRING,
retailPrice: DataTypes.INTEGER,
discount: DataTypes.FLOAT,
staticDiscount: DataTypes.INTEGER,
description: DataTypes.STRING,
promotion: {
type: DataTypes.JSON,
defaultValue: null
},
position: DataTypes.INTEGER,
display: DataTypes.BOOLEAN
}, {
timestamps: true,
paranoid: true
})
ViewItem.associate = function (models) {
// associations can be defined here
ViewItem.belongsTo(models.ProductView, {
as: 'ProductView',
foreignKey: 'ProductViewId',
onDelete: 'CASCADE'
})
ViewItem.belongsTo(models.Product, {
as: 'Product',
onDelete: 'CASCADE'
})
}
/* Hook */
ViewItem.afterCreate(async function (item) {
const {
Product,
ProductViewMap
} = require('.')
const {
id,
ProductId,
ProductViewId
} = item
/* Join table에 row를 추가합니다 */
await ProductViewMap.create({
ProductId,
ProductViewId
})
/* View 아이템중에 같은 Product를 가르키는 상품의 노출 설정을 false로 합니다. */
const product = await Product.findOne({
where: {
id: ProductId
}
})
await ViewItem.update({
display: false,
}, {
where: {
ProductId: ProductId
}
})
/* 생성되는 ViewItem의 초기 display 속성은 Product의 display를 따릅니다. */
await this.update({
display: product.display
}, {
where: {
id: id
}
})
})
ViewItem.listAndApply = async function (options) {
const {
ProductView,
Product,
} = require('.')
const payload = lodash.cloneDeep(options)
let {
include,
where,
order,
limit,
offset,
product
} = payload
if (!include) include = []
if (!product) product = {}
where = where ? where : null
order = order ? order : null
limit = limit ? limit : null
offset = offset ? offset : null
product.where = product.where ? product.where : null
const items = await ViewItem.findAll({
subquery: false,
include: [{
model: ProductView,
as: 'ProductView',
required: true
},
{
model: Product,
as: 'Product',
required: true,
where: product.where,
include: include
}
],
where: where,
limit: limit,
order: order
})
return items.map(item => applyView(item))
}
return ViewItem
}
/* private function */
const applyView = (item) => {
if (!item) return null
if (!item.Product) return null
if (!item.ProductView) return null
const product = lodash.cloneDeep(item.Product.toJSON())
const view = lodash.cloneDeep(item.ProductView.toJSON())
/*
View를 적용합니다.
#1. 가격정보를 수정합니다.
#2. 스티커와 태그를 추가합니다.
#3. 상세페이지 url을 추가합니다.
*/
if (view.additionalDiscount) {
const discount = product.discount + view.additionalDiscount
const retailPrice = Math.floor(product.originalPrice * (1 - discount / 100))
product.discount = discount
product.retailPrice = retailPrice
} else if (view.discount) {
const retailPrice = Math.floor(product.retailPrice * (1 - view.discount / 100))
const discount = Math.floor((1 - retailPrice / product.originalPrice) * 100)
product.retailPrice = retailPrice
product.discount = discount
}
if (view.staticDiscount) {
const retailPrice = product.retailPrice - view.staticDiscount
const discount = Math.floor((1 - retailPrice / product.originalPrice) * 100)
product.retailPrice = retailPrice
product.discount = discount
}
if (view.tag) product.tag = view.tag
if (view.sticker) product.sticker = sticker
product.detailUrl = view.detailUrl ? `${view.detailUrl}/${item.id}` : `/products/${item.id}`
/*
ViewItem
#1. 상품정보를 수정합니다.
#2. 가격정보를 수정합니다.
#3. retailPrice가 있다면 기존 계산값을 무시하고 이 값을 사용합니다.
*/
if (item.name) product.name = item.name
if (item.thumbnail) product.thumbnail = item.thumbnail
if (item.description) product.description = item.description
if (item.discount) {}
if (item.staticDiscount) {}
if (item.retailPrice) product.retailPrice = item.retailPrice
return ({
...product,
ProductView: view,
id: item.id,
ProductId: item.ProductId,
ProductViewId: item.ProductViewId
})
}
Full code is about 500 lines, I reduced some unrelacted part for readability.
Other models are not related with order clause.
Can you please try below for order sequelize literal:
req.filter.order.push([sequelize.literal(`"Product.rating"`), 'desc'])
// OR
req.filter.order.push([sequelize.literal("`Product.rating`"), 'desc'])
My problem wasn't in sequelize itself but my instance method.
I copied option object before querying to prevent mutation of option object, using lodash.cloneDeep(). But it turns out to that even deep clone can't copy the prototype chain.
const payload = lodash.cloneDeep(options)//I massed up in here
I can demonstrate it by executing code below.
const sequelize = require('sequelize')
const lodash = require('lodash')
const orginal = sequelize.literal('Product.rating')
const copied = lodash.cloneDeep(original)
console.log(original instanceof sequelize.Utils.SequelizeMethod )//true
console.log(copied instanceof sequelize.Utils.SequelizeMethod)//false
That is the reason why I got
Error: Order must be type of array or instance of a valid sequelize method.
Because copied object has no prototype chain pointing SequelizeMethod, inside of sequlieze query generator, the process failed to match my literal query and it leaded for me to getting broken query.
In the meantime, I still can't resolve unknown column name error in order clause when passing array for option.order argument. I took a look in abstract/query-generator.js briefly, and got some insight to resolve this problem. Generating order clause in getQueryOrders(), it would be even better than now, if order query generator refer the selected column name.
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
#this column name should be equal to order clause's column name
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product`.`rating` ASC,#otherwise, it invokes unknow column name error.
`ViewItem`.`createdAt` DESC;
# Then how about referring the selected column name in the where and order clause
# instead of managing each clause loosely?

Sequelize Error: Include unexpected. Element has to be either a Model, an Association or an object

I am receiving the error when I make a call to my API with a get request:
Include unexpected. Element has to be either a Model, an Association or an object.
My Models look like this:
module.exports = (sequelize, Sequelize) => {
const Productions = sequelize.define("productions", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
setupTime: {
type: Sequelize.DECIMAL(6, 3)
},
notes: {
type: Sequelize.TEXT
}
}, { timestamps: false });
return Productions;
};
module.exports = (sequelize, Sequelize) => {
const ProductionPrints = sequelize.define("productionPrints", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
compDate: {
type: Sequelize.DATE
}
}, { timestamps: false });
return ProductionPrints;
};
The relationship between the models is defined here:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
db.productions.hasOne(db.productionprints, {
foreignKey: {
name: 'productionId',
allowNull: false
}
});
db.productionprints.belongsTo(db.productions, { foreignKey: 'productionId' });
And the sequelize query looks as so:
const db = require("../models");
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
exports.findAll = (req, res) => {
Productions.findAll({
include: [ { model: ProductionPrints, as: 'prints' } ]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while finding the productions."
});
});
};
I have checked around for others with the issue but have had no avail with any solutions posted on those problems. Generally it was caused by typos, or error in the require paths. I have checked those and all my other includes work, just not on any of the models I include on the productions model.
Any feedback is greatly appreciated.
Error was being caused by a typo:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
when this was being referenced in the assigned to a constant:
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
shame on me for changing my case use:
db.productionprints != db.productionPrints
I had the same issue , this is usually caused by naming issue , to track the issue you can check one of the following places to resolve it
check if you are calling the correct model class name
when importing models becarefull not to call the file name instead of model name => the one exported
3.check if you got your association correctly by calling the exported model not the file name
check if your cases e.g users vs Users.
a bonus tip is to use same name for model and file name to avoid these issues because the moment you make them different you likely to make these mistakes
Following the answer of Kelvin Nyadzayo, i have the model.findOne(options) method with a
options.include like this:include: [ { } ] in the options parameter
The include has to have the proper syntax: [{model: Model, as: 'assciationName'}]
And the mine was empty
So this, was triggering the same error

Select parent model with child model attribute and destroy it - sequelize/MySQL

I have User and VerifyToken model with token and user_id attributes.
I need to select an user, but with token (from tokens table) value.
I have tried:
await models.User.destroy({
hierarchy: true,
where: {
token: token
}
});
This is User model:
const VerifyToken = require('./verifyToken');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
password: DataTypes.STRING,
email: DataTypes.STRING,
}, {
tableName: 'users',
//syncOnAssociation: true
hierarchy: true
});
User.associate = function (models) {
const {VerifyToken} = models;
User.hasOne(VerifyToken, {
onDelete: 'CASCADE'
});
};
return User;
};
And VerifyToken model:
const User = require('./user');
module.exports = (sequelize, DataTypes) => {
const VerifyToken = sequelize.define('VerifyToken', {
user_id: DataTypes.INTEGER,
token: DataTypes.STRING,
}, {
tableName: 'verify_tokens',
syncOnAssociation: true,
hierarchy: true
});
VerifyToken.associations = function (models) {
const {User} = models;
VerifyToken.belongsTo(User);
};
return VerifyToken;
};
The problem is that, I even don't know where to start. I have tried with include:[{model: models.VerifyToken, where: {}}], but how to use user_id called from the child model ?
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.
The problem statement you want is to support join and delete in one sequelize operation.
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.
In sequelize documenation, Model.destroy has no include in the options property.
So the only left option is to select the user_id's from VerifyToken model, then call destroy on User model, where id in user_id got from VerifyToken.
In code it will look like following
const verifyTokens = await VerifyToken.findAll({
where: {
token: {
[Sequelize.Op.In] : YOUR_TOKENS_FOR_WHICH_YOU_WANT_TO_DELETE_YOUR_USER
}
}
}
const userIdsToDestroy = verifyTokens.map(verifyToken => verifyToken.user_id)
await User.destroy({
where: {
id: {
[Sequelize.Op.in] : userIdsToDestroy
}
}
}