PAYATHON 2026

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。

判断依据不是取值有多少,而是支付方式本身是否需要独立的数据和生命周期。只有两个值,不等于必须单独建表;当前只有两个值,也不等于以后永远不需要建表。最终应根据业务是否需要管理这些值来决定。

备注:内容仅供参考。