【问题标题】:Using ON UPDATE CASCADE when update causes duplicate values更新导致重复值时使用 ON UPDATE CASCADE
【发布时间】:2014-06-28 23:49:22
【问题描述】:

在 PostgreSQL 中,我有一个主表 ICD9,它旨在保存 cicd9 和 cdesc 的唯一组合,并具有以下约束:

CONSTRAINT constraint_cdesc UNIQUE (cicd9, cdesc)

我有多个子表引用 ICD9 表:

CONSTRAINT fk_icd9 FOREIGN KEY (cicd9, cdesc)
REFERENCES icd9 (cicd9, cdesc) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED

我可以更改我需要的任何约束,但我的目标是使用主表来更正子表的所有拼写错误。我在互联网上没有找到好的(或任何)答案。问题是更新 cdesc 中的错误拼写或 cicd9 中的错误代码会导致主表中出现重复条目​​——因此会引发错误。

解决这个(我认为)相对常见的问题的最佳方法是什么?

注意:我在一个单独的程序中使用 C# (Net 4.0) 来处理 PostgreSQL 9.1 数据库服务器(我是其中的管理员)。

【问题讨论】:

  • 我认为你的做法是错误的。如果您打算让“主”表包含拼写,则应在主表中引入人工键(身份或类似键),并将其用作关联表中的 FK。重复描述是要避免的。对于您的特定问题,如果您在更新时遇到重复值冲突,那么这是因为数据会导致一个(您可能会修复拼写错误,其中已经存在非拼写错误记录),然后您应该更新数据手动/通过触发器代替。
  • @AllanS.Hansen 有道理。我需要更多地研究触发因素。举个例子真的很有帮助。

标签: c# sql database postgresql constraints


【解决方案1】:

如果updating icd9 中的条目导致unique constraint 违规,那么在我看来,在这种情况下您实际上想要做的是delete 来自icd9 的条目而不是更新它。

如果 deleting 该条目会在其他表中导致 FK constraint 违规,那么我建议首先通过更新子表中的条目以引用导致 unique constraint 的条目来解决该问题违规(即已经存在的“正确”值),然后对“不正确”值执行delete。

如果您想在某种程度上自动执行此操作,您可以创建一个 pl/pgSQL 函数:

  1. 检查是否会违反任何唯一约束
  2. 如果是,这是一个删除候选
  3. 然后,检查子表以查看是否有任何条目引用删除候选者
  4. 如果是这样,update 这些指向“正确”的行——即会导致违反唯一约束的行
  5. 然后对icd9 表中的行执行delete

注意:您不希望在约束 CASCADE 中包含 DELETE,因为您不想删除引用它的行,而是将它们更新为指向icd9 中的另一行。

编辑: 要以编程方式确定表具有的 FK(或其他)约束,您可以查询 pg_constraint 表,它是 catalog 的一部分。详情请见http://www.postgresql.org/docs/9.3/static/catalog-pg-constraint.html。

您可能希望将其与pg_class 结合起来,以从原始表名开始。请参阅http://www.postgresql.org/docs/9.3/static/catalog-pg-class.html 了解更多信息。

【讨论】:

  • 有没有办法在 PostgreSQL 数据库中查询依赖此约束的子表?显而易见的目标是发现要更改哪些表。
  • 我确实通过 Tom Lane 找到了这个:select confrelid::regclass, af.attname as fcol, conrelid::regclass, a.attname as col from pg_attribute af, pg_attribute a, (select conrelid,confrelid ,conkey[i] as conkey, confkey[i] as confkey from (select conrelid,confrelid,conkey,confkey, generate_series(1,array_upper(conkey,1)) as i from pg_constraint where contype = 'f') ss) ss2其中 af.attnum = confkey and af.attrelid = confrelid and a.attnum = conkey and a.attrelid = conrelid AND confrelid::regclass= 'my_table'::regclass AND af.attname = 'my_referenced_column';
  • 看来应该可以了。您可能还想在整个查询中添加 conname 元素,这是实际约束的名称,尽管取决于您处理事物的方式,这可能无关紧要(如果您只想知道依赖表是什么)。
【解决方案2】:

我建议将此作为一种可能的解决方案。如果有人感兴趣,我会对这个优化版本最感兴趣。感谢@Ken 的指点。 (我不知道如何最好地格式化这个,抱歉)。

