在 sequelize v6 中包含模型的问题排序 [已解决] - 这不是 sequelize 错误
Posted
技术标签:
【中文标题】在 sequelize v6 中包含模型的问题排序 [已解决] - 这不是 sequelize 错误【英文标题】:Problem ordering with including model in sequelize v6 [Solved] - It wasn't sequelize error 【发布时间】:2021-03-22 15:37:28 【问题描述】:我正在尝试将 order 子句添加到方法函数,但出现此未知列错误。
Error: Unknown column 'Product.createdAt' in 'order clause'
于是我查阅了SQL,发现问题是顺序子句中的列名 生成。
我将非常感谢任何解决此问题的想法。
系统
Windows 10 nodejs v13.11.0 续集 [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;
这个 SQL 工作正常(我想要生成的)
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product.rating` ASC,
`ViewItem`.`createdAt` DESC;
我试图解决这个问题。
// 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
我是 有解决此错误的想法吗?
已添加型号代码
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】:您能否尝试以下命令续集文字:
req.filter.order.push([sequelize.literal(`"Product.rating"`), 'desc'])
// OR
req.filter.order.push([sequelize.literal("`Product.rating`"), 'desc'])
【讨论】:
“Procut.rating
”和"Product.rating"
对我都不起作用。两者都为我的订单子句生成“ ``.DESC ”。在 order 子句中添加文字查询的任何其他解决方案?如果是这样,我应该通过查询界面查询是我解决这个问题的唯一选择吗?
对不起兄弟。我的问题不在于 sequelize 本身,而在于我的实例方法。我在查询之前复制了选项对象,以防止选项对象发生突变,使用 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 Shah【参考方案2】:
我的问题不在于 sequelize 本身,而在于我的实例方法。
我在查询之前复制了选项对象,以防止选项对象的突变,使用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 查询生成器内部,该过程无法匹配我的文字查询,这导致我的查询失败。
同时,在为option.order
参数传递数组时,我仍然无法解决 order 子句中的未知列名错误。我简要地查看了abstract/query-generator.js
,并获得了一些解决此问题的见解。在getQueryOrders()
生成订单子句,如果订单查询生成器引用选定的列名,那会比现在更好。
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?
【讨论】:
如果你仍然想克隆对象,你可以尝试使用 npm rfdc - npmjs.com/package/rfdc。文档说它还克隆了原型和属性。我自己没有尝试过,但值得一试。 我认为克隆对象仍然有用,因为它可以防止改变原始对象。听起来我可以用那个。感谢您让我知道@AbhishekShah以上是关于在 sequelize v6 中包含模型的问题排序 [已解决] - 这不是 sequelize 错误的主要内容,如果未能解决你的问题,请参考以下文章
Sequelize:在 findAll 中包含连接表属性包括