错误:某些行违反了检查约束

4 postgresql constraint postgresql-9.2 check-constraints

我有以下错误:

ERROR: check constraint "cc_at_least_one_mapping_needed" is violated by some row
Run Code Online (Sandbox Code Playgroud)

询问:

ALTER TABLE integrations.tax_aggregates
DROP COLUMN IF EXISTS myob_id,
ADD COLUMN myob_id integrations.FOREIGN_IDENTIFIER;
COMMENT ON COLUMN integrations.tax_aggregates.myob_id IS 'Foreign key for MYOB';

ALTER TABLE integrations.tax_aggregates DROP CONSTRAINT IF EXISTS cc_at_least_one_mapping_needed,
ADD CONSTRAINT cc_at_least_one_mapping_needed CHECK ((("qb_id" IS NOT NULL) :: INTEGER +
                                                      ("xero_id" IS NOT NULL) :: INTEGER +
                                                      ("freshbooks_id" IS NOT NULL) :: INTEGER +
                                                      ("myob_id" IS NOT NULL) :: INTEGER +
                                                      ("ppy_id" IS NOT NULL) :: INTEGER) > 0);

                                                      DROP INDEX IF EXISTS integrations.ix_tax_aggregates_myob_ids_ids;
CREATE INDEX ix_tax_aggregates_myob_ids_ids ON integrations.tax_aggregates USING BTREE (myob_id)
  WHERE myob_id IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?我该如何解决问题?

更新:

询问:

ALTER TABLE integrations.accounts
DROP COLUMN IF EXISTS myob_settings,
ADD COLUMN myob_settings JSON;

ALTER TABLE integrations.accounts DROP CONSTRAINT IF EXISTS cc_at_least_one_setting_needed,
    ADD CONSTRAINT cc_at_least_one_setting_needed CHECK (("qb_settings" IS NOT NULL) or
                                                          ("xero_settings" IS NOT NULL) or
                                                          ("freshbooks_settings" IS NOT NULL) or
                                                          ("myob_settings" IS NOT NULL) or
                                                          ("ppy_settings" IS NOT NULL));
Run Code Online (Sandbox Code Playgroud)

选择结果:

SELECT * FROM integrations.accounts WHERE qb_settings IS NOT NULL AND xero_settings IS NOT NULL and freshbooks_settings IS NOT NULL AND myob_settings IS NOT NULL AND ppy_settings IS NOT NULL
Run Code Online (Sandbox Code Playgroud)

它返回 0 行


SELECT * FROM integrations.accounts WHERE qb_settings IS NULL OR xero_settings IS NULL OR freshbooks_settings IS NULL OR myob_settings IS NULL OR ppy_settings IS NULL;
Run Code Online (Sandbox Code Playgroud)

返回(59 行):

"account_id"|"global_settings"|"qb_settings"|"xero_settings"|"freshbooks_settings"|"myob_settings"|"ppy_settings"

"30374"|""|"{""id"":""1412494720"",""active"":true,""settings"":{""secret"":""iisCCNSiH4jmRArWUPi0rxa9LntQdAA3kc7lbLtf"",""token"":""qyprdigrVpUijVKDxJ72iBZrnh4tvlwMA1QuVxxyPSgLIz4j"",""expires"":""2016-01-31 04:34:37""},""plan"":""QuickBooks Online Essentials""}"|""|""|""|""


ERROR:  check constraint "cc_at_least_one_setting_needed" is violated by some row
Run Code Online (Sandbox Code Playgroud)

Vér*_*ace 5

您有违反 CHECK CONSTRAINT 的 NULL。

要验证这确实是问题所在,请运行此 SQL。

SELECT * FROM integrations.accounts WHERE qb_settings IS NOT NULL OR            
xero_settings IS NOT NULL OR... (fill in the fields that correspond to 
those in the CONSTRAINT).
Run Code Online (Sandbox Code Playgroud)

这将为您提供所有包含违反约束的 NULL 值的字段的记录。

从您的 SQL 中,您有 59 条这样的记录 - 这就是您收到错误的原因。

您需要做的就是运行类似于以下内容的 SQL:

UPDATE My_Table SET My_Field = 0 WHERE My_Field IS NULL.
Run Code Online (Sandbox Code Playgroud)

显然,确保您设置的值符合您系统的业务逻辑和其他系统要求取决于您。


Len*_*art 5

您可以否定您的约束以找出不满足它的行:

SELECT * 
FROM integrations.accounts
WHERE NOT ((("qb_settings" IS NOT NULL) or
            ("xero_settings" IS NOT NULL) or
            ("freshbooks_settings" IS NOT NULL) or
            ("myob_settings" IS NOT NULL) or
            ("ppy_settings" IS NOT NULL)))
Run Code Online (Sandbox Code Playgroud)

这可以简化为:

      WHERE
      NOT NOT((("qb_settings" IS NULL) and
            ("xero_settings" IS NULL) and
            ("freshbooks_settings" IS NULL) and
            ("myob_settings" IS NULL) and
            ("ppy_settings" IS NULL)))
Run Code Online (Sandbox Code Playgroud)

并进一步作为:

       WHERE 
          "qb_settings" IS NULL and
          "xero_settings" IS NULL and
          "freshbooks_settings" IS NULL and
          "myob_settings" IS NULL and
          "ppy_settings" IS NULL
Run Code Online (Sandbox Code Playgroud)

请注意,约束的规则是它们不能评估为FalseNull满足约束,因此如果有可能评估为Null(不是在这种情况下),则必须考虑这一点。

从您的评论来看,您有兴趣按原样保留历史行,并且只验证新行。我能想到的两种选择:

  1. 向表中添加一个时间维度(比如在创建行时CREATE_TIME)并将约束更改为类似的内容:

     ALTER TABLE integrations.accounts
         ADD CONSTRAINT cc_at_least_one_setting_needed 
             CHECK ( (CREATE_TIME <= t_0) or 
                     ((qb_settings IS NOT NULL) or
                      (xero_settings IS NOT NULL) or
                      (freshbooks_settings IS NOT NULL) or
                      (myob_settings IS NOT NULL) or
                      (ppy_settings IS NOT NULL)) );
    
    Run Code Online (Sandbox Code Playgroud)

其中 t_0 是应验证行的时间。换句话说,它的含义是:(CREATE_TIME > t_0) => <your contraint>

  1. 创建触发器而不是检查约束。发生某些事情时会触发触发器,因此它们不关心已存在的行。就像是:

    CREATE FUNCTION validate_row() 
    RETURNS trigger AS $validate_row$
    BEGIN
        -- Check that data is valid
        IF NEW.qb_id IS NULL AND xero_id IS NULL AND ... THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
    END;
    $validate_row$ LANGUAGE plpgsql;
    
    CREATE TRIGGER validate_row 
        BEFORE INSERT OR UPDATE ON integrations.accounts
            FOR EACH ROW EXECUTE PROCEDURE validate_row();
    
    Run Code Online (Sandbox Code Playgroud)

这两个想法都未经测试。