Ubah Pengambilan Data dan biner __ $ update_mask


9

Kami menggunakan CDC untuk menangkap perubahan yang dilakukan pada tabel produksi. Baris yang diubah diekspor ke gudang data (informatica). Saya tahu bahwa kolom __ $ update_mask menyimpan kolom apa saja yang diperbarui dalam bentuk varbinary. Saya juga tahu bahwa saya bisa menggunakan berbagai fungsi CDC untuk mencari tahu dari topeng itu apa kolom-kolom itu.

Pertanyaan saya adalah ini. Adakah yang bisa mendefinisikan logika di balik topeng itu untuk kami sehingga kami dapat mengidentifikasi kolom yang diubah di gudang? Karena kami sedang memproses di luar server, kami tidak memiliki akses mudah ke fungsi-fungsi MSSQL CDC. Saya lebih suka memecah topeng sendiri dalam kode. Kinerja fungsi cdc di SQL end bermasalah untuk solusi ini.

Singkatnya, saya ingin mengidentifikasi kolom yang diubah dengan tangan dari bidang __ $ update_mask.

Memperbarui:

Sebagai alternatif, mengirimkan daftar kolom perubahan yang dapat dibaca manusia ke gudang juga dapat diterima. Kami menemukan ini dapat dilakukan dengan kinerja yang jauh lebih besar daripada pendekatan awal kami.

Jawaban CLR untuk pertanyaan ini di bawah ini memenuhi alternatif ini dan termasuk rincian menafsirkan topeng untuk pengunjung masa depan. Namun jawaban yang diterima menggunakan XML PATH adalah yang tercepat namun untuk hasil akhir yang sama.


Jawaban:


11

Dan moral dari cerita ini adalah ... ujian, coba hal-hal lain, berpikir besar, lalu kecil, selalu menganggap ada cara yang lebih baik.

Secara ilmiah menarik seperti jawaban terakhir saya. Saya memutuskan untuk mencoba satu pendekatan lain. Saya ingat saya bisa melakukan concat dengan trik XML PATH (''). Karena saya tahu bagaimana cara mendapatkan ordinal dari setiap kolom yang diubah dari daftar capt_column dari jawaban sebelumnya, saya pikir akan layak untuk diuji jika fungsi bit MS akan bekerja lebih baik seperti itu untuk apa yang kami butuhkan.

SELECT __$update_mask ,
        ( SELECT    CC.column_name + ','
          FROM      cdc.captured_columns CC
                    INNER JOIN cdc.change_tables CT ON CC.[object_id] = CT.[object_id]
          WHERE     capture_instance = 'dbo_OurTableName'
                    AND sys.fn_cdc_is_bit_set(CC.column_ordinal,
                                              PD.__$update_mask) = 1
        FOR
          XML PATH('')
        ) AS changedcolumns
FROM    cdc.dbo_MyTableName PD

Ini jauh lebih bersih daripada (meskipun tidak menyenangkan) semua CLR itu, mengembalikan pendekatan kembali ke kode SQL asli saja. Dan, drum roll .... mengembalikan hasil yang sama dalam waktu kurang dari satu detik . Karena data produksi adalah 100 kali lebih besar setiap detik diperhitungkan.

Saya meninggalkan jawaban yang lain untuk tujuan ilmiah - tetapi untuk sekarang, ini adalah jawaban yang benar.


Tambahkan _CT ke nama tabel dalam klausa FROM.
Chris Morley

1
Terima kasih telah kembali dan menjawab ini, saya mencari solusi yang sangat mirip sehingga kami dapat memfilternya sesuai dengan kode begitu panggilan SQL dilakukan. Saya tidak suka melakukan panggilan untuk setiap kolom di setiap baris yang dikembalikan dari CDC!
nik0lias

2

Jadi, setelah beberapa penelitian kami memutuskan untuk tetap melakukan ini di sisi SQL sebelum menyerahkan ke gudang data. Tetapi kami mengambil pendekatan yang jauh lebih baik ini (berdasarkan kebutuhan dan pemahaman baru tentang cara kerja topeng).

