在 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.SequelizeMethodcopied 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 中包含连接表属性包括

从多个模型中包含、选择、排序、限制(单个查询)

Sequelize - 自我引用有很多关系

按关联模型排序时,Sequelize 抛出错误“无法找到模型 x 的有效关联”

如何在sequelize中对嵌套模型中的列进行排序?

Sequelize V5->V6 升级:Typescript 错误属性 'x' 在类型 'y' 上不存在