我正在使用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 :(得分:0)
问题是SQL不会像你认为的那样处理错误。
Atomic :全有或全无。所有工作都分为事务性语句,这些语句必须成功或者整个修改/创建被还原,或者回滚,才能进入上一个工作状态。您可以使用BEGIN TRANSACTION
/ COMMIT TRANSACTION
或使用ROLLBACK TRANSACTION
明确说明作品所处的交易。 Read More: Transaction Statements和ROLLBACK TRANSACTION
一致:每个事务都必须使SQL Server处于有效状态。例如,虽然DROP TABLE MyTable;
可能是有效的事务,但如果MyTable具有依赖关系(即Foreign Key Constraints
),则SQL Server将处于不一致状态并将事务回滚到上一个一致状态。
隔离:每笔交易都在自己的时间和空间内进行。我们说序列化具体。隔离允许在服务器上同时进行多个甚至类似的语句,并且彼此独占地处理它们中的每一个。术语阻止是指语句在等待事务提交时,死区是两个事务无限期地等待的时间。
持久性:与内存中可能因突然断电而丢失的软件程序不同,SQL Server的事务在提交后永久保留在磁盘上。这为声明提供了终极性。这也是LOG文件到位的地方,因为它记录了对数据库执行的事务。 READ MORE: ACID PROPERTIES
我提到了所有这一切,因为您的BEGIN TRY/CATCH
块会查找这些问题。
召回SQL基于关系集理论。 SQL最适合对组/数据/对象组执行操作。它与继承完全不兼容,并且使用草书逻辑是可能的,但最多效率低下。 因此,在ETL过程中,将数据视为一整套。
相反,通过将数据视为整个集合,您可以转换用户/ Web界面的次要拼写错误/错误,并使用谓词子句({{1})隔离您希望编目的数据类型的实际错误} / 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
或RAISERROR
/ TRY
仅适用于整个交易......一个工作单元。如果您需要优雅地允许Merge语句失败,那很好。但要明白,正确的ETL 将保证这些昂贵的陈述永远不会失败。所以,无论如何,请在写入最终表格之前执行所有ETL过程。这是暂存表的重点...因此您可以过滤掉批量表中的非法或拼写错误。
做更少的事情是懒惰和不专业。学习正确的习惯,他们将帮助您免于未来的灾难。