Kami mendapatkan daftar nama kolom dan posisi ordinal mereka dengan pertanyaan ini. Pengembalian kembali dalam format XML sehingga kita bisa beralih ke SQL CLR.

DECLARE @colListXML varchar(max);

SET @colListXML = (SELECT column_name, column_ordinal
    FROM  cdc.captured_columns 
    INNER JOIN cdc.change_tables 
    ON captured_columns.[object_id] = change_tables.[object_id]
    WHERE capture_instance = 'dbo_OurTableName'
    FOR XML Auto);

Kami kemudian meneruskan blok XML itu sebagai variabel dan bidang mask ke fungsi CLR yang mengembalikan string kolom yang koma yang diubah per bidang biner _ $ update_mask. Fungsi clr ini menginterogasi bidang mask untuk bit perubahan untuk setiap kolom dalam daftar xml dan kemudian mengembalikan namanya dari ordinal terkait.

SELECT  cdc.udf_clr_ChangedColumns(@colListXML,
        CAST(__$update_mask AS VARCHAR(MAX))) AS changed
    FROM cdc.dbo_OurCaptureTableName
    WHERE NOT __$update_mask IS NULL;

Kode c # clr terlihat seperti ini: (dikompilasi ke dalam rakitan yang disebut CDCUtilities)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString udf_clr_cdcChangedColumns(string columnListXML, string updateMaskString)
    {
        /*  xml of column ordinals shall be formatted as follows:

            <cdc.captured_columns column_name="Column1" column_ordinal="1" />                
            <cdc.captured_columns column_name="Column2" column_ordinal="2" />                

        */

        System.Text.ASCIIEncoding encoding=new System.Text.ASCIIEncoding();
        byte[] updateMask = encoding.GetBytes(updateMaskString);

        string columnList = "";
        System.Xml.XmlDocument colList = new System.Xml.XmlDocument();
        colList.LoadXml("<columns>" + columnListXML + "</columns>"); /* generate xml with root node */

        for (int i = 0; i < colList["columns"].ChildNodes.Count; i++)
        {
            if (columnChanged(updateMask, int.Parse(colList["columns"].ChildNodes[i].Attributes["column_ordinal"].Value)))
            {
                columnList += colList["columns"].ChildNodes[i].Attributes["column_name"].Value + ",";
            }
        }

        if (columnList.LastIndexOf(',') > 0)
        {
            columnList = columnList.Remove(columnList.LastIndexOf(','));   /* get rid of trailing comma */
        }

        return columnList;  /* return the comma seperated list of columns that changed */
    }

    private static bool columnChanged(byte[] updateMask, int colOrdinal)
    {
        unchecked  
        {
            byte relevantByte = updateMask[(updateMask.Length - 1) - ((colOrdinal - 1) / 8)];
            int bitMask = 1 << ((colOrdinal - 1) % 8);  
            var hasChanged = (relevantByte & bitMask) != 0;
            return hasChanged;
        }
    }
}

Dan fungsi untuk CLR seperti ini:

CREATE FUNCTION [cdc].[udf_clr_ChangedColumns]
       (@columnListXML [nvarchar](max), @updateMask [nvarchar](max))
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [CDCUtilities].[UserDefinedFunctions].[udf_clr_cdcChangedColumns]

Kami kemudian menambahkan daftar kolom ini ke rowset dan meneruskannya ke gudang data untuk dianalisis. Dengan menggunakan kueri dan CLR kami menghindari keharusan menggunakan dua panggilan fungsi per baris per perubahan. Kita dapat langsung beralih ke daging dengan hasil yang disesuaikan untuk instance tangkapan perubahan kita.

Berkat posting stackoverflow ini disarankan oleh Jon Seigel untuk cara yang menafsirkan topeng.

Dalam pengalaman kami dengan pendekatan ini, kami dapat memperoleh daftar semua kolom yang diubah dari baris 10k cdc dalam waktu kurang dari 3 detik.


Terima kasih telah kembali dengan solusi, saya mungkin akan segera menggunakannya.
Mark Storey-Smith

Lihatlah jawaban BARU saya sebelum Anda melakukannya. Sekeren CLR ... kami menemukan cara yang lebih baik. Semoga berhasil.
RThomas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.