You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Sequelize关联模型Where查询时subQuery与分页冲突的解决方法咨询

问题

我使用如下where条件进行查询:

where = {
  [Op.or]: [
    {
      name: {
        [Op.like]: `%${word}%`,
      },
    },
    {
      description: {
        [Op.like]: `%${word}%`,
      },
    },
    {
      '$types.name$': {
        [Op.like]: `%${word}%`,
      },
    },
  ],
};

并通过以下方法获取包含Type和Feature模型的分页products数据:

const products = await this.productModel.findAndCountAll({
  subQuery: false,
  limit,
  offset,
  distinct: true,
  order: [['name', 'ASC']],
  where,
  include: [
    {
      model: Type,
      include: [
        {
          model: Feature,
        },
      ],
    },
  ],
});

为使$types.name$查询生效必须设置subQuery: false,但该配置与limit、offset配合时无法获取完整数据。使用的数据库方言为MariaDB,Product和Type模型定义如下:

Product模型:

@Table({ tableName: 'products' })
export class Product extends Model<Product, ProductCreationAttrs> {
  @Column({
    type: DataType.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  })
  id: number;

  @Column({ type: DataType.STRING, unique: true, allowNull: false })
  name: string;

  @Column({ type: DataType.STRING, allowNull: false })
  description: string;

  @HasMany(() => Type, { foreignKey: 'productId' })
  types: Type[];
}

Type模型:

@Table({ tableName: 'types' })
export class Type extends Model<Type, TypeCreationAttrs> {
  @Column({
    type: DataType.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  })
  id: number;

  @Column({ type: DataType.STRING, allowNull: false })
  name: string;

  @BelongsTo(() => Product, { foreignKey: 'productId' })
  product: Product;

  @BelongsToMany(() => Feature, () => TypeFeatures)
  features: Feature[];
}

请问有什么办法可以解决这个问题?

解决方案

方法一:先筛选符合条件的Product ID,再分页关联查询

先通过子查询获取所有匹配条件的Product ID集合,再基于这些ID执行分页和关联查询,规避subQuery: false导致的分页异常:

// 第一步:获取所有符合条件的Product ID
const matchedProductIds = await this.productModel.findAll({
  attributes: ['id'],
  subQuery: false,
  distinct: true,
  where,
  include: [{ model: Type }]
}).then(list => list.map(item => item.id));

// 第二步:基于ID分页查询,同时关联Type和Feature
const products = await this.productModel.findAndCountAll({
  limit,
  offset,
  distinct: true,
  order: [['name', 'ASC']],
  where: { id: matchedProductIds },
  include: [
    {
      model: Type,
      include: [{ model: Feature }]
    }
  ]
});

若匹配的Product数量极大,可改用嵌套子查询避免生成过长的ID数组:

const products = await this.productModel.findAndCountAll({
  limit,
  offset,
  distinct: true,
  order: [['name', 'ASC']],
  where: {
    id: {
      [Op.in]: this.productModel.findAll({
        attributes: ['id'],
        subQuery: false,
        distinct: true,
        where,
        include: [{ model: Type }]
      })
    }
  },
  include: [
    {
      model: Type,
      include: [{ model: Feature }]
    }
  ]
});

方法二:用Op.exists替代关联字段查询

将$types.name$的查询逻辑改为Op.exists子查询,这样可以开启subQuery: true,保证分页功能正常:

where = {
  [Op.or]: [
    { name: { [Op.like]: `%${word}%` } },
    { description: { [Op.like]: `%${word}%` } },
    {
      [Op.exists]: this.typeModel.findAll({
        attributes: [],
        where: {
          productId: { [Op.col]: 'product.id' },
          name: { [Op.like]: `%${word}%` }
        }
      })
    }
  ]
};

// 开启subQuery: true,分页正常工作
const products = await this.productModel.findAndCountAll({
  subQuery: true,
  limit,
  offset,
  distinct: true,
  order: [['name', 'ASC']],
  where,
  include: [
    {
      model: Type,
      include: [{ model: Feature }]
    }
  ]
});

核心是把关联表的查询逻辑移到exists子查询中,避免主查询因关联产生重复行干扰分页计数。

方法三:编写原生SQL查询

如果ORM方法无法满足需求,直接用原生SQL精确控制查询逻辑:

// 查询数据的SQL
const dataSql = `
  SELECT DISTINCT p.*, t.*, f.*
  FROM products p
  LEFT JOIN types t ON p.id = t.productId
  LEFT JOIN type_features tf ON t.id = tf.typeId
  LEFT JOIN features f ON tf.featureId = f.id
  WHERE p.name LIKE ? 
     OR p.description LIKE ? 
     OR t.name LIKE ?
  ORDER BY p.name ASC
  LIMIT ? OFFSET ?
`;

// 查询总条数的SQL
const countSql = `
  SELECT COUNT(DISTINCT p.id) AS total
  FROM products p
  LEFT JOIN types t ON p.id = t.productId
  WHERE p.name LIKE ? 
     OR p.description LIKE ? 
     OR t.name LIKE ?
`;

// 执行数据查询
const productRows = await this.productModel.sequelize.query(dataSql, {
  replacements: [`%${word}%`, `%${word}%`, `%${word}%`, limit, offset],
  model: Product,
  include: [{ model: Type, include: [{ model: Feature }] }]
});

// 执行计数查询
const countResult = await this.productModel.sequelize.query(countSql, {
  replacements: [`%${word}%`, `%${word}%`, `%${word}%`],
  type: this.productModel.sequelize.QueryTypes.SELECT
});

const finalResult = {
  rows: productRows,
  count: countResult[0].total
};

这种方式完全自定义查询逻辑,能彻底解决分页与关联查询的冲突,但需要维护原生SQL,灵活性高但可维护性稍低。

内容的提问来源于stack exchange,提问作者Alexei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 08:25:35