我刚开始学习 SQL(使用 SQLite),我试图弄清楚什么时候应该使用外键。向我解释的方式是,只要出现重复数据,就应该使用外键,只需保存 ID 以节省空间。我正在创建的数据库中有几千条记录,列出了类别和县(每列中可能有几十个唯一值)。所以我可以用县名和主键 id 为县制作一个单独的表,并对类别做同样的事情。我毫不怀疑它会使数据库缩小约 5%。但这是唯一的好处吗?似乎它使其他一切变得更加复杂。为原本不需要的县和类别添加 ID。在 phpLiteAdmin 中查看表格时,它只显示一个数字而不是类别/县名,使其更难以可视化。在这种情况下使用外键并制作单独的表有什么好处?还是我不应该这样做,而是将所有数据(重复和所有数据)都放在一张表中?另外 - 让县/类别表只有一列没有数字主键是否有意义,因为无论如何它们都是唯一的?这至少会在 phpLiteAdmin 中显示全名。提前致谢!在这种情况下使用外键并制作单独的表有什么好处?还是我不应该这样做,而是将所有数据(重复和所有数据)都放在一张表中?另外 - 让县/类别表只有一列没有数字主键是否有意义,因为无论如何它们都是唯一的?这至少会在 phpLiteAdmin 中显示全名。提前致谢!在这种情况下使用外键并制作单独的表有什么好处?还是我不应该这样做,而是将所有数据(重复和所有数据)都放在一张表中?另外 - 让县/类别表只有一列没有数字主键是否有意义,因为无论如何它们都是唯一的?这至少会在 phpLiteAdmin 中显示全名。提前致谢!
5 回答
如果您使用外键。它也称为参照完整性。
假设您有两个表,第一个表是 account_user,第二个表是 account_user_detail。因此 account_user 表将具有 account_id 的 account_number 的主键。account_user_detail 表将包含帐户持有人地址详细信息。因此,如果您将两个表关联起来,那么 account_number 或 account_id 将是相同的。所以使用第二个表中的主键值我们定义外键。外键标识第二个表中 account_number 的值是第一个表中具有相同帐号的 Xyz 先生的引用。
因此,外键用于将两个表与两个表共有的列连接起来,并共享相同的唯一值。
但这是唯一的好处吗?
不。
外键在逻辑上类似于大多数编程语言中的指针或引用。想象一下,试图通过复制数据来创建一些数据结构,而不能引用任何东西。没有外键的数据库也会有同样的问题。
如果没有引用事物的能力,您必须确保所有副本都是最新的。如果存在导致一个副本被更新但另一个副本没有更新的错误,那将有效地破坏数据——您将不再知道哪个副本是正确的。
避免冗余主要不是关于空间,而是关于数据完整性。数据库规范化(没有外键就无法完成)的全部目的是避免冗余,从而保护数据完整性。
在您的特定情况下...
- 一个类别(或国家)是否应该能够在不连接到主表中的任何行的情况下存在?
- 一个类别是否应该存在任何数据,独立于该类别连接到主表中的哪些行?
- 是否有任何操作(如重命名)应该独立完成?
如果任一答案为“是”,则应将类别放入单独的查找表中。此查找表是否应该使用自然(名称)或代理(ID)键是一个不同的问题。这里列出了一些优点和缺点。
外键约束用于限制允许存在于一列或一组列中的值。以婚姻为例:
CREATE TABLE person
(person_id INTEGER NOT NULL PRIMARY KEY
, name varchar NOT NULL
);
CREATE TABLE marriage
( person1 INTEGER NOT NULL PRIMARY KEY
, person2 INTEGER NOT NULL UNIQUE
, comment varchar
, CONSTRAINT marriage_1 FOREIGN KEY (person1) REFERENCES person(person_id)
, CONSTRAINT marriage_2 FOREIGN KEY (person2) REFERENCES person(person_id)
, CONSTRAINT order_in_court CHECK (person1 < person2)
);
-- add some data ...
INSERT INTO person(person_id,name) values (1,'Bob'),(2,'Alice'),(3,'Charles');
INSERT INTO marriage(person1,person2, comment) VALUES(1,2, 'Crypto marriage!') ; -- Ok
INSERT INTO marriage(person1,person2, comment) VALUES(2,1, 'Not twice!' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(3,3, 'No you dont...' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(2,3, 'OMG she did it again.' ) ; -- Should fail (does not)
INSERT INTO marriage(person1,person2, comment) VALUES(3,4, 'Non existant persons are not allowed to marry !' ) ; -- Should fail
SELECT p1.name, p2.name, m.comment
FROM marriage m
JOIN person p1 ON m.person1 = p1.person_id
JOIN person p2 ON m.person2 = p2.person_id
;
上面的 DDL 尝试对婚姻进行建模(并且部分失败)要建模的约束是:
- 只有现有的人可以结婚
- 婚姻只能存在于两个不同的人之间
- 一个人只能结一次婚
输出:
INSERT 0 3
INSERT 0 1
ERROR: new row for relation "marriage" violates check constraint "order_in_court"
ERROR: new row for relation "marriage" violates check constraint "order_in_court"
INSERT 0 1
ERROR: insert or update on table "marriage" violates foreign key constraint "marriage_2"
DETAIL: Key (person2)=(4) is not present in table "person".
name | name | comment
-------+---------+-----------------------
Bob | Alice | Crypto marriage!
Alice | Charles | OMG she did it again.
(2 rows)
如果您的国家名称是“美利坚合众国”,则为 24 字节。如果使用外键,则只需要 2-4 个字节。那是一个巨大的差异。
当您搜索国家名称时,它会非常快,因为您只需匹配一个数字而不是整个字符串。
此外,如果您在 country_id 字段上使用索引,它会变得更小。
我能理解你关于增加复杂性的观点。在您的情况下,您可以不使用外键,但您不应该这样做。您最终将需要它们,以便更好地准备和体验该主题。