Sequelize包含两次具有不同条件的相同关联

时间:2019-05-28 14:39:05

标签: node.js postgresql join foreign-keys sequelize.js

说我有一个表suppliers,该表通过另一个表tagssuppliers_tags关联。所以我的模型看起来像这样:

const Supplier = Sequelize.define('suppliers', {
  id: { type: Sequelize.INTEGER, primaryKey: true },
  name: Sequelize.STRING,
});

const SupplierTag = Sequelize.define('suppliers_tags', {
  id: { type: Sequelize.INTEGER, primaryKey: true },
  supplier_id: { type: Sequelize.INTEGER, references: { model: 'suppliers', key: 'id' } },
  tag_id: { type: Sequelize.INTEGER, references: { model: 'suppliers', key: 'id' } },
});

const Tag = Sequelize.define('tags', {
  id: { type: Sequelize.INTEGER, primaryKey: true },
  name: Sequelize.STRING,
  type: Sequelize.ENUM('food', 'drink'),
});

关联看起来像这样:

Supplier.belongsToMany(Tag, { as: 'tags', through: 'suppliers_tags', foreignKey: 'supplier_id' });

Tag.belongsToMany(Supplier, { as: 'supplierTag', through: 'suppliers_tags', foreignKey: 'tag_id' });

假设我在数据库中有以下数据:

供应商:

id  name
1   Supplier1
2   Supplier2
3   Supplier3

标签:

id  name   type
1   Food1  food
2   Vegan  food
3   Vegan  drink
4   Food2  food

(我特意将两个标签命名为相同的标签,对于此应用程序,具有不同类型的标签可以具有相同的名称非常重要。)

suppliers_tags

id  supplier_id  tag_id
1   1            1
2   1            3
3   1            2
4   2            1
5   2            4
6   3            2

现在我可以执行以下查询:

Supplier.findAll({
  include: [
    {
      model: Tag,
      as: 'tags',
      where: {
        [Op.and]: [
          { type: 'food' },
          { name: 'Vegan' },
        ],
      },
    },
  ],
});

这将返回Supplier1和Supplier3,正确加入suppliers_tagstags并过滤tags表以包含type'food'和name中的一个“素食主义者”。

现在,如果我要搜索同时满足以下两个条件的供应商怎么办:

  • 供应商具有一个关联的标签,该标签的类型为food,名称为Food1
  • 供应商具有一个关联的标签,该标签的类型为drink,名称为Vegan

天真的(?),我尝试了以下操作:

Supplier.findAll({
  include: [
    {
      model: Tag,
      as: 'tags_food',
      where: {
        [Op.and]: [
          { type: 'food' },
          { name: 'Food1' },
        ],
      },
    },
    {
      model: Tag,
      as: 'tags_drink',
      where: {
        [Op.and]: [
          { type: 'drink' },
          { name: 'Vegan' },
        ],
      },
    },
  ],
});

这会尝试两次联接tags表,但忽略添加别名,从而导致以下错误:

SequelizeEagerLoadingError: tags is associated to suppliers multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.

对,所以as上的include选项似乎没有达到预期的效果。如果我将关联修改为以下内容,该怎么办:

Supplier.belongsToMany(Tag, { as: 'tags_drink', through: 'suppliers_tags', foreignKey: 'supplier_id' });
Supplier.belongsToMany(Tag, { as: 'tags_food', through: 'suppliers_tags', foreignKey: 'supplier_id' });

Tag.belongsToMany(Supplier, { as: 'supplierTag', through: 'suppliers_tags', foreignKey: 'tag_id' });

现在,如果我运行相同的findAll查询,它将生成以下SQL:

SELECT
  "suppliers".*
  ,"tags_food"."id" AS "tags_food.id"
  ,"tags_food"."name" AS "tags_food.name"
  ,"tags_food"."type" AS "tags_food.type"
  ,"tags_food->suppliers_tags"."supplier_id" AS "tags_food.suppliers_tags.supplier_id"
  ,"tags_food->suppliers_tags"."tag_id" AS "tags_food.suppliers_tags.tag_id"
  ,"tags_drink"."id" AS "tags_drink.id"
  ,"tags_drink"."name" AS "tags_drink.name"
  ,"tags_drink"."type" AS "tags_drink.type"
  ,"tags_drink->suppliers_tags"."supplier_id" AS "tags_drink.suppliers_tags.supplier_id"
  ,"tags_drink->suppliers_tags"."tag_id" AS "tags_drink.suppliers_tags.tag_id"
FROM (
  SELECT
    "suppliers"."id"
    ,"suppliers"."name"
  FROM "suppliers" AS "suppliers"
  WHERE (
    SELECT "suppliers_tags"."supplier_id"
    FROM "suppliers_tags" AS "suppliers_tags"
    INNER JOIN "tags" AS "tag" ON "suppliers_tags"."tagId" = "tag"."id"
      AND ("tag"."type" = 'food' AND "tag"."name" = 'Food1')
    WHERE ("suppliers"."id" = "suppliers_tags"."supplier_id")
    LIMIT 1
  ) IS NOT NULL
  AND (
    SELECT "suppliers_tags"."supplier_id"
    FROM "suppliers_tags" AS "suppliers_tags"
    INNER JOIN "tags" AS "tag" ON "suppliers_tags"."tag_id" = "tag"."id"
      AND ("tag"."type" = 'drink' AND "tag"."name" = 'Vegan')
    WHERE ("suppliers"."id" = "suppliers_tags"."supplier_id")
    LIMIT 1
  ) IS NOT NULL
) AS "suppliers"
INNER JOIN (
  "suppliers_tags" AS "tags_food->suppliers_tags"
  INNER JOIN "tags" AS "tags_food"
    ON "tags_food"."id" = "tags_food->suppliers_tags"."tagId"
) ON "suppliers"."id" = "tags_food->suppliers_tags"."supplier_id"
  AND ("tags_food"."type" = 'food' AND "tags_food"."name" = 'Food1')
INNER JOIN (
  "suppliers_tags" AS "tags_drink->suppliers_tags"
  INNER JOIN "tags" AS "tags_drink"
    ON "tags_drink"."id" = "tags_drink->suppliers_tags"."tag_id"
) ON "suppliers"."id" = "tags_drink->suppliers_tags"."supplier_id"
  AND ("tags_drink"."type" = 'drink' AND "tags_drink"."name" = 'Vegan')
ORDER BY "suppliers"."id"

这是正确的并且是我需要的,除了一个错误:在tag_id联接的情况下,外键tagId已切换为tags_food(?!)。这当然会失败,并显示以下错误:

SequelizeDatabaseError: column tags_food->suppliers_tags.tagId does not exist

请注意,正确外键tag_id是由Sequelize在tags_drink上的联接中生成的。

如果我切换关系定义,即:

Supplier.belongsToMany(Tag, { as: 'tags_food', through: 'suppliers_tags', foreignKey: 'supplier_id' });
Supplier.belongsToMany(Tag, { as: 'tags_drink', through: 'suppliers_tags', foreignKey: 'supplier_id' });

Tag.belongsToMany(Supplier, { as: 'supplierTag', through: 'suppliers_tags', foreignKey: 'tag_id' });

然后成功为tag_id联接生成tags_food,为tagId联接产生奇怪的tags_drink

我可以将其归结为Sequelize的another基本缺陷,还是:

在Sequelize中,是否存在一种确定的方法可以在不同条件下两次将同一关系加入?

0 个答案:

没有答案