
原文地址:https://www.bytebase.com/blog/openai-vs-deepseek-ssn-database-schema-design

马斯克关于 SSN 数据库去重的评论在开发者群体中引发了巨大的争论,许多软件工程师对他的言论不屑一顾。让我们看看 OpenAI 和 DeepSeek 是如何设计这个 SSN 数据库 schema 的。

什么是 SSN?
给不在美国生活的读者介绍一下 -- SSN 就是社会安全号码,一个用于识别美国境内个人身份的九位数字。它的主要用途有:
收入识别:雇主使用 SSN 向政府报告雇员的工资和薪金,确保收入得到正确记录。
收税:美国国税局要求在报税(包括所得税申报)时使用 SSN,以跟踪个人纳税义务和预扣税款情况。
社会福利分配:政府机构使用 SSN 确定资格并分配福利(如社会保障、医疗保险、医疗补助和失业救济)。
信用评分:放款人、银行和信用局使用 SSN 跟踪个人的信用记录、贷款申请和债务偿还行为,从而形成信用评分的基础。
在美国,SSN 是高度敏感的信息。有这样一种说法:结婚誓言无法证明真爱;信任伴侣到能分享 SSN 的程度,才能证明是真爱。

第一回合
提示词:
You are a relational database schema designer. You are given the task to designthe schema for the United States' Social Security Number (SSN) database.The database needs to track basic personal information and payment profile.Minimal design, no whistles and bells. Generate Postgres SQL dialect.
你是一名关系数据库 schema 设计员。你的任务是设计美国社会保障号(SSN)数据库的 schema。该数据库需要跟踪个人基本信息和支付情况。
最小化设计,不要花里胡哨。使用 Postgres SQL 语言生成代码。
ChatGPT 4o
CREATE TABLE person (-- SSN is always 9 digits (without dashes)ssn CHAR(9) PRIMARY KEY,first_name VARCHAR(50) NOT NULL,last_name VARCHAR(50) NOT NULL,date_of_birth DATE NOT NULL,-- M: Male, F: Female, O: Othergender CHAR(1) CHECK (gender IN ('M', 'F', 'O')),address TEXT NOT NULL);CREATE TABLE payment_profile (ssn CHAR(9) PRIMARY KEY REFERENCES person(ssn) ON DELETE CASCADE,-- Basic validation for routing numberbank_routing_number CHAR(9) NOT NULL CHECK (bank_routing_number ~ '^[0-9]{9}$'),bank_account_number VARCHAR(20) NOT NULL,payment_method VARCHAR(10) CHECK (payment_method IN ('ACH', 'Check')) NOT NULL,last_payment_date DATE);CREATE INDEX idx_person_last_name ON person(last_name);CREATE INDEX idx_payment_method ON payment_profile(payment_method);
person.ssn 是主键,确保唯一性。
person.date_of_birth 确保有效的日期输入。
person.gender 是单字符字段,有约束条件(M、F、O)。
payment_profile(付款档案)引用 person.ssn(ON DELETE CASCADE,确保在删除某人时删除付款档案)。
payment_profile.bank_routing_number(银行路由号码)有一个 regex 约束,以确保它正好包含 9 位数字。
payment_profile.payment_method(付款方式)只允许使用 ACH 或支票。
关于 last_name 和 payment_method 的索引优化了常见的查找。
ChatGPT 将性别设计为非二元选项,使用 O 表示其他性别,令人惊喜。
DeepSeek R1
-- Table to store personal informationCREATE TABLE person (ssn CHAR(9) PRIMARY KEY, -- Social Security Number (9 digits, no hyphens)first_name VARCHAR(50) NOT NULL, -- First namelast_name VARCHAR(50) NOT NULL, -- Last namedate_of_birth DATE NOT NULL, -- Date of birthgender CHAR(1) NOT NULL, -- Gender (M/F/O for Male, Female, Other)created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);-- Table to store payment profilesCREATE TABLE payment_profile (id SERIAL PRIMARY KEY, -- Unique payment profile IDssn CHAR(9) NOT NULL REFERENCES person(ssn) ON DELETE CASCADE, -- Foreign key to persontotal_contributions DECIMAL(12, 2) DEFAULT 0.00, -- Total contributions madelast_payment_date DATE, -- Date of the last paymentlast_payment_amount DECIMAL(10, 2), -- Amount of the last paymentcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);
DeepSeek 的一个缺点是没有使用列检查来确保数据的有效性。

