Sequelize включить одну и ту же ассоциацию дважды с разными критериями - PullRequest
0 голосов
/ 28 мая 2019

Допустим, у меня есть таблица suppliers, которая связана с tags через другую таблицу suppliers_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' },
        ],
      },
    },
  ],
});

Возвращает Поставщика1 и Поставщика3, правильно соединяющего suppliers_tags и tags и фильтрующего таблицу tags для включения таблиц type 'food' и name 'Vegan'.

Теперь, что если я хочу найти поставщиков, в которых оба , выполняются следующие условия:

  • У поставщика есть связанный тег типа 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 был создан в соединении tags_drink с помощью Sequelize.

Если я переключу определения отношений, то есть ::1079*

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 или:

Существует ли определенный способ объединения одного и того же отношения дважды по разным критериям в Sequelize?

...