2016-10-14 145 views
0

我正在使用Merge语句更新并从我的表格中的web api(以数据表格的形式)插入批量记录。插入和更新工作正常,但当我发送一个损坏的值.Catch块不处理该值。这里是我的代码:捕获块没有处理该错误

//Creating type to fetch the data table from web api(have to go with this approach only) 

    CREATE TYPE [dbo].[CustomerType] AS TABLE(
      [Id] [int] NULL, 
      [Name] [nvarchar](100) NULL, 
      [Country] [nvarchar](50) NULL, 
      [Date] [datetime] NULL 
     ) 

    // Stored proc for update and insert using merge 

    CREATE PROCEDURE Update_Customers 
      @tblCustomers CustomerType READONLY 
    AS 
    BEGIN 

    BEGIN TRY 

      MERGE INTO Customers c1 
      USING @tblCustomers c2 
      ON c1.CustomerId=c2.Id 
      WHEN MATCHED THEN 
      UPDATE SET c1.Name = c2.Name 
       ,c1.Country = c2.Country, 
       c1.date =c2.date 
      WHEN NOT MATCHED THEN 
      INSERT VALUES(c2.Id, c2.Name,c2.date, c2.Country); 

    END TRY 


    BEGIN CATCH 

    //My table for logging the error 
    INSERT INTO ERROR_LOG(ERROR_LINE,ERROR_MESSAGE,PROC_NAME) 
    VALUES (ERROR_LINE(),ERROR_MESSAGE(),ERROR_PROCEDURE) 

    END CATCH 

    END 

    Thanks in advance 
+0

是什么导致了一个损坏的值? SQL不像您给出的信用那样严格。如果它的脏数据,没有发生。为什么首先出现错误?您的BEGIN TRAN在哪里?为什么你不能限制合并中的值?你将SQL视为一行行语言......它是关系型的。 –

+0

@ clifton_h-我将50条记录传递给数据表,并在一条记录中传递带有额外空间的日期(2016-10-15)。我的存储过程未在ERROR_LOG表中记录该错误(在catch块中使用)。你能帮助其他方法吗?我需要将错误插入该表中。 – paemmi

+0

为什么你需要这个错误日志?转换插入了错误数据的数据会不会更简单?为什么你需要“捕捉”这个插入错误?修复源代码,或使表格健壮。请记住,这是一个关系表。在将其插入表格之前验证您的数据。 –

回答

0

问题是SQL不会按照您认为的方式处理错误。

  1. SQL Server保持ACID状态。

原子:全部或全部。所有的工作都被分解为事务性陈述,这些陈述必须成功或者整个修改/创建被还原,或者回滚到以前的工作状态。通过使用BEGIN TRANSACTION/COMMIT TRANSACTION或使用ROLLBACK TRANSACTION,您可以明确说明某件作品的交易。 Read More: Transaction StatementsROLLBACK TRANSACTION

一致性:每个事务都必须使SQL Server处于有效状态。例如,虽然DROP TABLE MyTable;可能是有效的事务,但如果MyTable具有依赖关系(即Foreign Key Constraints),则SQL Server将处于不一致状态,并且会将事务回滚到上一个一致状态。

隔离:每笔交易发生在自己的时间和空间。我们说序列号具体。隔离功能允许在服务器上同时执行多个甚至相似的语句,并且每个语句都独占一个。术语阻止指当语句正在等待事务提交并且死锁是两个事务正在无限期地彼此等待时。

耐用性:与存在于内存中的软件程序不同,该程序可能因突然断电而丢失,SQL Server的事务对于磁盘一旦提交就永久存在。这提供了一个声明的最终结果。这也是LOG文件到位的地方,因为它记录了数据库上执行的事务。 READ MORE: ACID PROPERTIES

自从您的BEGIN TRY/CATCH块查找这些问题后,我提到了所有这些。

  1. 对待你的表作为数据集

召回SQL是基于关系集合理论。当它可以对组/数据/对象集执行操作时,SQL是最好的。它与继承完全不兼容,并且使用草写逻辑是可能的,但效果不佳。 因此,在您的ETL过程中,将数据视为整个集合。

相反,通过处理你的数据作为一个整体设置,您可以将用户/网络接口的次要拼写错误/错误,并隔离您希望使用谓词子句来编目数据类型的实际误差(WHERE/HAVING/ON

的你可以做什么的一个例子是类似以下内容:

CREATE TABLE #ETLTable(ID  INT   NOT NULL 
        , Name  VARCHAR(50) NOT NULL 
        , Comment VARCHAR(50) NULL 
        , Country VARCHAR(50) NOT NULL 
        , Dated  VARCHAR(50)); --implicitly declared as allowing NULL values unless the type denies it 

CREATE TABLE #MyTable (ID  INT   NOT NULL 
        , Name  VARCHAR(50) NOT NULL 
        , Comment VARCHAR(50) NULL 
        , Country VARCHAR(50) NOT NULL 
        , Dated  DATE); 

CREATE TABLE #ERROR_TABLE (ObjectID  INT NULL 
         , encrypted INT 
         , text   VARCHAR(MAX) 
         , start_time DATETIME2 
         , Table_ID  INT 
         , Table_Name VARCHAR(50) 
         , Table_Country VARCHAR(50) 
         , Table_Dated VARCHAR(20)); 