第二回合 - 改名者的新账户
提示词:
A person can change the name (e.g. after marriage)
个人可以更改姓名(例如:婚后改姓)
ChatGPT 4o
CREATE TABLE person (ssn CHAR(9) PRIMARY KEY, -- Immutable identifierdate_of_birth DATE NOT NULL,gender CHAR(1) CHECK (gender IN ('M', 'F', 'O')), -- M: Male, F: Female, O: Otheraddress TEXT NOT NULL);CREATE TABLE person_name (id SERIAL PRIMARY KEY,ssn CHAR(9) NOT NULL REFERENCES person(ssn) ON DELETE CASCADE,first_name VARCHAR(50) NOT NULL,last_name VARCHAR(50) NOT NULL,is_current BOOLEAN NOT NULL DEFAULT TRUE, -- Tracks the current namechanged_at TIMESTAMP DEFAULT NOW() -- Records when the name was changed);CREATE TABLE payment_profile (ssn CHAR(9) PRIMARY KEY REFERENCES person(ssn) ON DELETE CASCADE,bank_routing_number CHAR(9) NOT NULL CHECK (bank_routing_number ~ '^[0-9]{9}$'), -- 9-digit validationbank_account_number VARCHAR(20) NOT NULL,payment_method VARCHAR(10) CHECK (payment_method IN ('ACH', 'Check')) NOT NULL,last_payment_date DATE);-- Ensure only one current name per personCREATE UNIQUE INDEX idx_person_name_unique ON person_name(ssn) WHERE is_current = TRUE;CREATE INDEX idx_person_name_last_name ON person_name(last_name);CREATE INDEX idx_payment_method ON payment_profile(payment_method);
将姓名分到 person_name 中: 允许每个人有多个历史姓名。 使用 is_current 追踪当前与过去的姓名(使用唯一索引强制执行)。 使用 changed_at 记录姓名更改的时间。
保持 SSN 不变: person 表存储不会改变的属性(SSN、出生日期、性别、地址)。
姓名历史记录单独处理。 索引增强: idx_person_name_unique 可确保每个 SSN 只有一条 is_current = TRUE 记录。
idx_person_name_last_name 可以有效地按姓氏进行搜索。
-- Table to store personal informationCREATE TABLE person (ssn CHAR(9) PRIMARY KEY, -- Social Security Number (9 digits, no hyphens)first_name VARCHAR(50) NOT NULL, -- Current first namelast_name VARCHAR(50) NOT NULL, -- Current last namedate_of_birth DATE NOT NULL, -- Date of birthgender CHAR(1) NOT NULL, -- Gender (M/F/O for Male, Female, Other)created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);-- Table to store historical name changesCREATE TABLE name_history (id SERIAL PRIMARY KEY, -- Unique identifier for each name change recordssn CHAR(9) NOT NULL REFERENCES person(ssn) ON DELETE CASCADE, -- Foreign key to personfirst_name VARCHAR(50) NOT NULL, -- First name at the time of the changelast_name VARCHAR(50) NOT NULL, -- Last name at the time of the changechange_date DATE NOT NULL, -- Date of the name changecreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);-- Table to store payment profilesCREATE TABLE payment_profile (id SERIAL PRIMARY KEY, -- Unique payment profile IDssn CHAR(9) NOT NULL REFERENCES person(ssn) ON DELETE CASCADE, -- Foreign key to persontotal_contributions DECIMAL(12, 2) DEFAULT 0.00, -- Total contributions madelast_payment_date DATE, -- Date of the last paymentlast_payment_amount DECIMAL(10, 2), -- Amount of the last paymentcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);
DeepSeek 这次解释了设计的优势:
当前姓名:person 表总是反映最新的姓名。 历史跟踪:name_history 表跟踪所有姓名随时间发生的变化。 数据完整性:外键和 ON DELETE CASCADE 确保了数据的一致性。
DeepSeek 在这里作出了不同的设计选择:person 表仍然保留 first_name 和 last_name 列,只是被重新用于存储当前姓名。

