#mysql #node.js #sequelize.js
#mysql #node.js #sequelize.js
Вопрос:
Я пытаюсь добавить предложение order в функцию метода, но получил эту неизвестную ошибку столбца.
Error: Unknown column 'Product.createdAt' in 'order clause'
Итак, я просмотрел SQL и обнаружил, что проблема заключается в имени столбца в предложении order, сгенерированном sequelize.
Я буду очень благодарен за любую идею по решению этой проблемы.
Система
- windows 10
- nodejs v13.11.0
- sequelize [CLI: 6.2.0, ORM: 6.3.4]
Маршрутизатор
...
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))
}
Этот SQL сломан (то, что я получил от 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
})
}
Полный код составляет около 500 строк, я сократил некоторые несвязанные части для удобства чтения.
Другие модели не связаны с предложением order.
Комментарии:
1. Пожалуйста, поделитесь кодом миграции, если вы используете, и, пожалуйста, поделитесь кодом модели.
Ответ №1:
Не могли бы вы попробовать ниже, чтобы упорядочить литерал sequelize:
req.filter.order.push([sequelize.literal(`"Product.rating"`), 'desc'])
// OR
req.filter.order.push([sequelize.literal("`Product.rating`"), 'desc'])
Комментарии:
1. Оба »
Procut.rating
» и"Product.rating"
не работает для меня. оба производят » `. DESC » для моего предложения order. Любое другое решение для добавления буквального запроса в предложение order? если да, должен ли я запрашивать через интерфейс запросов, это мой единственный вариант для решения этой проблемы?2. Извини, брат. Моя проблема была не в самом sequelize, а в моем методе экземпляра. Я скопировал объект option перед запросом, чтобы предотвратить мутацию объекта option, используя Lodash.deepClone . Но оказывается, что даже deepClone не может скопировать цепочку прототипов. Я проверил это с
sequelize.literal('Product.rating') instanceof sequelize.Utils.SequelizeMethod
помощью иcopied instaceof sequelize.Utils.SequelizeMethod
. Это причина, по которой я получилError: Order must be type of array or instance of a valid sequelize method.
. Спасибо за ваши усилия @Abishek Shah3. для меня это сработало для упорядочения по включенному полю модели.
Ответ №2:
Моя проблема была не в самом sequelize, а в моем методе экземпляра. Я скопировал объект option перед запросом, чтобы предотвратить мутацию объекта option, используя lodash.cloneDeep()
. Но оказывается, что даже глубокий клон не может скопировать цепочку прототипов.
const payload = lodash.cloneDeep(options)//I massed up in here
Я могу продемонстрировать это, выполнив приведенный ниже код.
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
Именно по этой причине я получил
Error: Order must be type of array or instance of a valid sequelize method.
Поскольку copied
объект не имеет указания цепочки прототипов SequelizeMethod
внутри генератора запросов sequlieze, процесс не смог соответствовать моему буквальному запросу, и это привело к тому, что я получил неработающий запрос.
В то же время я все еще не могу разрешить ошибку неизвестного имени столбца в предложении order при передаче массива для option.order
аргумента. Я abstract/query-generator.js
кратко взглянул и получил некоторое представление о решении этой проблемы. Генерация предложения order в getQueryOrders()
, было бы даже лучше, чем сейчас, если генератор запросов order ссылается на выбранное имя столбца.
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?
Комментарии:
1. Если вы все еще хотите клонировать объект, вы можете попробовать использовать npm rfdc — npmjs.com/package/rfdc . В документации говорится, что он также клонирует prototype amp; properties. Я сам не пробовал, но это может стоить попробовать.
2. Я думаю, что объект клонирования все еще будет полезен, поскольку он может предотвратить изменение исходного объекта. Звучит так, что я мог бы это использовать. Спасибо, что дали мне знать @AbhishekShah