Nach einigen Recherchen haben wir uns daher entschlossen, dies weiterhin auf der SQL-Seite zu tun, bevor wir es an das Data Warehouse übergeben. Aber wir verfolgen diesen viel verbesserten Ansatz (basierend auf unseren Bedürfnissen und dem neuen Verständnis der Funktionsweise der Maske).
Mit dieser Abfrage erhalten wir eine Liste der Spaltennamen und ihrer Ordnungspositionen. Die Rückgabe erfolgt in einem XML-Format, damit wir sie an SQL CLR weitergeben können.
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);
Wir übergeben diesen XML-Block dann als Variable und das Maskenfeld an eine CLR-Funktion, die eine durch Kommas getrennte Zeichenfolge der Spalten zurückgibt, die sich gemäß dem Binärfeld _ $ update_mask geändert haben. Diese clr-Funktion fragt das Maskenfeld nach dem Änderungsbit für jede Spalte in der XML-Liste ab und gibt dann den Namen von der zugehörigen Ordnungszahl zurück.
SELECT cdc.udf_clr_ChangedColumns(@colListXML,
CAST(__$update_mask AS VARCHAR(MAX))) AS changed
FROM cdc.dbo_OurCaptureTableName
WHERE NOT __$update_mask IS NULL;
Der c # clr-Code sieht folgendermaßen aus: (kompiliert in eine Assembly namens 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;
}
}
}
Und die Funktion zur CLR lautet wie folgt:
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]
Anschließend hängen wir diese Spaltenliste an das Rowset an und übergeben sie zur Analyse an das Data Warehouse. Durch die Verwendung der Abfrage und des clr müssen keine zwei Funktionsaufrufe pro Zeile und Änderung verwendet werden. Wir können direkt zum Fleisch mit Ergebnissen springen, die für unsere Änderungserfassungsinstanz angepasst wurden.
Dank dieses von Jon Seigel vorgeschlagenen Stackoverflow-Beitrags für die Interpretation der Maske.
Nach unserer Erfahrung mit diesem Ansatz können wir in weniger als 3 Sekunden eine Liste aller geänderten Spalten aus 10.000 cdc-Zeilen abrufen.