Sequelize查询返回行数与生成SQL不一致问题求助
问题描述
定义了如下Sequelize模型:
import { DataTypes, Model } from 'sequelize'; class A extends Model{} A.init({ name: DataTypes.STRING, start_date: DataTypes.DATE, end_date: DataTypes.DATE, deleted: DataTypes.BOOLEAN }) class B extends Model {} B.init({ a_id: DataTypes.INTEGER, text: DataTypes.STRING, created_at: DataTypes.STRING, updated_at: DataTypes.DATE, }) B.belongsTo(A); A.hasMany(B);
执行查询获取前50条关联B的A表记录(左外连接)时,Sequelize仅返回49条数据,但将logging输出的SQL在DBeaver中执行能得到50条结果。
查询代码:
const params = { limit: 50, offset: 0, order: [ [ 'id', 'desc']], attributes: [ 'id', 'name', 'start_date', 'end_date', ], subQuery: false, where: { deleted: false }, include: [{model: B}], logging: console.log } const results = await A.findAll(params); // results长度为49
生成的SQL语句(简化后):
SELECT * FROM "A" LEFT OUTER JOIN B ON "A"."id" = "B"."a_id" WHERE "A"."deleted" = false ORDER BY "A"."id" DESC LIMIT 50 OFFSET 0;
额外信息:
- 移除
include参数后,Sequelize能返回全部50条A表记录 - limit设为25时返回25条,设为100时返回99条,但SQL执行均返回对应数量
- A表约1000条记录,与B表为一对多关系,仅1条A表记录无关联B表数据
- 清空B表后问题依旧
更新信息:
- 结果打印代码:
const results = await this.A.findAll(findParams); console.log('Final results: ', results.length);
- 清空B表后所有A表记录均能正常显示
原因分析与解决方案
核心原因
用LEFT JOIN加LIMIT时,Sequelize会把同一A记录关联的多条B记录合并成一个A实例。但如果LIMIT截断的刚好是某条A记录关联的最后一条B行,Sequelize会判定这条A记录没被查询到,最终返回的实例数就比SQL结果少1。另外subQuery: false会让Sequelize直接在JOIN后的结果上做LIMIT,而不是先筛选A的ID,这会放大问题——因为JOIN后的行可能包含同一A的多条数据,LIMIT 50可能只覆盖到49个不同的A实例。
解决方法
用子查询先获取A表ID
删掉subQuery: false(默认就是true),让Sequelize先查符合条件的前50条A记录的ID,再关联B表。修改后的查询参数:const params = { limit: 50, offset: 0, order: [ [ 'id', 'desc']], attributes: [ 'id', 'name', 'start_date', 'end_date', ], // 移除subQuery: false,使用默认值 where: { deleted: false }, include: [{model: B}], logging: console.log }这种方式下,Sequelize会先执行
SELECT id FROM A WHERE deleted = false ORDER BY id DESC LIMIT 50,再用这些ID关联B表,确保返回50个A实例,不受关联B记录数量影响。添加distinct参数去重
如果必须保留subQuery: false,可以在参数里加distinct: true,确保只返回不同的A记录:const params = { limit: 50, offset: 0, order: [ [ 'id', 'desc']], attributes: [ 'id', 'name', 'start_date', 'end_date', ], subQuery: false, where: { deleted: false }, include: [{model: B}], distinct: true, // 添加该参数 logging: console.log }注意:这种方式在数据量大时可能影响性能。
检查模型主键定义
确认A模型的主键是id(Sequelize默认会自动生成id主键,但若手动修改过需核实),主键缺失或错误会导致Sequelize合并JOIN结果时出错,减少实例数量。
内容的提问来源于stack exchange,提问作者lordoku

