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

sequelize-typescript多对多关联查询构建问题求助

问题分析

你的查询失效的核心原因是把关联表的过滤条件放在了include.through.where中,这部分条件会被Sequelize解析为JOIN的ON子句,而非WHERE子句,无法起到全局过滤的作用。同时,你试图通过characterId: null匹配未关联任何产品的记录,但中间表的characterId是非空外键,这个条件本身就不成立。

目标SQL的逻辑是:

  • 筛选未被软删除的Character
  • 满足以下任一条件:
    1. 关联的产品ID不等于指定值
    2. 没有关联任何产品(中间表无对应记录)

但当前写法未将这些条件正确映射到主查询的WHERE子句,导致所有符合主表基础条件的Character都被返回。

解决方案

需要在主表的where选项中直接构建逻辑,结合Op.or、Op.notExists和关联子查询实现需求:

写法一:使用原生SQL片段(简洁直接)

import { Op } from 'sequelize';

return await CharacterModel.findAndCountAll({
  where: {
    deletedAt: null,
    [Op.or]: [
      // 条件1:没有关联任何产品(中间表无对应记录)
      {
        [Op.notExists]: sequelize.literal(`
          SELECT 1 FROM products_characters pc 
          WHERE pc.character_id = "CharacterModel".id
        `)
      },
      // 条件2:未关联指定产品
      {
        [Op.notExists]: sequelize.literal(`
          SELECT 1 FROM products_characters pc 
          WHERE pc.character_id = "CharacterModel".id 
          AND pc.product_id = '${productId}'
        `)
      }
    ]
  },
  order: [["createdAt", "desc"]],
  limit: queryParams.limit,
  offset: queryParams.offset,
  attributes: {
    exclude: ['updatedAt', 'deletedAt']
  },
  include: [] // 无需返回关联产品,故清空include
});

写法二:纯Sequelize API风格(避免原生SQL)

import { Op, col } from 'sequelize';

// 子查询:检查是否关联了指定产品
const hasTargetProductSubquery = ProductCharacterModel.findOne({
  attributes: [],
  where: {
    characterId: col('CharacterModel.id'),
    productId: productId
  }
});

// 子查询:检查是否有任何关联产品
const hasAnyProductSubquery = ProductCharacterModel.findOne({
  attributes: [],
  where: {
    characterId: col('CharacterModel.id')
  }
});

return await CharacterModel.findAndCountAll({
  where: {
    deletedAt: null,
    [Op.or]: [
      // 没有关联任何产品
      { [Op.notExists]: hasAnyProductSubquery },
      // 未关联指定产品
      { [Op.notExists]: hasTargetProductSubquery }
    ]
  },
  order: [["createdAt", "desc"]],
  limit: queryParams.limit,
  offset: queryParams.offset,
  attributes: {
    exclude: ['updatedAt', 'deletedAt']
  },
  include: []
});
原写法失效的具体原因
  1. include.through.where的作用误解:该选项仅用于过滤需要返回的关联表记录,不会过滤主表数据。比如一个Character关联多个产品时,它只会排除符合条件的产品关联,但主表的Character依然会被返回。
  2. characterId: null条件无效:中间表products_characters的characterId是外键且不允许为空,这个条件永远匹配不到任何记录,无法筛选出未关联产品的Character。
  3. 生成SQL的缺失:从日志可见,最终SQL的WHERE子句仅包含主表的deleted_at和id条件,完全没有关联表的过滤逻辑,这就是返回所有符合主表条件记录的直接原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:14:51