对所有Yii2标准化怪胎的问题。
在Yii2中设置非标准化列的最佳位置在哪里?
例如,我有模型客户,分支,收银机和交易。
在一个完美的世界中,在一个完美标准化的数据库中,我们的 Transaction 模型将仅包含cashregister_id
, CashRegister 将存储branch_id
,并且分支将存储customer_id
。但是,由于性能问题,我们有时有时不得不使用未规范化 交易模型,其中包含以下内容:
创建交易时,我要存储所有3个值。设置
$transaction->branch_id = $transaction->cashRegister->branch_id;
$transaction->customer_id = $transaction->cashRegister->branch->customer_id;
但是在控制器中感觉不正确。
一种解决方案是在 Transaction 模型的 aftersave()中执行此操作,并将这些列设置为只读。但这似乎也更好,但并不完美。
我想知道设置这些重复列的最佳做法是什么,或者最好的位置是什么,以确保保持数据完整性?
答案 0 :(得分:1)
我曾经有过类似的问题,开始使用afterSave()
或beforeSave()
似乎是一个很好的解决方案,但最终导致难以维护意大利面条式代码。最后,我创建了单独的组件来管理这种关系。像这样:
class TransactionsManager extends Component {
public function createTransaction(TransactionInfo $info, CashRegister $register) {
// magic
}
}
然后,您不直接创建或更新Transaction
模型,而是始终使用此组件并将所有逻辑封装在其中。这样,ActiveRecord的工作方式就类似于数据表示形式,并且不包含任何高级业务逻辑。在某些情况下,它看起来比$model->load($data) && $model->save()
更为复杂,但毕竟,当您将所有逻辑都放在一个地方并且不需要调试save()
调用链(一个模型运行{ save()
中具有不同模型的{1}},其在afterSave()
中具有不同模型的save()
,依此类推)。
答案 1 :(得分:1)
以下是纯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)
);
注意:
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 支持复合外键。)如果希望数据库维护冗余数据,则可以使用以下触发器:
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
表。