Relational Database Design:是否创建 Payment Type Entity
结论
如果支付方式长期只有 Cash 和 Card,一般不需要单独创建 TRANSACTION_TYPE 表。可以直接在 SALE 和 PURCHASE 表中保存支付方式,再用 CHECK 约束限定合法值。
如果支付方式以后可能增加,或者需要保存显示名称、手续费、启用状态等属性,再建立支付方式表并使用 foreign key 会更合适。
固定取值时的推荐设计
字段名建议使用 PAYMENT_TYPE 或 PAYMENT_METHOD。SALE_TYPE 更容易被理解成“销售类型”,无法准确表达支付方式。
CREATE TABLE SALE (
SALE_ID BIGINT PRIMARY KEY,
PAYMENT_TYPE VARCHAR(10) NOT NULL,
SALE_AMOUNT DECIMAL(12, 2) NOT NULL,
CUSTOMER_ID BIGINT NOT NULL,
CONSTRAINT CK_SALE_PAYMENT_TYPE
CHECK (PAYMENT_TYPE IN ('Cash', 'Card'))
);
CREATE TABLE PURCHASE (
PURCHASE_ID BIGINT PRIMARY KEY,
PAYMENT_TYPE VARCHAR(10) NOT NULL,
PURCHASE_AMOUNT DECIMAL(12, 2) NOT NULL,
CUSTOMER_ID BIGINT NOT NULL,
CONSTRAINT CK_PURCHASE_PAYMENT_TYPE
CHECK (PAYMENT_TYPE IN ('Cash', 'Card'))
);
这种设计有几个直接的好处:
- 表结构简单,显示支付方式时不需要额外执行
JOIN。 - 数据库会阻止
Cheque、cash等非法值写入。 - 查询结果可以直接阅读,不必根据数字 ID 判断支付方式。
SALE和PURCHASE可以遵循相同的业务规则。
例如:
SELECT SALE_ID, PAYMENT_TYPE, SALE_AMOUNT
FROM SALE
WHERE PAYMENT_TYPE = 'Card';
CHECK 约束需要分别定义在两个表中。应用层验证不能替代数据库约束,因为脚本、批量导入程序或其他服务也可能直接写入数据。
什么时候应该创建支付方式表
当支付方式不再是简单的固定枚举,而是需要单独管理的业务对象时,建立关联表更合理。例如,系统以后可能需要:
- 增加
BankTransfer、PayPal等支付方式; - 设置支付方式是否启用;
- 保存用户界面中的显示名称;
- 配置手续费率或结算规则;
- 记录第三方支付渠道;
- 避免逐一修改多个表中的
CHECK约束。
这时可以采用下面的设计:
CREATE TABLE PAYMENT_TYPE (
PAYMENT_TYPE_CODE VARCHAR(20) PRIMARY KEY,
DISPLAY_NAME VARCHAR(100) NOT NULL,
IS_ACTIVE BOOLEAN NOT NULL
);
初始化数据:
INSERT INTO PAYMENT_TYPE (
PAYMENT_TYPE_CODE,
DISPLAY_NAME,
IS_ACTIVE
)
VALUES
('CASH', 'Cash', TRUE),
('CARD', 'Card', TRUE);
业务表使用稳定的代码作为 foreign key:
CREATE TABLE SALE (
SALE_ID BIGINT PRIMARY KEY,
PAYMENT_TYPE_CODE VARCHAR(20) NOT NULL,
SALE_AMOUNT DECIMAL(12, 2) NOT NULL,
CUSTOMER_ID BIGINT NOT NULL,
CONSTRAINT FK_SALE_PAYMENT_TYPE
FOREIGN KEY (PAYMENT_TYPE_CODE)
REFERENCES PAYMENT_TYPE (PAYMENT_TYPE_CODE)
);
CREATE TABLE PURCHASE (
PURCHASE_ID BIGINT PRIMARY KEY,
PAYMENT_TYPE_CODE VARCHAR(20) NOT NULL,
PURCHASE_AMOUNT DECIMAL(12, 2) NOT NULL,
CUSTOMER_ID BIGINT NOT NULL,
CONSTRAINT FK_PURCHASE_PAYMENT_TYPE
FOREIGN KEY (PAYMENT_TYPE_CODE)
REFERENCES PAYMENT_TYPE (PAYMENT_TYPE_CODE)
);
使用 CASH、CARD 这类业务代码,通常比 TYPE_ID = 1、TYPE_ID = 2 这种没有业务含义的编号更直观。如果项目统一使用 surrogate key,也可以保留数值型 TYPE_ID,但最好同时为支付方式代码添加 UNIQUE 约束:
CREATE TABLE PAYMENT_TYPE (
TYPE_ID SMALLINT PRIMARY KEY,
TYPE_CODE VARCHAR(20) NOT NULL UNIQUE,
DISPLAY_NAME VARCHAR(100) NOT NULL
);
不建议只依赖普通字符串字段
下面这个字段没有数据库约束:
PAYMENT_TYPE VARCHAR(10) NOT NULL
因此,它可以写入拼写或大小写不一致的值:
Cash
cash
CARD
Credit Card
这些值在业务上可能表示同一种支付方式,但数据库会把它们视为不同的数据。无论是否建立关联表,都应该用 CHECK、foreign key 或数据库提供的枚举机制统一取值。
数据库原生 ENUM 也能处理固定取值,但不同数据库对 ENUM 的支持及修改方式差异较大。如果需要兼顾迁移便利性,VARCHAR 配合 CHECK 通常更通用。
选择原则
如果系统中的支付方式长期只有 Cash 和 Card,建议使用:
PAYMENT_TYPE VARCHAR(10)
CHECK (PAYMENT_TYPE IN ('Cash', 'Card'))
如果以后需要扩展支付方式,或者为其保存独立属性,则应改用 PAYMENT_TYPE 表和 foreign key。
判断依据不是取值有多少,而是支付方式本身是否需要独立的数据和生命周期。只有两个值,不等于必须单独建表;当前只有两个值,也不等于以后永远不需要建表。最终应根据业务是否需要管理这些值来决定。
备注:内容仅供参考。
