【问题标题】:How to identify which row/rows are causing an error during a MERGE statement and why如何在 MERGE 语句期间识别哪些行/行导致错误以及原因
【发布时间】:2014-03-08 11:15:49
【问题描述】:

我在我的 C# 批处理作业中使用下面的代码来处理大量 (20k+) 的更新和插入。但是,在测试过程中,我可以看到,如果出现问题,例如违反约束,我只会返回第一条错误消息,并且没有关于导致问题的记录(或记录)的信息。

是否有可以使用.NetT-SQL 实现的错误处理方法或技术,可以为我们提供此功能?

C#

    private static string insertCommand =
        "INSERT (ChannelCode, DrmTerrDesc, IndDistrnId, StateCode, ZipCode, EndDate, EffectiveDate, LastUpdateId, LastUpdateDate, ErrorCodes, Status) " +
        "VALUES(Source.ChannelCode, Source.DrmTerrDesc, Source.IndDistrnId, Source.StateCode, Source.ZipCode, Source.EndDate, Source.EffectiveDate, Source.LastUpdateId, Source.LastUpdateDate, Source.ErrorCOdes, Source.Status)";

    private static string updateCommand = "UPDATE SET Target.ChannelCode = Source.ChannelCode, Target.DrmTerrDesc = Source.DrmTerrDesc, Target.IndDistrnId = Source.IndDistrnId," +
                                                "Target.StateCode = Source.StateCode, Target.ZipCode = Source.ZipCode, Target.EndDate = Source.EndDate, Target.EffectiveDate = Source.EffectiveDate," +
                                                "Target.LastUpdateId = Source.LastUpdateId, Target.LastUpdateDate = Source.LastUpdateDate, Target.ErrorCodes = Source.ErrorCodes," +
                                                "Target.Status = Source.Status ";

    public static int Update(List<ZipCodeTerritory> updates, Dictionary<object, string> errorList)
    {
        int results = 0;
        try
        {
            //Load updates into datatable
            DataTable table = LoadData(updates, true);

            //Script to create temp table
            string tmpTable =   "CREATE TABLE [dbo].[ZipCodeTerritoryTemp]( " +
                                "[ChannelCode] [char](1) NOT NULL, " +
                                "[DrmTerrDesc] [nvarchar](30) NOT NULL, " +
                                "[IndDistrnId] [char](3) NULL, " +
                                "[StateCode] [char](3) NOT NULL, " +
                                "[ZipCode] [char](9) NULL, " +
                                "[EndDate] [date] NOT NULL, " +
                                "[EffectiveDate] [date] NOT NULL, " +
                                "[LastUpdateId] [char](8) NULL, " +
                                "[LastUpdateDate] [date] NULL, " +
                                "[Id] [int] NULL, " +               
                                "[ErrorCodes] [varchar](255) NULL, " +
                                "[Status] [char](1) NULL)";

            using (SqlConnection connection = new SqlConnection(connString))
            {
                connection.Open();

                //Create temp table
                SqlCommand cmd = new SqlCommand(tmpTable, connection);
                cmd.ExecuteNonQuery();

                try
                {

                    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
                    {
                        //Write to temp table
                        bulkCopy.DestinationTableName = "ZipCodeTerritoryTemp";
                        bulkCopy.WriteToServer(table);

                        //Merge changes in temp table with ZipCodeTerritory
                        string mergeSql = "merge ZipCodeTerritory as Target " +
                                          "using ZipCodeTerritoryTemp as Source " +
                                          "on " +
                                          "Target.Id = Source.Id " +
                                          "when matched then " +
                                          updateCommand +
                                          "when not matched then " +
                                          insertCommand + ";";

                        cmd.CommandText = mergeSql;
                        results = cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception ex)
                {
                    SendEmail.ErrorMail(ex.Message);
                }
                finally
                {
                    //Drop temp table
                    SqlCommand final = new SqlCommand("DROP TABLE [dbo].[ZipCodeTerritoryTemp]", connection);
                    final.ExecuteNonQuery();
                }
            }
        }
        catch (Exception ex)
        {
            SendEmail.ErrorMail(ex.Message);
        }
        return results;
    }

【问题讨论】:

    标签: c# sql sql-server tsql merge


    【解决方案1】:

    对于您的 Merge 语句,您缺少一件小事

    merge ZipCodeTerritory as Target " +
    "using (SELECT * FROM ZipCodeTerritoryTemp) as Source " + --<-- need to select data
    "on " +
    "Target.Id = Source.Id " +
    "when matched then " +
    updateCommand +
    "when not matched then " +
    insertCommand + ";";
    

    但在您继续采用这种方法之前,Read This 来自 Aaron Bertrand 的关于 MERGE 声明问题的文章。

    我建议你使用 IF EXISTS 这样的方法...

    更新

    UPDATE T
    SET  T.Col1 = S.Col1,
         T.Col2 = S.Col2,
         T.Col3 = S.Col3
    FROM ZipCodeTerritory T INNER JOIN ZipCodeTerritoryTemp S
    ON   T.id = S.id
    

    插入

    INSERT INTO ZipCodeTerritory(Col1, Col2, Col3, ....)
    SELECT S.Col1, S.Col2, S.Col3, ....
    FROM   ZipCodeTerritoryTemp S LEFT JOIN ZipCodeTerritory T
    ON     S.ID = T.ID
    WHERE  T.ID IS NULL 
    

    【讨论】:

    • 不确定这将如何解决错误处理/确定哪个记录有问题以及原因的问题。如果您使用SqlCommand 对象来执行UPDATE 和INSERT`s,您会在异常或其他情况下获取该信息吗?
    • 添加selectusing 子句是不必要的,除非您要添加where 子句。 using ZipCodeTerritoryTemp as Source 完全有效。
    【解决方案2】:

    简短的回答是您无法通过执行MERGE 语句来确定这一点,您必须在执行MERGE 之前检查这些冲突。

    换句话说(这一点我怎么强调都不为过):始终验证您的输入。

    您可以在两点进行验证:在将数据批量复制到临时表之前以及将临时表合并到目标表之前。根据数据问题的性质,您可以在数据到达服务器之前完成大部分验证。

    我在使用MERGE 语句时通常会遇到三类主要问题:

    1. 键冲突(源中的重复行)
    2. 数据格式错误(例如,日期的字符串表示不能正确转换为 DATE
    3. 约束失败(不允许使用空值、外键错误等)

    前两个通常可以在将数据推送到服务器之前检测到。第三个取决于您的约束的性质......但通常我们可以在它们到达服务器之前解决这些问题。

    可以通过按键对数据进行分组来检测数据中的键冲突(在本例中为Id)。假设您有两条具有相同 Id 值的记录,但您希望合并到具有最高 LastUpdateDate 的一条记录。一种选择是:

    var cleanupdates = 
        from update in updates
        group update by update.Id into grp
        select grp.OrderByDescending(u => u.LastUpdateDate).First();
    

    如果您的约束问题与空值有关,请使用where 子句过滤掉那些具有无效空值的记录。如果它们与外键约束相关,请将这些键加载到列表中并对其进行过滤。您可以使用 LINQ 查询进行大量验证。

    重要的是您进行验证。否则你的MERGE 会失败,你也不知道为什么。

    【讨论】:

      猜你喜欢
      • 2020-11-07
      • 1970-01-01
      • 2021-08-14
      • 2022-07-11
      • 1970-01-01
      • 2016-11-20
      • 2022-06-28
      • 1970-01-01
      相关资源
      最近更新 更多