如何从 Rails 中的外部数据库同步我的数据?

How can I sync my data from an external database in Rails?

我正在将信息从另一个数据库导入我的 Rails 数据库。目前我已经查询数据库以从 table 中获取所有记录,然后我创建了我的模型的新对象并分配了值。我想检测记录是否已更新,如果已更新,我将更新记录。这些记录大约有 40 个属性。我可以使用什么方法查询数据库以查看记录是否已更改?目前我正在使用以下方法,但它似乎很慢。

SELECT A.attribute1, A.attribute2, A.attribute3, ...
  FROM external.dbo.myobject A
 INNER JOIN internal.dbo.myobject B
    ON A.key = B.key
 WHERE (A.attribute1 <> B.attribute1 OR
        A.attribute2 <> B.attribute2 OR
        A.attribute3 <> B.attribute3 OR
        ...)

1) 要检测更改,最简单的解决方案是使用 EXCEPT

以上query/statement

INSERT INTO #changes (...)
SELECT ... FROM external.dbo.object
EXCEPT
SELECT ... FROM internal.dbo.object

external.dbo.object 中不同或不存在于 internal.dbo.object 中的所有行插入 #changes

对于同步我会使用 MERGE 语句(见上面的例子):

MERGE   dbo.InternalObj AS i
USING   #changes AS e ON i.ID = e.ID
WHEN MATCHED 
    THEN UPDATE SET ... 
WHEN NOT MATCHED
    THEN INSERT ... -- This clause INSERT new rows

2) 检测更改的另一个选项是使用 ROWVERSION 数据类型,它是一个二进制值 在插入行或 更新行时自动 生成.

示例:

CREATE TABLE dbo.InternalObj (
    ID      INT PRIMARY KEY,
    ColA    VARCHAR(100) NOT NULL,
    rw      BINARY(8) NOT NULL 
);
GO

CREATE TABLE dbo.ExternalObj (
    ID      INT IDENTITY(1,1) PRIMARY KEY,
    ColA    VARCHAR(100) NOT NULL,
    rw      ROWVERSION NOT NULL -- When ColA values are changed, SQL Server will automaticcaly update [rw]
);
GO
INSERT dbo.ExternalObj (ColA) VALUES ('A')
INSERT dbo.ExternalObj (ColA) VALUES ('B')
GO

-- First test & sync
MERGE   dbo.InternalObj AS i
USING   dbo.ExternalObj AS e ON i.ID = e.ID
WHEN MATCHED AND i.rw <> e.rw -- Same [ID] but differet [rw] values
    THEN
    UPDATE -- This clause update changed rows
    SET i.ColA  = e.ColA,
        i.rw    = e.rw 
WHEN NOT MATCHED
    THEN 
    INSERT  (ID, ColA, rw) -- This clause INSERT new rows
    VALUES  (e.ID, e.ColA, e.rw);
GO
SELECT * FROM dbo.InternalObj;
/*
ID          ColA rw
----------- ---- ------------------
1           A    0x0000000000000FA6
2           B    0x0000000000000FA7
*/
GO

-- Second test & sync
INSERT dbo.ExternalObj (ColA) VALUES ('C')
UPDATE  dbo.ExternalObj
SET     ColA = ColA + '#'
WHERE   ID = 2
GO
MERGE   dbo.InternalObj AS i
USING   dbo.ExternalObj AS e ON i.ID = e.ID
WHEN MATCHED AND i.rw <> e.rw -- Same [ID] but differet [rw] values
    THEN
    UPDATE -- This clause update changed rows
    SET i.ColA  = e.ColA,
        i.rw    = e.rw 
WHEN NOT MATCHED 
    THEN 
    INSERT  (ID, ColA, rw) -- This clause INSERT new rows
    VALUES  (e.ID, e.ColA, e.rw);
GO
SELECT * FROM dbo.InternalObj;
/*
ID          ColA rw
----------- ---- ------------------
1           A    0x0000000000000FA6
2           B#   0x0000000000000FA9
3           C    0x0000000000000FA8
*/
GO

如果我需要检测 any 列中的更改,我会使用 [ROWVERSION]。

注意:像 UPDATE ... SET ColA = <the same value> 这样的简单操作会更改受影响行的 [rw] 值。

3) 第三种解决方案使用 BINARY_CHECKSUM 函数为每一行生成校验和值。通过比较每个 ID/行的这些校验和值,我们可以检测到变化

ALTER TABLE dbo.InternalObj DROP COLUMN [rw]
ALTER TABLE dbo.ExternalObj DROP COLUMN [rw]
GO

-- Test / sync
;WITH CteTarget
AS (
    SELECT *, BINARY_CHECKSUM(*) AS CRC
    FROM (
    SELECT  ID, ColA -- Only selected columns
    FROM    dbo.InternalObj 
    ) x
), CteSource
AS (
    SELECT *, BINARY_CHECKSUM(*) AS CRC
    FROM (
    SELECT  ID, ColA -- Only selected columns
    FROM    dbo.ExternalObj 
    ) y
)
MERGE   CteTarget i
USING   CteSource e ON i.ID = e.ID 
WHEN MATCHED AND EXISTS(SELECT i.CRC EXCEPT SELECT e.CRC) -- Same [ID] but differet [CRC] values 
    THEN
    UPDATE -- This clause update changed rows
    SET i.ColA  = e.ColA
WHEN NOT MATCHED 
    THEN 
    INSERT  (ID, ColA) -- This clause INSERT new rows
    VALUES  (e.ID, e.ColA);
GO
SELECT * FROM dbo.InternalObj;
/*
ID          ColA
----------- ----
1           A
2           B
*/
GO

注意:BINARY_CHECKSUM 函数可能会产生哈希冲突(具有不同值的行 => 相同的校验和 => 未检测到更改)。