doushao1087 2018-07-04 11:50
浏览 47
已采纳

在Yii2中设置非规范化列的最佳实践

Question to all Yii2 normalization geeks out there.

Where is the best place to set non-normalized columns in Yii2?

Example, I have models Customer, Branch, CashRegister, and Transaction. In a perfect world, and in a perfectly normalized Database, our Transaction model would have only the cashregister_id, The CashRegister would store branch_id, and the Branch would store customer_id. However due to performance issues, we find ourselves obliged sometimes though to have a non-normalized Transaction model containing the following:

  1. cashregister_id
  2. branch_id
  3. customer_id

When creating a transaction, I want to store all 3 values. Setting

$transaction->branch_id = $transaction->cashRegister->branch_id;
$transaction->customer_id = $transaction->cashRegister->branch->customer_id;

however in the controller does not feel correct.

One solution would be to do this in aftersave() in the Transaction model and make those columns read-only. But this also seems better but not perfect.

I wanted to know what is the best practice or where is the best place to set those duplicate columns, to make sure that the data integrity is maintained?

  • 写回答

2条回答 默认 最新

  • douyingbei1458 2018-07-09 22:20
    关注

    The following is a DB-only solution.

    I assume your relations are:

    • A customer has many branches
    • A branch has many cashregisters
    • A cashregister has many transactions

    The corresponding schema could be:

    create table customers (
        customer_id int auto_increment,
        customer_data text,
        primary key (customer_id)
    );
    
    create table branches (
        branch_id int auto_increment,
        customer_id int not null,
        branch_data text,
        primary key (branch_id),
        index (customer_id),
        foreign key (customer_id) references customers(customer_id)
    );
    
    create table cashregisters (
        cashregister_id int auto_increment,
        branch_id int not null,
        cashregister_data text,
        primary key (cashregister_id),
        index (branch_id),
        foreign key (branch_id) references branches(branch_id)
    );
    
    create table transactions (
        transaction_id int auto_increment,
        cashregister_id int not null,
        transaction_data text,
        primary key (transaction_id),
        index (cashregister_id),
        foreign key (cashregister_id) references cashregisters(cashregister_id)
    );
    

    (Note: This should be part of your question - so we wouldn't need to guess.)

    If you want to include redundant columns (branch_id and customer_id) in the transactions table, you should make them part of the foreign key. But first you will need to include a customer_id column in the cashregisters table and also make it part of the foreign key.

    The extended schema would be:

    create table customers (
        customer_id int auto_increment,
        customer_data text,
        primary key (customer_id)
    );
    
    create table branches (
        branch_id int auto_increment,
        customer_id int not null,
        branch_data text,
        primary key (branch_id),
        index (customer_id, branch_id),
        foreign key (customer_id) references customers(customer_id)
    );
    
    create table cashregisters (
        cashregister_id int auto_increment,
        branch_id int not null,
        customer_id int not null,
        cashregister_data text,
        primary key (cashregister_id),
        index (customer_id, branch_id, cashregister_id),
        foreign key (customer_id, branch_id)
            references branches(customer_id, branch_id)
    );
    
    create table transactions (
        transaction_id int auto_increment,
        cashregister_id int not null,
        branch_id int not null,
        customer_id int not null,
        transaction_data text,
        primary key (transaction_id),
        index (customer_id, branch_id, cashregister_id),
        foreign key (customer_id, branch_id, cashregister_id)
            references cashregisters(customer_id, branch_id, cashregister_id)
    );
    

    Notes:

    • Any foreign key constraint needs an index in the child (referencing) and the parent (referenced) table, which can support the constraint check. The given column order in the keys allows us to define the schema with only one index per table.
    • A foreign key should always reference a unique key in the parent table. However in this example the composition of referenced columns is (at least) implicitly unique, because it contains the primary key. In almost any other RDBMS you would need to define the indices in the "middle" tables (branches and cashregisters) as UNIQUE. This however is not necessary in MySQL.
    • The composite foreign keys will take care of the data integrity/consistency. Example: If you have a branch entry with branch_id = 2 and customer_id = 1 - you wan't be able to insert a cashregister with branch_id = 2 and customer_id = 3, because this would violate the foreign key constraint.
    • You will probably need more indices for your queries. Most probably you will need cashregisters(branch_id) and transactions(cashregister_id). With these indices you might not even need to change your ORM relation code. (though AFAIK Yii supports composite foreign keys.)
    • You can define relations like "customer has many transactions". Previously you would need to use "has many through", involving two middle/bridge tables. This will save you two joins in many cases.

    If you want the redundant data to be maintained by the database, you can use the following triggers:

    create trigger cashregisters_before_insert
    before insert on cashregisters for each row
        set new.customer_id = (
            select b.customer_id
            from branches b
            where b.branch_id = new.branch_id
        )
    ;
    
    delimiter $$
    create trigger transactions_before_insert
    before insert on transactions for each row
    begin
        declare new_customer_id, new_branch_id int;
        select c.customer_id, c.branch_id into new_customer_id, new_branch_id
            from cashregisters c
            where c.cashregister_id = new.cashregister_id;
        set new.customer_id = new_customer_id;
        set new.branch_id   = new_branch_id;
    end $$
    delimiter ;
    

    Now you can insert new entries without defining the redundant values:

    insert into cashregisters (branch_id, cashregister_data) values
        (2, 'cashregister 1'),
        (1, 'cashregister 2');
    
    insert into transactions (cashregister_id, transaction_data) values
        (2, 'transaction 1'),
        (1, 'transaction 2');
    

    See demo: https://www.db-fiddle.com/f/fE7kVxiTcZBX3gfA81nJzE/0

    If your business logic allows to update the relations, you should extend your foreign keys with ON UPDATE CASCADE. This will make the changes through the relation chain down to the transactions table.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 关于#java#的问题:找一份能快速看完mooc视频的代码
  • ¥15 这种微信登录授权 谁可以做啊
  • ¥15 请问我该如何添加自己的数据去运行蚁群算法代码
  • ¥20 用HslCommunication 连接欧姆龙 plc有时会连接失败。报异常为“未知错误”
  • ¥15 网络设备配置与管理这个该怎么弄
  • ¥20 机器学习能否像多层线性模型一样处理嵌套数据
  • ¥20 西门子S7-Graph,S7-300,梯形图
  • ¥50 用易语言http 访问不了网页
  • ¥50 safari浏览器fetch提交数据后数据丢失问题
  • ¥15 matlab不知道怎么改,求解答!!