【问题标题】:Make NOT NULL constraint apply only to new rows使 NOT NULL 约束仅适用于新行
【发布时间】:2014-08-01 05:38:52
【问题描述】:

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

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

CREATE TABLE ticket_id_list (
   ticket_id  NUMBER,
   issued_to  VARCHAR2(100),
   CONSTRAINT ticket_id_list_pk PRIMARY KEY (ticket_id)
);

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

INSERT INTO ticket_id_list (ticket_id, issued_to)
   VALUES (1, 'Arthur');
INSERT INTO ticket_id_list (ticket_id, issued_to)
   VALUES (2, 'Ford');

稍后,弹出以下要求:

从现在开始,我们需要在票证表中存储其他东西的参考号。引用必须是非空。但旧记录必须具有 NULL 值。

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

现在如果我们这样做:

ALTER TABLE ticket_id_list
   ADD ref_no VARCHAR2(6) NOT NULL;

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

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

【问题讨论】:

    标签: sql oracle constraints


    【解决方案1】:

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

    alter table ticket_id_list
      add constraint nul_ref
      check (ticket_id < 123456 or ref is not null);
    

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

    【讨论】:

      【解决方案2】:

      我发现了一个巧妙的技巧:NOVALIDATE

      添加一个可为空的列:

      ALTER TABLE ticket_id_list
         ADD ref_no VARCHAR2(6);
      

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

      ALTER TABLE ticket_id_list
         ADD CONSTRAINT new_ref_nonull CHECK(ref_no IS NOT NULL) NOVALIDATE;
      

      现在让我们检查一下:

      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
      

      正如预期的那样。

      编辑NOVALIDATE 仅在您不需要更新旧行时才有效。如果您更新了旧行,则约束将失败。不过,对于这种情况,这不是问题。

      【讨论】:

      • 现在尝试更新一些具有NULL 值的旧行。
      • @zerkms:好点。但不需要更新。参考编号不应更新。
      • 我不确定这是个好主意,因为您可能会欺骗优化器。它有时使用表约束来确定计划。它可能会被这样的事情弄糊涂:SELECT * FROM ticket_id_list WHERE ref_no IS NULL。我认为编写 INSERT 触发器会更安全。
      • @WW.:注意。但是 INSERT 触发器是 PL/SQL 代码。它不会比约束慢吗?我想我必须做一个测试来比较这两种情况。
      • 是的,触发器会更慢。当有人描述该列不为空的表时,它也不那么明显。
      【解决方案3】:

      您必须将此要求视为业务逻辑,因为在您的情况下它是。

      然后在触发器中实现这个业务逻辑。

      CREATE TRIGGER t_ticket_id_list
          BEFORE INSERT ON ticket_id_list
          FOR EACH ROW
            BEGIN
               IF (:NEW is null) THEN
                  RAISE_APPLICATION_ERROR(-20001, 'Cannot Insert 1 in this table');
               END IF;
            END;
      

      注意 - SQL Server 也可以这样做MSDN Create trigger documentations

      【讨论】:

      • 谢谢,但问题被标记为oracle。这是 SQL Server。
      • 糟糕,我的错。但对于 oracle 来说也是如此。为它编辑。
      【解决方案4】:

      如果您想将约束设置为 UNIQUE 列,那么它将允许所有以前的值都可以为空,但所有新值都应该是唯一的,并且它的默认值应该是 Null。

      但是如果你想将表列设置为 NOT NULL,它应该包含默认值或任何值都应该隐式指定。

      【讨论】:

        猜你喜欢
        • 2013-04-12
        • 1970-01-01
        • 1970-01-01
        • 2015-04-22
        • 1970-01-01
        • 2018-08-24
        • 2022-01-19
        • 2021-11-16
        • 2020-12-18
        相关资源
        最近更新 更多