web-dev-qa-db-ja.com

任意の数のキー列を持つレコードを識別する最も効率的な方法

大規模なサードパーティのデータセットを使用しています。長い経験から、システムに入力したらすぐにデータの着信行にサロゲートIDを付与して、検証や保管などの際に簡単に追跡できるようにすることは非常に良いアイデアであることがわかりました。問題は、キー値が潜在的にすべてのディメンション値であり、200列にすることができます。

私の一般的なプロセスはこれです:

  1. ステージングテーブルにデータを読み込みます。
  2. キー値と代理IDSのみを保持する2番目のIdMatchテーブルとデータを照合します。

    IF OBJECT_ID('Staging.myTest') IS NULL
        CREATE TABLE Staging.myTest (
            [ID] INT IDENTITY(1,1) NOT NULL,
            [Hash] INT NULL,
            [Dim_1] NVARCHAR(32) NOT NULL,
            [Dim_2] NVARCHAR(32) NOT NULL,
            [Dim_3] NVARCHAR(32) NULL,
            [Met_1] INT NULL,
            [Met_2] DECIMAL(5,2) NULL
        );
    
    IF OBJECT_ID('IdMatch.myTest') IS NULL
        CREATE TABLE IdMatch.myTest (
            [ID] INT IDENTITY(1,1) NOT NULL,
            [Hash] INT NULL,
            [Dim_1] NVARCHAR(32) NOT NULL,
            [Dim_2] NVARCHAR(32) NOT NULL,
            [Dim_3] NVARCHAR(32) NULL,
        );
    
    TRUNCATE TABLE Staging.myTest;
    TRUNCATE TABLE IdMatch.myTest;
    
    INSERT INTO Staging.myTest
        ([Dim_1], [Dim_2], [Dim_3])
    VALUES ('A', 'A', 'A'),
        ('B', 'B', 'B'),
        ('C', 'C', NULL),
        ('C', 'C', 'C'),
        ('D', 'D', 'D');
    
    INSERT INTO IdMatch.myTest
        ([Dim_1], [Dim_2], [Dim_3])
    VALUES ('A', 'A', 'A');
    
    --My Proc (as script) for setting the index.
    INSERT INTO [IdMatch].myTest
        ([Dim_1], [Dim_2], [Dim_3])
    SELECT src.[Dim_1], src.[Dim_2], src.[Dim_3]
    FROM Staging.myTest AS src
    WHERE NOT EXISTS (
            SELECT tgt.[Dim_1], tgt.[Dim_2], tgt.[Dim_3]
            FROM [IdMatch].myTest AS tgt
            WHERE tgt.[Dim_1] = src.[Dim_1]
                AND tgt.[Dim_2] = src.[Dim_2]
                AND tgt.[Dim_3] = src.[Dim_3]
        );
    
    SELECT * FROM IdMatch.myTest
    

問題:この方法でサロゲートIDを取得するためのマッチングは、実際のデータセットにNVARCHARデータの200以上の列が含まれている場合、長い時間がかかります。もっと良い方法はありますか?私は事前計算ハッシュを試しましたが、私が生成する最終的な衝突を処理する方法がわかりません。

1
Jamie Marshall

一般的なアプローチの1つは、衝突の可能性が非常に低いハッシュ関数を選択して、存在しないと想定することです。

CREATE TABLE Staging.myTest (
    [ID] INT IDENTITY(1,1) NOT NULL,
    [Hash] AS 
        CONVERT(binary(32), 
            HASHBYTES('SHA2_256', 
                CONCAT(Dim_1, N'|', Dim_2, N'|', Dim_3))),
    [Dim_1] NVARCHAR(32) NOT NULL,
    [Dim_2] NVARCHAR(32) NOT NULL,
    [Dim_3] NVARCHAR(32) NULL,
    [Met_1] INT NULL,
    [Met_2] DECIMAL(5,2) NULL
);
GO
CREATE TABLE IdMatch.myTest (
    [ID] INT IDENTITY(1,1) NOT NULL,
    [Hash] AS 
        CONVERT(binary(32), 
            HASHBYTES('SHA2_256', 
                CONCAT(Dim_1, N'|', Dim_2, N'|', Dim_3))),
    [Dim_1] NVARCHAR(32) NOT NULL,
    [Dim_2] NVARCHAR(32) NOT NULL,
    [Dim_3] NVARCHAR(32) NULL,
);
GO
-- Declared unique because we have decided it will be
CREATE UNIQUE NONCLUSTERED INDEX 
    IX_HASH 