第三回合 - 添加代理密钥
提示词:
Using SSN as the primary key could expose security risk as SSN is sensitive.
使用 SSN 作为主键可能会暴露安全风险,因为 SSN 是敏感信息。
ChatGPT 4o
CREATE TABLE person (person_id SERIAL PRIMARY KEY, -- Surrogate key for securityssn CHAR(9) UNIQUE NOT NULL CHECK (ssn ~ '^[0-9]{9}$'), -- Enforced uniqueness and validationdate_of_birth DATE NOT NULL,gender CHAR(1) CHECK (gender IN ('M', 'F', 'O')), -- M: Male, F: Female, O: Otheraddress TEXT NOT NULL);CREATE TABLE person_name (id SERIAL PRIMARY KEY,person_id INT NOT NULL REFERENCES person(person_id) ON DELETE CASCADE,first_name VARCHAR(50) NOT NULL,last_name VARCHAR(50) NOT NULL,is_current BOOLEAN NOT NULL DEFAULT TRUE, -- Tracks the current namechanged_at TIMESTAMP DEFAULT NOW() -- Records when the name was changed);CREATE TABLE payment_profile (person_id INT PRIMARY KEY REFERENCES person(person_id) ON DELETE CASCADE,bank_routing_number CHAR(9) NOT NULL CHECK (bank_routing_number ~ '^[0-9]{9}$'), -- 9-digit validationbank_account_number VARCHAR(20) NOT NULL,payment_method VARCHAR(10) CHECK (payment_method IN ('ACH', 'Check')) NOT NULL,last_payment_date DATE);-- Ensure only one current name per personCREATE UNIQUE INDEX idx_person_name_unique ON person_name(person_id) WHERE is_current = TRUE;CREATE INDEX idx_person_name_last_name ON person_name(last_name);CREATE INDEX idx_payment_method ON payment_profile(payment_method);
代理主键(person_id): 取代 ssn 作为主键,降低暴露风险。
使外键关系参考 person_id,而不是 ssn。 SSN 保持唯一并经过验证: UNIQUE NOT NULL 约束确保没有重复。
正则表达式约束(CHECK (ssn ~ '^[0-9]{9}$')) 确保格式有效。 外键使用 person_id: person_name 和 payment_profile 引用 person_id 而不是 ssn。
避免在多个地方暴露 SSN。
DeepSeek R1
-- Table to store personal informationCREATE TABLE person (id SERIAL PRIMARY KEY, -- Surrogate key (auto-incrementing integer)ssn CHAR(9) NOT NULL UNIQUE, -- Social Security Number (9 digits, no hyphens)first_name VARCHAR(50) NOT NULL, -- Current first namelast_name VARCHAR(50) NOT NULL, -- Current last namedate_of_birth DATE NOT NULL, -- Date of birthgender CHAR(1) NOT NULL, -- Gender (M/F/O for Male, Female, Other)created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);-- Table to store historical name changesCREATE TABLE name_history (id SERIAL PRIMARY KEY, -- Unique identifier for each name change recordperson_id INT NOT NULL REFERENCES person(id) ON DELETE CASCADE, -- Foreign key to personfirst_name VARCHAR(50) NOT NULL, -- First name at the time of the changelast_name VARCHAR(50) NOT NULL, -- Last name at the time of the changechange_date DATE NOT NULL, -- Date of the name changecreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);-- Table to store payment profilesCREATE TABLE payment_profile (id SERIAL PRIMARY KEY, -- Unique payment profile IDperson_id INT NOT NULL REFERENCES person(id) ON DELETE CASCADE, -- Foreign key to persontotal_contributions DECIMAL(12, 2) DEFAULT 0.00, -- Total contributions madelast_payment_date DATE, -- Date of the last paymentlast_payment_amount DECIMAL(10, 2), -- Amount of the last paymentcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Record creation timestamp);
出于安全的重要更改:
person 表中的代理键(id):
person 表现在使用自动递增整数(id)作为主键。 ssn 列仍然存储,但不再是主键。它被标记为 UNIQUE,以确保没有两个人共享相同的 SSN。
外键引用:
name_history 表和 payment_profile 表现在使用 person_id(代理键)而不是 ssn 来引用人员表。
SSN 存储:
SSN 列安全地存储在人员表中。在实际应用中,应考虑加密该列或使用其他安全措施(如散列或标记化)来保护敏感数据。

总结
它们最初都能设计出合理的 schema。
它们都能根据后续提示更改设计。在上面的例子中,两者都能纳入姓名变更并引入代理密钥来解决安全问题。
将 gender 列设计为非二元选项。
使用列检查确保数据有效性。
在经常被查询的列上包含索引。
解释设计选择并证明设计决策的合理性。
我个人喜欢它精炼 person 表的设计,因为这样可以返回当前姓名,使查询更加方便。 解释简洁明了,切中要害。

写在最后的感想
如上所述,schema 设计涉及各种权衡。确定唯一 SSN 是否是一个好的设计选择需要视情境而定。即使是最先进的模型,也仍然需要人工指导才能生成高质量的 schema 设计,以适应现实世界中的使用情况(如姓名变更)并解决安全问题(如 SSN 暴露)。

Bytebase签约Dwango,助力B站弹幕文化鼻祖优化数据库变更及查询操作







