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

lzfw57am  于 2021-06-20  发布在  Mysql
关注(0)|答案(2)|浏览(355)

向所有yii2标准化极客提问。
在yii2中设置非规范化列的最佳位置是哪里?
例如,我有客户模型、分行模型、收银机模型和交易模型。在一个完美的世界中,在一个完美规范化的数据库中,我们的事务模型只有 cashregister_id ,收银机将存储 branch_id ,分支将存储 customer_id . 但是,由于性能问题,我们有时不得不使用非规范化的交易模型,其中包含以下内容:
收银机id
分支机构id
客户id
创建事务时,我要存储所有3个值。设置

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

但是在控制器中感觉不正确。
一种解决方案是在事务模型的aftersave()中执行此操作,并使这些列为只读。但这似乎更好,但并不完美。
我想知道什么是最佳实践,或者在哪里设置这些重复列以确保数据完整性的最佳位置?

bfnvny8b

bfnvny8b1#

我也有过类似的问题 afterSave() 或者 beforeSave() 起初看起来是一个很好的解决方案,但最终导致难以维护意大利面条代码。我最终创建了单独的组件来管理这种关系。比如:

class TransactionsManager extends Component {

    public function createTransaction(TransactionInfo $info, CashRegister $register) {
        // magic
    }
}

那你不是在创建或更新 Transaction 直接建模,您总是使用此组件并封装其中的所有逻辑。activerecord的工作原理更像是一种数据表示,不包含任何高级业务逻辑。在某些情况下,它看起来比 $model->load($data) && $model->save() 但毕竟,当您将所有逻辑放在一个地方并且不需要调试时,维护起来就容易多了 save() 调用链(一个模型运行) save() 不同型号的 afterSave() 它运行 save() 不同型号的 afterSave() ... 以此类推)。

rjzwgtxy

rjzwgtxy2#

下面是一个只支持db的解决方案。
我想你们的关系是:
一个客户有很多分支机构
分行有许多收银机
收银机有许多交易
相应的模式可以是:

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

(注意:这应该是你问题的一部分-因此我们不需要猜测。)
如果要包含冗余列( branch_id 以及 customer_id )在 transactions 表中,您应该使它们成为外键的一部分。但首先你需要包括一个 customer_id 中的列 cashregisters 表,并使其成为外键的一部分。
扩展模式为:

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

笔记:
任何外键约束都需要子表(引用)和父表(引用)中的索引,以支持约束检查。键中给定的列顺序允许我们定义每个表只有一个索引的模式。
外键应始终引用父表中的唯一键。但是在这个例子中,引用列的组合(至少)是隐式唯一的,因为它包含主键。在几乎任何其他rdbms中,您都需要在“中间”表中定义索引( branches 以及 cashregisters )作为 UNIQUE . 然而,这在mysql中不是必需的。
复合外键将负责数据的完整性/一致性。示例:如果您有一个分支条目 branch_id = 2 以及 customer_id = 1 -你不能把收银机 branch_id = 2 以及 customer_id = 3 ,因为这将违反外键约束。
您可能需要更多的索引来进行查询。很可能你需要 cashregisters(branch_id) 以及 transactions(cashregister_id) . 使用这些索引,您甚至可能不需要更改orm关系代码(尽管afaik yii支持复合外键。)
您可以定义关系,如“客户有许多交易”。以前需要使用“has many through”,包括两个中间/桥接表。在许多情况下,这将节省两个连接。
如果希望数据库维护冗余数据,可以使用以下触发器:

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 ;

现在您可以插入新条目,而无需定义冗余值:

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');

请参见演示:https://www.db-fiddle.com/f/fe7kvxitczbx3gfa81njze/0
如果您的业务逻辑允许更新关系,那么应该使用 ON UPDATE CASCADE . 这将通过关系链向下到 transactions table。

相关问题