编程语言
首页 > 编程语言> > javascript-FindAll及其包括涉及复杂的多对多(多对多)关系(sequelizejs)

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

作者:互联网

这有a sibling question in Software Engineering SE.

考虑公司,产品和人员.

通过联结表Company_Product,公司与产品之间存在多对多关系,因为给定的公司可能会生产多个产品(例如“汽车”和“自行车”),也可能会生产给定的产品,例如“汽车”,可以由多家公司生产.在联结表Company_Product中,有一个额外的字段“ price”,它是给定公司出售给定产品的价格.

通过联结表Company_Product_Person,Company_Product和Person之间存在另一对多关系.是的,这是一个涉及一个实体的多对多关系,该实体已经是一个联结表.这是因为一个人可以拥有多个产品,例如,公司1的汽车和公司2的自行车,而同一公司产品又可以由一个以上的人拥有,因为例如person1和person2都可以从公司1.在联结表Company_Product_Person中,有一个额外的字段“ thoughts”,其中包含该人在购买company_product时的想法.

我想使用sequelize进行查询,以从数据库中获取Company的所有实例,以及具有相关Company_Product的所有相关产品,而这些产品又包括具有Company_Product_Persons的所有相关人员.

获取两个联结表的元素也很重要,因为“价格”和“想法”字段很重要.

而且我不知道该怎么做.

我将代码做得尽可能短以进行调查.看起来很大,但其中大多数是模型声明样板:(要运行它,请先执行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));
        });
    });

我的目标是一次性获得具有所有深层人员和Company_Product_Persons的一系列公司:

// 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": "..."
                            }
                        }
                    ]
                }
            }
        ]
    }
];

我怎样才能做到这一点?

注意:我可以分别进行两个查询,并编写一些代码以“联接”检索到的对象,但这将在计算上昂贵且丑陋.我正在寻找正确的方法来做到这一点.

解决方法:

在这里操作.

简短答案

解决方案的关键是重新考虑关联.将关联更改为:

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" });

更改退货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也与其他表本身有关系.

从严格意义上讲,这在技术上并没有错,但是存在一种更自然的方法来构造数据库以表示相同的事物.更好的是,使用这种新方法,使我想要的查询变得非常简单.

解决方案:新方法

当我们看到我们正在建模可以购买的物品以及自己购买的物品时,解决方案就会出现.与其将这些信息“伪装”在多对多关系的联结表内,不如将它们作为自己的表中的显式实体作为方案中的显式实体.

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

>公司留在公司
>产品成为ProductType
>公司_产品成为产品
>人住人
>公司_产品_人员成为采购人员

然后我们看到:

>产品具有一个公司和一个产品类型.相反,同一公司可以与多个产品相关,而同一产品类型可以与多个产品相关.
>一件商品有一个产品和一个人.相反,同一产品可以与多个购买相关,而同一产品可以与多个人相关.

请注意,不再存在多对多关系.关系变为:

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逻辑的问题,而不再是涉及数据库或续集的问题.

结论

尽管问题中提供的数据库方案本身在技术上并没有错,但通过稍微更改方案即可找到解决方案.

我们不再使用简单的联结表以外的联结表,而是摆脱了多对多关系,并将联结表“提升”为方案的成熟实体.实际上,这些表是相同的.变化只是在关系和看待它们的方式上.

标签:eager-loading,sequelize-js,node-js,many-to-many,javascript
来源: https://codeday.me/bug/20191109/2013029.html