仅对新行使用 CHECK 约束对一组列强制执行 NOT NULL

9 postgresql null database-design postgresql-9.2 check-constraints

我有一个表,需要添加一个没有默认值的新列:

约束:

ALTER TABLE integrations.billables 
DROP CONSTRAINT IF EXISTS cc_at_least_one_mapping_needed_billables,
ADD CONSTRAINT cc_at_least_one_mapping_needed_billables 
CHECK ((("qb_id" IS NOT NULL) :: INTEGER +
    ("xero_id" IS NOT NULL) :: INTEGER +
    ("freshbooks_id" IS NOT NULL) :: INTEGER +
    ("unleashed_id" IS NOT NULL) :: INTEGER +
    ("csv_data" IS NOT NULL) :: INTEGER +
    ("myob_id" IS NOT NULL) :: INTEGER) > 0);
Run Code Online (Sandbox Code Playgroud)

柱子:

ALTER TABLE integrations.billables
DROP COLUMN IF EXISTS myob_id,
ADD COLUMN myob_id varchar(255);
Run Code Online (Sandbox Code Playgroud)

题:

如何为下一个值添加约束,而不是为那些已经存在的值添加约束?(否则我会得到某些行违反了错误检查约束“”)。

这与我之前的问题有关:错误:某些行违反了检查约束

And*_*y M 10

如果您有一serial列或一个自动填充的整数nextval(这样您就永远不应该为该列插入具有显式值的新行),您还可以检查该列的值是否大于特定值:

(
  (("qb_id" IS NOT NULL) :: INTEGER +
  ("xero_id" IS NOT NULL) :: INTEGER +
  ("freshbooks_id" IS NOT NULL) :: INTEGER +
  ("unleashed_id" IS NOT NULL) :: INTEGER +
  ("csv_data" IS NOT NULL) :: INTEGER +
  ("myob_id" IS NOT NULL) :: INTEGER) > 0
  OR
  YourSerialColumn <= value
)
Run Code Online (Sandbox Code Playgroud)

其中value应该currval在更改/重新创建约束时根据列/相应序列确定。

这样,IS NOT NULL检查将仅适用于YourSerialColumn值大于的行value

笔记

这可以看作是David Spillet 建议的变体。主要区别在于该解决方案不需要结构更改(分区)。但是,这两个选项都依赖于表中是否存在合适的列。如果没有这样的列,而您可以专门添加它来解决此问题,那么采用分区的想法可能是这两者中更好的选择。


a_h*_*ame 9

只需将约束添加为 NOT VALID

从手册:

如果约束被标记为NOT VALID,则跳过可能冗长的初始检查以验证表中的所有行是否满足约束。该约束仍将针对后续插入或更新强制执行(即 [...] 并且除非新行与指定的检查约束匹配,否则它们将失败)

  • 我在这里看到的一个潜在陷阱是,如果您打算让该列在以后的现有行中保持为空,那么您也永远无法更新它们的任何其他列。 (4认同)

jpm*_*c26 5

将所有现有行标记为旧:

ALTER TABLE integrations.billables
ADD COLUMN is_old BOOLEAN NOT NULL DEFAULT false;

UPDATE integrations.billables SET is_old = true;
Run Code Online (Sandbox Code Playgroud)

并设置约束以忽略旧行:

ALTER TABLE integrations.billables
ADD CONSTRAINT cc_at_least_one_mapping_needed_billables 
CHECK (
    NOT(("qb_id", "xero_id", "freshbooks_id", "unleashed_id", "csv_data", "myob_id") IS NULL)
    OR is_old
);
Run Code Online (Sandbox Code Playgroud)

(是的,该IS NULL检查有效。请参阅此处。)

这种机制的优点:

  • 约束仍然有效
  • 您可以继续更新旧行而不填写此值

缺点:

  • 类似的情况在未来会很混乱。您必须boolean为第二个新列添加第二列或其他一些跳圈。
  • 如果你强制更新的行被赋予一个值,这不会这样做。
  • 这有可能被滥用,因为有人可以将is_old标志翻转为true. (不过,这可以解决。见下文。)如果最终用户无法直接访问数据库并且您可以相信开发人员不会对数据做古怪的事情,那么这不是什么值得关注的事情。

如果你担心有人改变的标志,你可以设置一个触发器,以防止任何插入或更新从设置is_oldtrue

CREATE FUNCTION throw_error_on_illegal_old()
  RETURNS trigger
  LANGUAGE plpgsql
  AS $$
  BEGIN
    IF NEW.is_old THEN
      -- Need to make sure we don't try to access
      -- OLD in an INSERT
      IF TG_OP = 'INSERT' THEN
        RAISE 'Cannot create new with is_old = true';
      ELSE
        IF NOT OLD.is_old THEN
          RAISE 'Cannot change is_old from false to true';
        END IF;
      END IF;
    END IF;
    -- If we get here, all tests passed
    RETURN NEW;
  END
  $$
;

CREATE TRIGGER billables_prohibit_marking_row_old
BEFORE INSERT OR UPDATE ON integrations.billables
FOR EACH ROW EXECUTE PROCEDURE throw_error_on_illegal_old()
;
Run Code Online (Sandbox Code Playgroud)

您仍然必须相信,没有人会修改数据库架构并删除您的触发器或其他东西,但如果他们要这样做,他们也可以删除约束。

这是一个SQLFiddle 演示。请注意,“应该跳过”行不在输出中(如我们所愿)。