返回

Sequelize Unknown Column 错误处理:不报错返回 NULL

mysql

Sequelize 中 "Unknown column" 错误的处理方法:避免报错并返回 NULL

遇到 Sequelize 报 "Unknown column" 错误,通常是因为数据库表里少了某个字段。开发环境报这个错挺正常的,但在生产环境,我们更希望程序能稳点,别直接崩溃,最好是找不到字段就返回个 NULL。

问题原因:字段缺失

问题很直接:Sequelize 查询时用到的某个字段,在数据库表里找不到了。这可能是有人不小心删了字段,或者数据库迁移出了岔子。

拿咱们碰到的这个情况来说:

SELECT `ID`, `free_delivery_sum`, `other_field` FROM `s4y_fillials`;

free_delivery_sum 字段没了,Sequelize 找不到,就报 "Unknown column 'free_delivery_sum' in 'field list'"。

我们想要的结果是,即使 free_delivery_sum 没了,也别报错,返回个像这样的东西:{ID:1,free_delivery_sum:null,other_field:"some_data"}

解决方案:不改数据库,搞定问题

接下来,咱们不用去数据库里把字段加回去,也能解决这个问题。下面有几个办法,根据实际情况选用:

1. 使用 attributes 选项 (推荐)

这是个好办法,直接告诉 Sequelize 你要查哪些字段。就算数据库里有多的字段,或者少了某个字段,Sequelize 都不会管,只按你说的来。

原理:

Sequelize 的 findAll 方法(以及其他查询方法)有个 attributes 选项,可以让你精确控制要查哪些字段。

代码示例:

const FilialDBModel = require('./models/filial'); // 假设这是你的模型

async function getFillials() {
  try {
    const fillials = await FilialDBModel.findAll({
      attributes: ['ID', 'other_field'] // 只查这两个字段
    });
    console.log(fillials);
      //如果 free_delivery_sum 不存在也正常运行,不会报错。
      // fillials 里的每个对象都只有 ID 和 other_field 这两个属性
  } catch (error) {
    console.error("出错了:", error);
  }
}

getFillials();

进阶使用技巧:

  • 包含和排除: attributes 不光能指定要哪些字段,还能指定不要哪些字段。

     attributes: { exclude: ['free_delivery_sum'] }
     // 排除 'free_delivery_sum'
    

或者
javascript const { QueryTypes } = require('sequelize'); //... const fillials = await sequelize.query("SELECT ID,other_field FROM s4y_fillials", { type: QueryTypes.SELECT }); //...

  • 别名: 还可以给字段起个别名。

    attributes: ['ID', ['other_field', 'renamed_field']]
      //把 other_field 重命名为 renamed_field
    
  • raw: true结合使用

    如果使用raw: true,可以获得更纯粹的数据,减少ORM带来的额外开销.

     const fillials = await FilialDBModel.findAll({
       attributes: ['ID', 'other_field'],
       raw: true
     });
    

安全建议:

明确指定要查询的字段,能减少暴露敏感数据的风险。

2. 使用 try...catch 捕获错误

这个方法比较直接,就是在代码里把可能出错的部分包起来,出错的时候做点特殊处理。

原理:

try...catch 包住 Sequelize 查询,如果查询出错(比如碰到 "Unknown column"),就在 catch 里处理错误,返回一个你想要的结果。

代码示例:

const FilialDBModel = require('./models/filial');

async function getFillials() {
  try {
    const fillials = await FilialDBModel.findAll();
    console.log(fillials);
  } catch (error) {
      if (error.name === 'SequelizeDatabaseError' && error.message.includes('Unknown column')) {
          // 如果是 "Unknown column" 错误
           const defaultValues = { ID: null, free_delivery_sum: null, other_field: null }; //可以定义默认
            const manuallyFetchedData = await FilialDBModel.findAll({
                attributes: ['ID','other_field'], //用其他方法来替代,这样可以保留代码原本的结构。
              raw:true,
            });

          const result = manuallyFetchedData.map(item=>({...defaultValues,...item}));

           console.log("遇到'Unknown column'错误,返回:",result);
           return result;

      } else {
      console.error("其他错误:", error);
    }
  }
}

getFillials();

进阶使用
可以进一步的将错误捕获,做成一个通用的工具函数,进行集中处理:

async function safeSequelizeQuery(model, queryOptions, defaultValues) {
 try {
   return await model.findAll(queryOptions);
 } catch (error) {
   if (error.name === 'SequelizeDatabaseError' && error.message.includes('Unknown column')) {

      if(queryOptions.attributes && queryOptions.attributes.exclude)
      {
        //根据配置获取存在的
       const realAttributes = queryOptions.attributes.exclude.filter(attribute=> !error.message.includes(attribute) );

        const manuallyFetchedData = await model.findAll({
           attributes: {exclude:realAttributes},
           raw:true,
         });
        const result = manuallyFetchedData.map(item=>({...defaultValues,...item}));
       return  result;

      }else{
         const manuallyFetchedData = await model.findAll({
           attributes:  Object.keys(defaultValues),
           raw:true,
         });
        const result = manuallyFetchedData.map(item=>({...defaultValues,...item}));
         return result;
      }
   }
   throw error; // 其他错误, 抛出去
 }
}

// 用法示例
//假设defaultValues包含了所有数据库model的可能出现的字段。
const defaultValues = { ID: null, free_delivery_sum: null, other_field: null };

async function getFillials(){

const result = await safeSequelizeQuery(FilialDBModel, {}, defaultValues); //所有列
console.log(result);
}

async function getFillials2(){

const result = await safeSequelizeQuery(FilialDBModel, {attributes: { exclude: ['free_delivery_sum'] }}, defaultValues); //排除 free_delivery_sum
 console.log(result);

}

安全建议:

错误处理要细致,不同的错误类型可能有不同的处理方式。另外,对于未知异常,最好往上抛出。

3. 使用虚拟字段(Computed Fields/ Virtual Fields)

Sequelize 允许定义虚拟字段。这些字段不在数据库里,而是在你获取数据的时候,通过一些计算得出来的。

原理:
在模型定义中使用 DataTypes.VIRTUAL 来定义一个虚拟字段。定义时可以指定一个 get 方法,该方法会在查询后,被调用。我们可以在 get方法里对获取结果做一些操作。

代码:

// 在你的 FilialDBModel 模型定义里
const FilialDBModel = sequelize.define('s4y_fillials', {
  ID: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  other_field: {
    type: DataTypes.STRING,
    allowNull: true
  },
  free_delivery_sum: {
     type: DataTypes.VIRTUAL,
        get() {
           return null; // 直接返回 null
        },
       set(value) {
         throw new Error('不要尝试设置虚拟字段!');
        }
   }

},{
    timestamps:false //根据需添加
});

进阶用法

  1. 可以在虚拟字段里添加逻辑,让他能动态适应不同情况
        free_delivery_sum: {
            type: DataTypes.VIRTUAL,
            get() {
                if (this.getDataValue('some_other_field') === 'some_value') {
                  return this.getDataValue('another_field');  //根据不同条件做处理。
                }
               return null;
              }
         }
    

安全建议:
虚拟字段不会存储在数据库,不会有数据泄露问题。

这几种方法,各有各的好处,在不改动数据库的情况下,都能解决 “Unknown Column” 错误,让你在生产环境下不报错,而是返回 NULL。 方法一因为更符合数据库操作习惯,更推荐。