【发布时间】:2013-06-13 09:18:01
【问题描述】:
我有一个分布式事务,我需要合并到目标远程表中。 现在根据 MSDN 不允许 MERGE INTO:“target_table 不能是远程表”。
所以我的解决方法如下: 0. 开始分布式事务 1. 定义一个游标 2. 打开它 3. 如果游标至少有一条记录 (CURSOR_STATUS()=1) 获取下一个 4. 如果存在(选择顶部 1 * from target_remote_table where id = @myCurrentCursorId) -> 当 true 更新 target_remote_table 当 false 插入 target_remote_table 5. 提交/回滚分布式事务取决于 trancount 和 xact_state
它有效,但我知道游标是邪恶的,你不应该使用它们。所以我想问一下是否有其他方法可以通过不使用游标来解决这个问题?
USE [My_DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[my_proc_merge_into_remote_table]
@ID_A INT,
@ID_B INT
AS
BEGIN
SET NOCOUNT ON;
-- CURSOR VALUES
DECLARE @field_A INT
DECLARE @field_B INT
DECLARE @field_C INT
DECLARE @field_D BIT
DECLARE @field_E INT
DECLARE @field_F DATETIME
DECLARE @field_G VARCHAR(20)
DECLARE @field_H DATETIME
DECLARE @field_I VARCHAR(20)
BEGIN TRY
BEGIN DISTRIBUTED TRANSACTION
-- CURSOR !!
DECLARE my_cursor CURSOR FOR
SELECT b.field_A ,
b.field_B,
c.field_C,
a.field_D,
a.field_E,
GETDATE() AS field_F,
a.field_G,
GETDATE() AS field_H,
a.field_I
FROM dbo.source_tbl a
LEFT JOIN dbo.base_element_tbl l
ON a.obj_id = l.obj_id AND a.element_id = l.element_id
INNER JOIN dbo.base_obj_tbl b
ON a.obj_id = b.obj_id
INNER JOIN dbo.element_tbl c
ON a.element_id = c.element_id
WHERE a.ID_B = @ID_B
AND a.ID_A = @ID_A;
OPEN my_cursor;
-- check if cursor result set has at least one row
IF CURSOR_STATUS('global', 'my_cursor') = 1 BEGIN
FETCH NEXT FROM my_cursor
INTO @field_A,
@field_B,
@field_C,
@field_D,
@field_E,
@field_F,
@field_G,
@field_H,
@field_I;
WHILE @@FETCH_STATUS = 0 BEGIN
-- HINT: MY_REMOTE_TARGET_TABLE is a Synonym which already points to the correct database and table
IF EXISTS(SELECT TOP 1 * FROM MY_REMOTE_TARGET_TABLE WHERE field_A = @field_A AND field_B = @field_B AND field_C = @field_C AND field_E = @field_E)
UPDATE MY_REMOTE_TARGET_TABLE SET field_D = @field_D, field_H = @field_H, field_I = @field_I;
ELSE
INSERT INTO MY_REMOTE_TARGET_TABLE (field_A, field_B, field_C, field_D, field_E, field_F, field_G, field_H, field_I) VALUES (@field_A, @field_B, @field_C, @field_D, @field_E, @field_F, @field_G, @field_H, @field_I);
FETCH NEXT FROM my_cursor
INTO @field_A,
@field_B,
@field_C,
@field_D,
@field_E,
@field_F,
@field_G,
@field_H,
@field_I;
END;
END;
CLOSE my_cursor;
DEALLOCATE my_cursor;
IF (@@TRANCOUNT > 0 AND XACT_STATE() = 1)
BEGIN
COMMIT TRANSACTION
END
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0 AND XACT_STATE() = -1)
ROLLBACK TRANSACTION
END CATCH;
END
【问题讨论】:
-
MERGE 的目标不能是远程表,但 Source 可以。您可以从远程服务器运行查询。
-
另外,任何可以用 MERGE 写的东西也可以用 INSERT/UPDATE/DELETE 组合重写
-
我将尝试从远程服务器中开始合并。 “也用 INSERT/UPDATE/DELETE 重写”这正是我所做的,这只是您需要决定我们是否需要插入或更新的部分,这很棘手。截至目前,我通过光标选择每条记录并一一检查它是否已经存在来做到这一点。我只是想知道是否有比主机服务器上更好的解决方案。光标解决方案也很慢,所以这是另一个原因。
-
如果不查看确切的查询,我无法确定,但您不需要逐行运行 INSERT/UPDATE。您应该能够使用一条语句更新所有需要更新的行,然后使用另一条语句插入所有需要插入的行。 (在目标上进行 INNER JOIN 更新,LEFT JOIN + WHERE NULL 用于插入)
-
我在上面添加了到目前为止的内容。为了按照你说的去做,我该如何改变呢?
标签: sql sql-server tsql