Sql Oracle-部分可空的复合外键
我有一个遗留的Oracle数据库,它有一个我想理解的奇怪的怪癖。它有一个复合外键,某些列可以为空。对我来说,这听起来像是一个粗心的开发人员的糟糕设计,但我想征求意见。当然,最初的开发团队早已不复存在 表中的列要大得多,但我认为我能够在下面的示例中提取问题:Sql Oracle-部分可空的复合外键,sql,oracle,foreign-keys,Sql,Oracle,Foreign Keys,我有一个遗留的Oracle数据库,它有一个我想理解的奇怪的怪癖。它有一个复合外键,某些列可以为空。对我来说,这听起来像是一个粗心的开发人员的糟糕设计,但我想征求意见。当然,最初的开发团队早已不复存在 表中的列要大得多,但我认为我能够在下面的示例中提取问题: create table quadrant ( region number(9) not null, area number(9) not null, caption varchar2(20), primary key (re
create table quadrant (
region number(9) not null,
area number(9) not null,
caption varchar2(20),
primary key (region, area)
);
insert into quadrant (region, area, caption) values (10, 123, 'Chicago');
insert into quadrant (region, area, caption) values (10, 125, 'Wisconsin');
create table farm (
id number(9),
region_id number(9) not null,
area_id number(9),
name varchar2(50),
constraint fk_region_area foreign key (region_id, area_id)
references quadrant (region, area)
);
insert into farm (id, region_id, area_id, name) values (5, 10, null, 'farm 1');
insert into farm (id, region_id, area_id, name) values (6, 11, null, 'farm 2');
select * from farm;
结果:
ID REGION_ID AREA_ID NAME
-- --------- ------- ------
5 10 <null> farm 1 <-- Does it point to anything?
6 11 <null> farm 2 <-- Region 11 doesn't even exist!
ID区域\u ID区域\u ID名称
-- --------- ------- ------
5 10农场1关于使用空表示某事,存在很多争论。有些人认为null表示该值未知或表示无效,其他人则认为它本身就是一个实际值。我怀疑在这种情况下,它代表未知。假设您记录了100年前一个县的农场位置。使用一些当地的历史书籍,你已经绘制出了那个时期70%的现有农场及其确切边界(或大约),但对于剩下的30%,有些已经知道了地区,有些只知道存在。在这种情况下,我肯定会说空外键是有意义的。这仅仅是未知信息。对于您的“功能”有一些猜测:也许是区域字段仅适用于某些农场?示例:指定区域的农场需要支付一些附加费或税费(在这里猜测,因为我不知道您的数据)?在这种情况下,NULL表示某种东西(不需要支付)。也许在实施“区域”之前就存在农场,因此从未分配过农场?在这种情况下,NULL实际上意味着NULL,因为该区域从未存在过,因此是未知的。我不知道它对您的数据模型是否有意义,但对于部分NULL外键有明确的用例
考虑一个简单的固定资产表(计算机、汽车、建筑等——会计会折旧的东西)。假设他们想知道资产在哪里使用,所以他们有两列:company\u id
和department\u id
有些资产,如建筑物,可能在各个部门之间共享,因此我希望使用外键,如(123,null)
。我还希望在COMPANY\u ID
上的COMPANY
表中有一个单独的外键
这种设置的含义是,公司id必须是已知值,公司/部门组合(如果存在)必须是已知组合
更新
我不知道你为什么认为甲骨文不能做我描述的事情。下面是一个简单的测试:
CREATE TABLE tst_company
( company_id NUMBER NOT NULL PRIMARY KEY );
CREATE TABLE tst_department
( company_id NUMBER NOT NULL,
department_id NUMBER NOT NULL,
CONSTRAINT tst_department_pk PRIMARY KEY ( company_id, department_id ),
CONSTRAINT tst_department_f1 FOREIGN KEY ( company_id ) REFERENCES tst_company ( company_id ) );
CREATE TABLE tst_asset
( asset_id NUMBER NOT NULL PRIMARY KEY,
company_id NUMBER NOT NULL,
department_id NUMBER,
CONSTRAINT tst_asset_f1 FOREIGN KEY ( company_id ) REFERENCES tst_company ( company_id ),
CONSTRAINT tst_asset_f2 FOREIGN KEY ( company_id, department_id ) REFERENCES tst_department ( company_id, department_id ) );
INSERT INTO tst_company ( company_id ) VALUES (1);
INSERT INTO tst_department ( company_id, department_id ) VALUES (1, 10);
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1001, 1, 10); -- Department specific asset
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1002, 1, NULL); -- Non-department specific asset
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1003, 2, NULL); -- Bad company - fails
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1004, 1, 11); -- Bad department - fails
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1005, 2, 11); -- Bad company AND department - fails
感谢所有的回答和评论。这个问题迫使我学习一些新的东西,这是一件好事@菲利普西给了我一个大线索。我想重述一下我学到的东西,因为它可能对其他人有用,这是一个记录它的好地方
这个问题有两个方面:第一,部分空外键意味着什么,第二,它是如何实现的
部分空外键的含义
正如@agiles231所指出的,关于这意味着什么,有很多争论NULL
可能意味着:
- 值是未知的
- 其他人说这意味着该值是无效的
- 其他人说,
NULL
本身就是一个真正的值李>
简而言之,到目前为止,它的含义还没有明确的答案
我猜根据人们如何解释空值,在外键中使用它们(以及验证它们)的策略可能会有所不同
部分空外键的实现
定义了(第4.10.2节)将复合外键与可空值匹配的三种不同方式:
- MatchSIMPLE:如果复合外键的任何列为null,则接受、存储外键,但不根据引用的表进行验证。这通常是数据库提供的默认模式。在SQL-92标准中,描述了此模式,但未命名
- MatchPARTIAL:如果复合外键的任何列为null,则将每个非null列与引用表匹配,以检查至少有一行存在该值。我没有看到任何数据库实现这种模式
- 完全匹配:不接受部分空外键。外键为完全空或完全不为空。如果为null,则不会对引用的表进行验证。如果不为null,则根据引用的表对其进行完全验证。这是我所期望的默认行为(在我幸福的无知中)
我检查了10个不同的数据库是如何实现这些模式的,下面是我的发现:
Database Engine Match SIMPLE Match PARTIAL Match FULL
--------------- ------------ ------------- ----------
Oracle 12c1 YES*1 NO NO
DB2 10.5 YES*1 NO NO
PostgreSQL 10 YES*1 NO YES
SQL Server 2014 YES*1 NO NO
MariaDB 10.3 YES*1 NO*2 NO*2
MySQL 8.0 YES*1 NO*2 NO*2
Sybase ASE 16 YES*1 NO YES
H2 1.4 YES*1 NO NO
Derby 10.13 YES*1 NO NO
HyperSQL 2.3 YES*1 NO YES
*1这是默认模式
*2在创建表时接受,但被忽略
简言之:
- 默认情况下,所有已测试的数据库的行为方式相同:它们默认匹配简单数据库
- 我测试的任何数据库都不支持部分匹配。我想这是有道理的,因为我个人觉得它没什么用处。此外,在不创建引用表上所有可能的索引组合的情况下,对单独的外键列执行部分验证可能会变得非常昂贵
- PostgreSQL实现了完全匹配和Sybase ASE。这是个好消息!令人惊讶的是,HyperSQL(这个小小的数据库)也是如此
实施完全匹配的变通方法
好消息是,如果您碰巧需要,可以在任何测试过的数据库中实现完全匹配,这是一个相当简单的解决方法。只需添加一个表约束,该约束允许所有空列或所有非空列。比如:
create table farm (
id int,
region_id int,
area_id int,
name varchar(50),
constraint fk_region_area foreign key (region_id, area_id)
references quadrant (region, area),
constraint fkfull_region_area check ( -- here's the workaround
region_id is null and area_id is null or
region_id is not null and area_id is not null)
);
insert into farm (id, region_id, area_id, name) values (5, 10, null, 'farm 1'); -- fails
insert into farm (id, region_id, area_id, name) values (6, 11, null, 'farm 2'); -- fails
insert into farm (id, region_id, area_id, name) values (7, 10, 125, 'farm 3'); -- succeeds
insert into farm (id, region_id, area_id, name) values (8, null, null, 'farm 4'); -- succeeds
它工作得很好
最后,作为一个非常个人的观点,我会