internal static void UpdateDxLibraryTx(PhysicalExam.DX newdx, PhysicalExam.DX olddx)
    {
        String Unique_Violation = "23505";

        // nothing to change.
        if (olddx.description == null) return;  

        PhysicalExam.DX oldDx = (PhysicalExam.DX)olddx;

        // the server structure was created in MainWindow on initialization.
        string connect = server.connection_string;

        string update = " update icd9 set cicd9='" + newdx.icd9 + "', cdesc= '" + newdx.description.ToLower().Trim() + "'" +
                " ,chronic ='" + newdx.chronic + "' ,modified='" + DateTime.Now + "'" +
                " where cicd9 ='" + oldDx.icd9 + "'" +
                " and trim(cdesc) ='" + oldDx.description.ToLower().Trim() + "' and chronic ='" + oldDx.chronic + "';";

        string getchildtables = " select confrelid::regclass, af.attname as fcol,   "+ 
                                " conrelid::regclass, a.attname as col              "+
                                " from pg_attribute af, pg_attribute a,             "+
                                " (select conrelid,confrelid,conkey[i] as conkey, confkey[i] as confkey "+
                                " from (select conrelid,confrelid,conkey,confkey,   "+
                                " generate_series(1,array_upper(conkey,1)) as i     "+
                                " from pg_constraint where contype = 'f') ss) ss2   "+
                                " where af.attnum = confkey and af.attrelid = confrelid and             "+ 
                                " a.attnum = conkey and a.attrelid = conrelid "+
                                " AND confrelid::regclass = 'ICD9'::regclass AND ( af.attname = 'cicd9' OR af.attname ='cdesc');";

        string sf = " update {0} set cicd9= '"+ newdx.icd9 +"' ,cdesc= '"+ newdx.description.ToLower().Trim() +"',"   +
                        " chronic = '"+newdx.chronic+"', modified= '"+ DateTime.Now +"'"                    +
                        " where cicd9 = '"+ oldDx.icd9 +"' and trim(cdesc)= '"+ oldDx.description.ToLower().Trim() +"' and chronic ='"+ oldDx.chronic +"';";

        string delete = "Delete from icd9 where trim(cicd9) = '" + oldDx.icd9.Trim() + "'" +
                        " and trim(cdesc)='" + oldDx.description.Trim() + "' and chronic = '"+ oldDx.chronic +"';";


        // NpsqlConnection is unmanaged code...should use a using() block.
        using (NpgsqlConnection pgConnection = new NpgsqlConnection(connect))
        {
            try
            {
                pgConnection.Open();
                // Connection successful

                // Create a new transaction
                using (NpgsqlTransaction pgTransaction = (NpgsqlTransaction)pgConnection.BeginTransaction())
                {
                    try
                    {
                        using (NpgsqlCommand updateCMD = new NpgsqlCommand(update, pgConnection, pgTransaction))
                        {
                            updateCMD.ExecuteNonQuery();
                        }

                        //Commit transaction. Hope it works!  Will fail for unique key violation.
                        pgTransaction.Commit();

                    }
                    catch (NpgsqlException ex)
                    {
                        pgTransaction.Rollback();

                        if (ex.Code == Unique_Violation)            
                        {                                
                            try
                            {
                                using (NpgsqlDataAdapter children = new NpgsqlDataAdapter(getchildtables,pgConnection))   
                                {
                                    DataSet ds = new DataSet();
                                    children.Fill(ds);

                                    DataView dv = new DataView();
                                    dv.Table = ds.Tables[0];

                                    foreach (DataRowView drv in dv)
                                    {
                                        string childtable = (string)drv[2];
                                        string updatechild = String.Format(sf, childtable);

                                        NpgsqlCommand updateCMD = new NpgsqlCommand(updatechild, pgConnection);
                                        int k = updateCMD.ExecuteNonQuery();
                                    }
                                    /* all tables now point to new value. remove old value from parent table. */

                                    NpgsqlCommand deleteCMD = new NpgsqlCommand(delete, pgConnection);
                                    int j = deleteCMD.ExecuteNonQuery();
                                }
                            }
                            catch (NpgsqlException ex1)
                            {
                                string error = ex1.Message;
                                throw;
                            }

                        }
                        else
                            throw;
                    }
                }
            }
            catch (NpgsqlException ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            catch (Exception ex)
            {
                string s = ex.Message;
                throw;
            }
        }
    }

【讨论】:

    猜你喜欢
    • 2010-12-01
    • 2015-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    相关资源
    最近更新 更多