Make NOT NULL约束仅适用于新行

sam*_*ris 4 sql oracle constraints

我将尝试将这种情况作为一个最小的例子:

假设我们有一张门票表,定义如下:

CREATE TABLE ticket_id_list (
   ticket_id  NUMBER,
   issued_to  VARCHAR2(100),
   CONSTRAINT ticket_id_list_pk PRIMARY KEY (ticket_id)
);
Run Code Online (Sandbox Code Playgroud)

系统使用了一段时间,数据被添加到表中:

INSERT INTO ticket_id_list (ticket_id, issued_to)
   VALUES (1, 'Arthur');
INSERT INTO ticket_id_list (ticket_id, issued_to)
   VALUES (2, 'Ford');
Run Code Online (Sandbox Code Playgroud)

之后,弹出以下要求:

此时我们需要在票证表中存储一个参考号给其他东西.引用必须为非null.但旧记录必须NULL值.

(愚蠢,好像这声音,这是一个真正的要求.)

现在,如果我们这样做:

ALTER TABLE ticket_id_list
   ADD ref_no VARCHAR2(6) NOT NULL;
Run Code Online (Sandbox Code Playgroud)

约束将立即被违反,我们将得到:ORA-01758: table must be empty to add mandatory (NOT NULL) column.

我们当然可以在业务逻辑中添加一个检查,但这很麻烦.由于无法解释的原因,我们无法使用默认值.有没有办法添加仅适用于新记录的NOT NULL约束?

Emu*_*Emu 7

您可以添加检查约束,其中某个日期之后的值不能为空; 就像是:

alter table ticket_id_list
  add constraint nul_ref
  check (ticket_id < 123456 or ref is not null);
Run Code Online (Sandbox Code Playgroud)

这确实假设ticket_id只会随着时间的推移而上升,如果你碰巧在你的表中有一个日期字段,那么使用它可能更清楚.


sam*_*ris 5

我发现了一个巧妙的方法:NOVALIDATE

添加一个可为空的列:

ALTER TABLE ticket_id_list
   ADD ref_no VARCHAR2(6);
Run Code Online (Sandbox Code Playgroud)

然后添加一个表约束,但一定要指定NOVALIDATE

ALTER TABLE ticket_id_list
   ADD CONSTRAINT new_ref_nonull CHECK(ref_no IS NOT NULL) NOVALIDATE;
Run Code Online (Sandbox Code Playgroud)

现在让我们检查一下:

INSERT INTO ticket_id_list (ticket_id, issued_to, ref_no)
   VALUES (3, 'Zaphod', '2A4252');
1 row inserted

INSERT INTO ticket_id_list (ticket_id, issued_to, ref_no)
   VALUES (4, 'Marvin', NULL);
ORA-02290: check constraint (NEW_REF_NONULL) violated
Run Code Online (Sandbox Code Playgroud)

正如预期的那样。

编辑NOVALIDATE仅当您不需要更新旧行时才有效。如果您更新旧行,则约束将失败。不过,对于这个例子,这不是问题。

  • 我不确定这是个好主意,因为您可能会欺骗优化器。它有时使用表约束来确定计划。它可能会被这样的事情搞糊涂:`SELECT * FROM ticket_id_list WHERE ref_no IS NULL`。我认为编写 INSERT 触发器会更安全。 (4认同)
  • 现在尝试更新一些具有“NULL”值的旧行。 (2认同)