Допустим, у меня есть таблица 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?