ON IdMatch.myTest 
    ([Hash]);

注:ほとんどの人は、ハッシュのために列のNULLを空の文字列に置き換えます。これは、CONCATのデフォルトの動作です。 NULLと空の文字列を区別する必要がある場合は、使用する他のマジック値を特定し、null許容列をISNULLまたはCOALESCEでラップする必要があります。

次に、一致しない行を追加します。

INSERT Staging.myTest
(
    Dim_1, 
    Dim_2, 
    Dim_3
)
SELECT
    SRC.Dim_1,
    SRC.Dim_2,
    SRC.Dim_3
FROM Staging.myTest AS SRC
WHERE
    NOT EXISTS
    (
        SELECT 1
        FROM IdMatch.myTest AS TGT
        WHERE
            TGT.[Hash] = SRC.[Hash]
    );

Greg Lowによる T-SQLで変更された行の検索– CHECKSUM、BINARY_CHECKSUM、HASHBYTES を参照してください。この方法をデータでテストして、このスキームが機能するかどうかを確認する必要があります。

1
Paul White 9

あなたの 前の質問 はハッシュとしてCHECKSUMを使用して言及されました。私の他の回答で述べたように、これは一般に機能しませんが、試してみたい場合、可能な実装は以下のとおりです。

CREATE TABLE Staging.myTest (
    [ID] INT IDENTITY(1,1) NOT NULL,
    [Hash] AS CHECKSUM(Dim_1, Dim_2, Dim_3),
    [Dim_1] NVARCHAR(32) NOT NULL,
    [Dim_2] NVARCHAR(32) NOT NULL,
    [Dim_3] NVARCHAR(32) NULL,
    [Met_1] INT NULL,
    [Met_2] DECIMAL(5,2) NULL
);
GO
CREATE TABLE IdMatch.myTest (
    [ID] INT IDENTITY(1,1) NOT NULL,
    [Hash] AS CHECKSUM(Dim_1, Dim_2, Dim_3),
    [Dim_1] NVARCHAR(32) NOT NULL,
    [Dim_2] NVARCHAR(32) NOT NULL,
    [Dim_3] NVARCHAR(32) NULL,
);
GO
-- For lookups
CREATE UNIQUE CLUSTERED INDEX c ON IdMatch.myTest (ID);
GO
-- Not unique!
CREATE NONCLUSTERED INDEX 
    IX_HASH 
ON IdMatch.myTest 
    ([Hash]);

新しい行を見つけるには、ハッシュの衝突も考慮する必要があります。ここでの考え方は、ハッシュチェックが一致の可能性を見つけた場合にのみ、列を詳細にチェックすることです。

INSERT Staging.myTest 
(
    Dim_1, 
    Dim_2, 
    Dim_3
)
SELECT
    SRC.Dim_1,
    SRC.Dim_2,
    SRC.Dim_3
FROM Staging.myTest AS SRC
LEFT JOIN IdMatch.myTest AS HSH
    ON HSH.[Hash] = SRC.[Hash]
WHERE
    1 = CASE
            -- No hash match, definitely missing (pass through)
            WHEN HSH.[Hash] IS NULL THEN 1
            -- Hash match, check columns in detail to confirm
            WHEN NOT EXISTS
            (
                -- Null-aware column comparison
                SELECT
                    -- Source column list
                    SRC.Dim_1, SRC.Dim_2, SRC.Dim_3
                INTERSECT 
                SELECT
                    -- Target column list
                    T.Dim_1, T.Dim_2, T.Dim_3
                FROM IdMatch.myTest AS T
                WHERE
                    T.ID = HSH.ID
            ) THEN 1
            -- Otherwise exact match already exists
            ELSE 0
        END;

Null対応の列比較は、私の記事 非文書化クエリプラン:等価比較 で説明されています。

このクエリを正しく記述し、完全な列比較の前にハッシュチェックを保証する方法はいくつかあります。これはCASEを使用して記述したものです。これにより、ロジックが非常に明確になり、実行プランの形が気に入ったからです。

1
Paul White 9