CREATE TABLE #ACTIONS ([Action] VARCHAR(50) 
          , [inserted_ID]  int 
          , inserted_Name  VARCHAR(50) 
          , inserted_Country VARCHAR(50) 
          , inserted_Date  Date 
          , deleted_ID   int 
          , deleted_Name  VARCHAR(50) 
          , deleted_Country  VARCHAR(50) 
          , deleted_Date  Date) 

INSERT INTO #MyTable (ID, Name, Country, Dated) 
VALUES (1, 'Mary', 'USA', '12/23/12') 
    , (2, 'Julio', 'Mexico', '12/25/12') 
    , (3, 'Marx', 'USA', '11/11/12') 
    , (4, 'Ann', 'USA', '11/27/12'); 

INSERT INTO #ETLTable(ID, Name, Country, Comment, Dated) 
VALUES (1,'Mary', 'USA', 'Valid Date', '12/23/12') 
    , (2,'Julio', 'Mexico', 'Invalid Date', '12-25,12') 
    , (3,'Marx', 'USA', 'Valid but incorrect Date', '12-11/25') --this actually means YY-MM-DD 
    , (4,'Ann','USA', 'Not Matching Date', '12-23-12') 
    , (5, 'Hillary', 'USA', 'New Entry', '11/24/12'); 

/*SQL Server is fairly flexible to datatypes entries, so be explicit. 
Note the date highlighted. This will fail your code since it is of datatype DATETIME. CAST is implicit anyways, and should not be depended on in important queries. Theoretically, you could catch this with your TRY/CATCH block, but the entire Merge statement would be rolled back...an expensive and probably unacceptable cost. 
    You should proof your INSERTIONS of errors before you start expensive transactions like the MERGE statement. In this example, I knew what dates were being entered and that only the Japanese version might be entered. Dates are very finicky (DATE has no format) and best handled outside the merge statement altogether. */ 
;WITH CTE AS (
SELECT ID, Name, Country, Comment, ISNULL(TRY_CAST(Dated AS Date), CAST(CONVERT(datetime, Dated, 11) AS DATE)) AS Dated --TRY_CAST returns a NULL if it cannot succeed and will not fail your query. 
       FROM #ETLTable 
       WHERE ISDATE(Dated) = 1 
      ) 

MERGE INTO #MyTable TGT 
    USING CTE SRC ON TGT.ID = SRC.ID 
        AND TGT.Name = SRC.Name 
WHEN MATCHED AND SRC.Dated > TGT.Dated 
    THEN UPDATE SET TGT.Dated = SRC.Dated 
        , TGT.Comment = SRC.Comment 
WHEN NOT MATCHED BY TARGET 
    THEN INSERT(ID, Name, Country, Comment, Dated) VALUES (SRC.ID, SRC.Name, SRC.Country, SRC.Comment, SRC.DATED) 
    OUTPUT $action AS [Action] 
     , inserted.ID 
     , inserted.Name 
     , inserted.Country 
     , inserted.Dated 
     , deleted.ID 
     , deleted.Name 
     , deleted.Country 
     , deleted.Dated 
    INTO #Actions; 

/* Note, you would have to run this query separately, as it only records active transactions. */ 
--CREATE PROC MyProject 
--AS BEGIN 
--WAITFOR DELAY '00:00:10' 
--Print 'ME' 
--END 
;WITH CTE AS (

      SELECT t.objectid, encrypted, text, start_time 
      FROM sys.dm_exec_requests AS r 
      CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t 
       INNER JOIN (SELECT object_id FROM sys.procedures 
          WHERE object_id = OBJECT_ID('MyProject')) B ON t.objectid = B.object_id) 

INSERT INTO #ERROR_TABLE (ObjectID, encrypted, text, start_time, Table_ID, Table_Name, Table_Country, Table_Dated) 
SELECT CTE.objectid, CTE.encrypted, CTE.text, CTE.start_time, B.Table_ID, B.Table_Name, B.Table_Country, B.Table_Dated 
FROM CTE 
RIGHT OUTER JOIN (SELECT ID AS Table_ID 
         , Name AS Table_Name 
         , Country AS Table_Country 
         , Dated AS Table_Dated 
       FROM #ETLTable 
       WHERE ISDATE(Dated) = 0) B ON 1 = 1 

SELECT * FROM #ERROR_TABLE 
SELECT * FROM #Actions 
SELECT * FROM #MyTable 

注意,脚本不破的SQL Server任何东西,其实分开坏输入前Merge声明。

  • 请注意#ERROR_TABLE中的有用信息。您实际上可以利用这些信息进行实时推荐。 这是您的错误追踪的目标。
  • 提醒一下,了解您的RAISERRORTRY/CATCH只适用于整个交易......一个工作单元。如果您需要优雅地允许Merge语句失败,那就好了。但要明白,适当的ETL 将保证这种昂贵的陈述永不失败

因此,无论如何,请在您写入最终表格之前执行所有ETL过程。这是临时表的要点...所以你可以在你的批量表中过滤非法或拼写错误。

少做事就是懒惰和不专业。学习正确的习惯,他们会帮助你免受未来的灾难。

+0

个人而言,我喜欢将数据加载到临时表中,然后提升可以正确转换的行。您提到了批量插入,因此必须是在插入实时数据之前清理并准备好的数据。这就是为什么你应该使用临时表(我希望你一直在讨论这个问题),这样你就可以将ETL过程分解成子阶段,以保证在最终数据库表中获得完美的结果。没有什么比在事先应该处理大块数据时必须更改实时数据更糟糕。 –

相关问题