FindAll包括涉及复杂的多对多(多对多)关系(sequelizejs)

时间:2018-02-09 19:42:33

标签: javascript node.js many-to-many sequelize.js eager-loading

这有a sibling question in Software Engineering SE

考虑CompanyProductPerson

CompanyProduct之间通过联结表Company_Product存在多对多关系,因为某个公司可能会生产多个产品(例如&# 34;汽车"和#34;自行车"),但也有一个特定的产品,如汽车",可以由多家公司生产。在联结表Company_Product中,有一个额外的字段"价格"这是给定公司销售给定产品的价格。

通过联结表Company_ProductPersonCompany_Product_Person之间存在另一种多对多关系。是的,它是一个多对多关系,涉及一个已经是联结表的实体。这是因为一个人可以拥有多个产品,例如来自company1的汽车和来自company2的自行车,反过来同一个company_product可以由多个人拥有,因为例如person1和person2都可以从公司1。在联结表Company_Product_Person中有一个额外的字段"想法"其中包含了他们购买company_product时的想法。

我想与sequelize进行查询,以便从数据库中获取Company的所有实例,其中所有相关的Products都包含相应的Company_Product,其中包含所有Persons相关Company_Product_Persons与相应的npm install sequelize sqlite3

获取两个联结表的元素也很重要,因为字段"价格"和#34;想法"很重要。

我无法弄清楚如何做到这一点。

我尽可能缩短代码来调查此问题。 看起来很大,但大部分都是模型声明样板:(要运行它,先做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)); }); });

Companys

我的目标是一次性获取所有深层嵌套PersonsCompany_Product_Persons的{​​{1}}数组:

// 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"检索到的对象,但这将是计算上昂贵和丑陋的。我正在寻找正确的方法来做到这一点。

1 个答案:

答案 0 :(得分:3)

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结构并不完全是提到的“目标”,但它只是重新排序的问题。

长答案

我找到了一个解决方案,涉及重新处理表之间的关联,即使问题中给出的关联在技术上不是错误的。一种新的方式来查看问题,改变关联,是找到一种方法来做我想要的事情的关键。

分析旧方法

首先,我的问题中给出的两个联结表都不仅仅是“联合表”。它们不仅仅是定义哪些元素与哪些元素相关的工具,而是更多:

  • 他们还有额外的信息(分别是“价格”和“思想”字段);

  • 第一个Company_Product也与其他表本身有关系。

严格来说,这在技术上并不是错误的,但是有一种更自然的方式来构建数据库来表示相同的事物。更好的是,使用这种新方法,使我想要的查询变得非常简单。

解决方案:新方法

当我们看到我们正在为可购买的商品和购买商品建模时,解决方案就会上升。我们不会将这些信息“伪装”在多对多关系的联结表中,而是将它们作为我们方案中的显式实体,并使用自己的表格。

首先,澄清一下,让我们重命名我们的模型:

  • 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相关。

请注意,不再存在多对多关系。关系变成:

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%等同于问题中提到的“目标”,因为Product和ProductType的嵌套顺序是交换的(Person和Purchase也是如此),但是转换为所需的结构是现在只需编写一些javascript逻辑,而不再是涉及数据库或续集的问题。

结论

虽然问题中提供的数据库方案在技术上并不错误本身,但通过稍微改变方案找到了解决方案。

我们没有使用超过简单联结表的联结表,而是摆脱了多对多关系,并将联结表“提升”到我们方案的成熟实体。实际上,表格是一样的;这些变化只发生在关系和观察它们的方式上。