FindAll 包含涉及复杂的多对(多对多)关系(sequelizejs)
FindAll with includes involving a complicated many-to-(many-to-many) relationship (sequelizejs)
这有 a sibling question in Software Engineering SE.
考虑 Company
、Product
和 Person
。
Company
和 Product
之间存在多对多关系,通过结点 table Company_Product
,因为给定的公司可能生产多个产品(例如 "car" 和 "bicycle"),而且给定的产品,例如 "car",可以由多家公司生产。在交界处 table Company_Product
有一个额外的字段 "price" 这是给定公司销售给定产品的价格。
Company_Product
和 Person
之间还有另一个多对多关系,通过连接点 table Company_Product_Person
。是的,它是一种多对多关系,涉及一个已经是联结点的实体 table。这是因为一个人可以拥有多种产品,例如 company1 的汽车和 company2 的自行车,而同样的 company_product 可以由多个人拥有,因为例如 person1 和 person2 都可以拥有从公司 1 买了一辆车。在交界处 table Company_Product_Person
有一个额外的字段 "thoughts",其中包含人们在购买 company_product.
时的想法
我想用 sequelize 进行查询,以从数据库中获取 Company
的所有实例,以及所有相关的 Products
和相应的 Company_Product
包括所有相关的 Persons
和相应的 Company_Product_Persons
.
获取两个结点 table 的元素也很重要,因为字段 "price" 和 "thoughts" 很重要。
我不知道该怎么做。
为了调查这个问题,我尽量缩短了代码。 看起来很大,但大部分是模型声明样板:(要运行呢,先做npm install sequelize sqlite3
)
const Sequelize = require("sequelize");
const sequelize = new Sequelize({ dialect: "sqlite", storage: "db.sqlite" });
// ================= MODELS =================
const Company = sequelize.define("company", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Product = sequelize.define("product", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Person = sequelize.define("person", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Company_Product = sequelize.define("company_product", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
companyId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "company",
key: "id"
},
onDelete: "CASCADE"
},
productId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "product",
key: "id"
},
onDelete: "CASCADE"
},
price: Sequelize.INTEGER
});
const Company_Product_Person = sequelize.define("company_product_person", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
companyProductId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "company_product",
key: "id"
},
onDelete: "CASCADE"
},
personId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "person",
key: "id"
},
onDelete: "CASCADE"
},
thoughts: Sequelize.STRING
});
// ================= RELATIONS =================
// Many to Many relationship between Company and Product
Company.belongsToMany(Product, { through: "company_product", foreignKey: "companyId", onDelete: "CASCADE" });
Product.belongsToMany(Company, { through: "company_product", foreignKey: "productId", onDelete: "CASCADE" });
// Many to Many relationship between Company_Product and Person
Company_Product.belongsToMany(Person, { through: "company_product_person", foreignKey: "companyProductId", onDelete: "CASCADE" });
Person.belongsToMany(Company_Product, { through: "company_product_person", foreignKey: "personId", onDelete: "CASCADE" });
// ================= TEST =================
var company, product, person, company_product, company_product_person;
sequelize.sync({ force: true })
.then(() => {
// Create one company, one product and one person for tests.
return Promise.all([
Company.create({ name: "Company test" }).then(created => { company = created }),
Product.create({ name: "Product test" }).then(created => { product = created }),
Person.create({ name: "Person test" }).then(created => { person = created }),
]);
})
.then(() => {
// company produces product
return company.addProduct(product);
})
.then(() => {
// Get the company_product for tests
return Company_Product.findAll().then(found => { company_product = found[0] });
})
.then(() => {
// person owns company_product
return company_product.addPerson(person);
})
.then(() => {
// I can get the list of Companys with their Products, but couldn't get the nested Persons...
return Company.findAll({
include: [{
model: Product
}]
}).then(companies => {
console.log(JSON.stringify(companies.map(company => company.toJSON()), null, 4));
});
})
.then(() => {
// And I can get the list of Company_Products with their Persons...
return Company_Product.findAll({
include: [{
model: Person
}]
}).then(companyproducts => {
console.log(JSON.stringify(companyproducts.map(companyproduct => companyproduct.toJSON()), null, 4));
});
})
.then(() => {
// I should be able to make both calls above in one, getting those nested things
// at once, but how??
return Company.findAll({
include: [{
model: Product
// ???
}]
}).then(companies => {
console.log(JSON.stringify(companies.map(company => company.toJSON()), null, 4));
});
});
我的目标是一次性获得一个包含所有深层嵌套的 Persons
和 Company_Product_Persons
的 Companys
数组:
// My goal:
[
{
"id": 1,
"name": "Company test",
"createdAt": "...",
"updatedAt": "...",
"products": [
{
"id": 1,
"name": "Product test",
"createdAt": "...",
"updatedAt": "...",
"company_product": {
"id": 1,
"companyId": 1,
"productId": 1,
"price": null,
"createdAt": "...",
"updatedAt": "...",
"persons": [
{
"id": 1,
"name": "Person test",
"createdAt": "...",
"updatedAt": "...",
"company_product_person": {
"id": 1,
"companyProductId": 1,
"personId": 1,
"thoughts": null,
"createdAt": "...",
"updatedAt": "..."
}
}
]
}
}
]
}
];
我该怎么做?
注意:我可以分别进行两个查询并为 "join" 检索到的对象编写一些代码,但这在计算上会很昂贵且难看。我正在寻找执行此操作的正确方法。
OP在这里。
简答
解决的关键是重新思考联想。将关联更改为:
Company.hasMany(Company_Product, { foreignKey: "companyId" });
Company_Product.belongsTo(Company, { foreignKey: "companyId" });
Product.hasMany(Company_Product, { foreignKey: "productId" });
Company_Product.belongsTo(Product, { foreignKey: "productId" });
Company_Product.hasMany(Company_Product_Person, { foreignKey: "companyProductId" });
Company_Product_Person.belongsTo(Company_Product, { foreignKey: "companyProductId" });
Person.hasMany(Company_Product_Person, { foreignKey: "personId" });
Company_Product_Person.belongsTo(Person, { foreignKey: "personId" });
将return company.addProduct(product);
改为
return Company_Product.create({
companyId: company.id,
productId: product.id,
price: 99
}).then(created => { company_product = created });
将return company_product.addPerson(person)
改为
return Company_Product_Person.create({
companyProductId: company_product.id,
personId: person.id,
thoughts: "nice"
}).then(created => { company_product_person = created });
回答问题的查询是
Company.findAll({
include: [{
model: Company_Product,
include: [{
model: Product
}, {
model: Company_Product_Person,
include: [{
model: Person
}]
}]
}]
})
生成的 JSON 结构并不完全是问题中提到的 "goal",而只是 re-ordering.
的问题
长答案
我找到了一个解决方案,涉及重新处理 table 之间的关联,即使问题中给出的关联在技术上并没有错。一种看待问题的新方法,改变关联,是找到一种方法来做我想做的事情的关键。
分析旧方法
首先,我问题中给出的两个路口table都超过了"just"路口table。它们不仅仅是定义哪些元素与哪些元素相关的工具,而是更多的东西:
他们还有额外的信息(字段分别为 "price" 和 "thoughts");
第一个 Company_Product
也与其他 table 本身有关系。
严格来说,这在技术上并没有错,但是有一种更自然的方式来构造数据库来表示相同的事物。更好的是,使用这种新方法,使我想要的查询变得非常简单。
解决方案:新方法
当我们看到我们正在对可以购买的项目和购买本身进行建模时,解决方案就出现了。我们不会将此信息 "disguised" 保存在 many-to-many 关系的连接点 table 内,而是将它们作为我们方案中的显式实体,具有自己的 tables.
所以,首先,为了澄清,让我们重命名我们的模型:
Company
保持 Company
Product
变为 ProductType
Company_Product
变为 Product
Person
保持 Person
Company_Product_Person
变为 Purchase
然后我们看到:
- 一个
Product
有一个Company
和一个ProductType
。相反,同一个 Company
可以关联多个 Product
,同一个 ProductType
可以关联多个 Product
.
- 一个
Purchase
有一个Product
和一个Person
。相反,同一个 Product
可以关联多个 Purchase
,同一个 Product
可以关联多个 Person
.
请注意,不再有 many-to-many 关系。关系变为:
Company.hasMany(Product, { foreignKey: "companyId" });
Product.belongsTo(Company, { foreignKey: "companyId" });
ProductType.hasMany(Product, { foreignKey: "productTypeId" });
Product.belongsTo(ProductType, { foreignKey: "productTypeId" });
Product.hasMany(Purchase, { foreignKey: "productId" });
Purchase.belongsTo(Product, { foreignKey: "productId" });
Person.hasMany(Purchase, { foreignKey: "personId" });
Purchase.belongsTo(Person, { foreignKey: "personId" });
然后,原来的company.addProduct(product);
变成了
Product.create({
companyId: company.id
productTypeId: productType.id,
price: 99
})
并且类似地 company_product.addPerson(person);
变为
Purchase.create({
productId: product.id,
personId: person.id,
thoughts: "nice"
})
现在,我们可以很容易地看到进行所需查询的方法:
Company.findAll({
include: [{
model: Product,
include: [{
model: ProductType
}, {
model: Purchase,
include: [{
model: Person
}]
}]
}]
})
上述查询的结果并不是100%等同于问题中提到的"goal",因为Product和ProductType的嵌套顺序被调换了(Person和Purchase也是这样),而是转换为所需的结构现在只是编写一些 javascript 逻辑的问题,不再是涉及数据库或 sequelize 的问题。
结论
虽然问题中提供的数据库方案在技术上没有错误本身,但通过稍微改变方案找到了解决方案。
我们没有使用比简单的联结 table 更复杂的联结 table,而是摆脱了 many-to-many 关系和 "promoted" 联结 [=141] =]s 到我们方案的 full-fledged 个实体。事实上,table 是相同的;变化只是关系和看待关系的方式。
这有 a sibling question in Software Engineering SE.
考虑 Company
、Product
和 Person
。
Company
和 Product
之间存在多对多关系,通过结点 table Company_Product
,因为给定的公司可能生产多个产品(例如 "car" 和 "bicycle"),而且给定的产品,例如 "car",可以由多家公司生产。在交界处 table Company_Product
有一个额外的字段 "price" 这是给定公司销售给定产品的价格。
Company_Product
和 Person
之间还有另一个多对多关系,通过连接点 table Company_Product_Person
。是的,它是一种多对多关系,涉及一个已经是联结点的实体 table。这是因为一个人可以拥有多种产品,例如 company1 的汽车和 company2 的自行车,而同样的 company_product 可以由多个人拥有,因为例如 person1 和 person2 都可以拥有从公司 1 买了一辆车。在交界处 table Company_Product_Person
有一个额外的字段 "thoughts",其中包含人们在购买 company_product.
我想用 sequelize 进行查询,以从数据库中获取 Company
的所有实例,以及所有相关的 Products
和相应的 Company_Product
包括所有相关的 Persons
和相应的 Company_Product_Persons
.
获取两个结点 table 的元素也很重要,因为字段 "price" 和 "thoughts" 很重要。
我不知道该怎么做。
为了调查这个问题,我尽量缩短了代码。 看起来很大,但大部分是模型声明样板:(要运行呢,先做npm install sequelize sqlite3
)
const Sequelize = require("sequelize");
const sequelize = new Sequelize({ dialect: "sqlite", storage: "db.sqlite" });
// ================= MODELS =================
const Company = sequelize.define("company", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Product = sequelize.define("product", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Person = sequelize.define("person", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING
});
const Company_Product = sequelize.define("company_product", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
companyId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "company",
key: "id"
},
onDelete: "CASCADE"
},
productId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "product",
key: "id"
},
onDelete: "CASCADE"
},
price: Sequelize.INTEGER
});
const Company_Product_Person = sequelize.define("company_product_person", {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
companyProductId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "company_product",
key: "id"
},
onDelete: "CASCADE"
},
personId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "person",
key: "id"
},
onDelete: "CASCADE"
},
thoughts: Sequelize.STRING
});
// ================= RELATIONS =================
// Many to Many relationship between Company and Product
Company.belongsToMany(Product, { through: "company_product", foreignKey: "companyId", onDelete: "CASCADE" });
Product.belongsToMany(Company, { through: "company_product", foreignKey: "productId", onDelete: "CASCADE" });
// Many to Many relationship between Company_Product and Person
Company_Product.belongsToMany(Person, { through: "company_product_person", foreignKey: "companyProductId", onDelete: "CASCADE" });
Person.belongsToMany(Company_Product, { through: "company_product_person", foreignKey: "personId", onDelete: "CASCADE" });
// ================= TEST =================
var company, product, person, company_product, company_product_person;
sequelize.sync({ force: true })
.then(() => {
// Create one company, one product and one person for tests.
return Promise.all([
Company.create({ name: "Company test" }).then(created => { company = created }),
Product.create({ name: "Product test" }).then(created => { product = created }),
Person.create({ name: "Person test" }).then(created => { person = created }),
]);
})
.then(() => {
// company produces product
return company.addProduct(product);
})
.then(() => {
// Get the company_product for tests
return Company_Product.findAll().then(found => { company_product = found[0] });
})
.then(() => {
// person owns company_product
return company_product.addPerson(person);
})
.then(() => {
// I can get the list of Companys with their Products, but couldn't get the nested Persons...
return Company.findAll({
include: [{
model: Product
}]
}).then(companies => {
console.log(JSON.stringify(companies.map(company => company.toJSON()), null, 4));
});
})
.then(() => {
// And I can get the list of Company_Products with their Persons...
return Company_Product.findAll({
include: [{
model: Person
}]
}).then(companyproducts => {
console.log(JSON.stringify(companyproducts.map(companyproduct => companyproduct.toJSON()), null, 4));
});
})
.then(() => {
// I should be able to make both calls above in one, getting those nested things
// at once, but how??
return Company.findAll({
include: [{
model: Product
// ???
}]
}).then(companies => {
console.log(JSON.stringify(companies.map(company => company.toJSON()), null, 4));
});
});
我的目标是一次性获得一个包含所有深层嵌套的 Persons
和 Company_Product_Persons
的 Companys
数组:
// My goal:
[
{
"id": 1,
"name": "Company test",
"createdAt": "...",
"updatedAt": "...",
"products": [
{
"id": 1,
"name": "Product test",
"createdAt": "...",
"updatedAt": "...",
"company_product": {
"id": 1,
"companyId": 1,
"productId": 1,
"price": null,
"createdAt": "...",
"updatedAt": "...",
"persons": [
{
"id": 1,
"name": "Person test",
"createdAt": "...",
"updatedAt": "...",
"company_product_person": {
"id": 1,
"companyProductId": 1,
"personId": 1,
"thoughts": null,
"createdAt": "...",
"updatedAt": "..."
}
}
]
}
}
]
}
];
我该怎么做?
注意:我可以分别进行两个查询并为 "join" 检索到的对象编写一些代码,但这在计算上会很昂贵且难看。我正在寻找执行此操作的正确方法。
OP在这里。
简答
解决的关键是重新思考联想。将关联更改为:
Company.hasMany(Company_Product, { foreignKey: "companyId" });
Company_Product.belongsTo(Company, { foreignKey: "companyId" });
Product.hasMany(Company_Product, { foreignKey: "productId" });
Company_Product.belongsTo(Product, { foreignKey: "productId" });
Company_Product.hasMany(Company_Product_Person, { foreignKey: "companyProductId" });
Company_Product_Person.belongsTo(Company_Product, { foreignKey: "companyProductId" });
Person.hasMany(Company_Product_Person, { foreignKey: "personId" });
Company_Product_Person.belongsTo(Person, { foreignKey: "personId" });
将return company.addProduct(product);
改为
return Company_Product.create({
companyId: company.id,
productId: product.id,
price: 99
}).then(created => { company_product = created });
将return company_product.addPerson(person)
改为
return Company_Product_Person.create({
companyProductId: company_product.id,
personId: person.id,
thoughts: "nice"
}).then(created => { company_product_person = created });
回答问题的查询是
Company.findAll({
include: [{
model: Company_Product,
include: [{
model: Product
}, {
model: Company_Product_Person,
include: [{
model: Person
}]
}]
}]
})
生成的 JSON 结构并不完全是问题中提到的 "goal",而只是 re-ordering.
的问题长答案
我找到了一个解决方案,涉及重新处理 table 之间的关联,即使问题中给出的关联在技术上并没有错。一种看待问题的新方法,改变关联,是找到一种方法来做我想做的事情的关键。
分析旧方法
首先,我问题中给出的两个路口table都超过了"just"路口table。它们不仅仅是定义哪些元素与哪些元素相关的工具,而是更多的东西:
他们还有额外的信息(字段分别为 "price" 和 "thoughts");
第一个
Company_Product
也与其他 table 本身有关系。
严格来说,这在技术上并没有错,但是有一种更自然的方式来构造数据库来表示相同的事物。更好的是,使用这种新方法,使我想要的查询变得非常简单。
解决方案:新方法
当我们看到我们正在对可以购买的项目和购买本身进行建模时,解决方案就出现了。我们不会将此信息 "disguised" 保存在 many-to-many 关系的连接点 table 内,而是将它们作为我们方案中的显式实体,具有自己的 tables.
所以,首先,为了澄清,让我们重命名我们的模型:
Company
保持Company
Product
变为ProductType
Company_Product
变为Product
Person
保持Person
Company_Product_Person
变为Purchase
然后我们看到:
- 一个
Product
有一个Company
和一个ProductType
。相反,同一个Company
可以关联多个Product
,同一个ProductType
可以关联多个Product
. - 一个
Purchase
有一个Product
和一个Person
。相反,同一个Product
可以关联多个Purchase
,同一个Product
可以关联多个Person
.
请注意,不再有 many-to-many 关系。关系变为:
Company.hasMany(Product, { foreignKey: "companyId" });
Product.belongsTo(Company, { foreignKey: "companyId" });
ProductType.hasMany(Product, { foreignKey: "productTypeId" });
Product.belongsTo(ProductType, { foreignKey: "productTypeId" });
Product.hasMany(Purchase, { foreignKey: "productId" });
Purchase.belongsTo(Product, { foreignKey: "productId" });
Person.hasMany(Purchase, { foreignKey: "personId" });
Purchase.belongsTo(Person, { foreignKey: "personId" });
然后,原来的company.addProduct(product);
变成了
Product.create({
companyId: company.id
productTypeId: productType.id,
price: 99
})
并且类似地 company_product.addPerson(person);
变为
Purchase.create({
productId: product.id,
personId: person.id,
thoughts: "nice"
})
现在,我们可以很容易地看到进行所需查询的方法:
Company.findAll({
include: [{
model: Product,
include: [{
model: ProductType
}, {
model: Purchase,
include: [{
model: Person
}]
}]
}]
})
上述查询的结果并不是100%等同于问题中提到的"goal",因为Product和ProductType的嵌套顺序被调换了(Person和Purchase也是这样),而是转换为所需的结构现在只是编写一些 javascript 逻辑的问题,不再是涉及数据库或 sequelize 的问题。
结论
虽然问题中提供的数据库方案在技术上没有错误本身,但通过稍微改变方案找到了解决方案。
我们没有使用比简单的联结 table 更复杂的联结 table,而是摆脱了 many-to-many 关系和 "promoted" 联结 [=141] =]s 到我们方案的 full-fledged 个实体。事实上,table 是相同的;变化只是关系和看待关系的方式。