Quindi, dopo alcune ricerche, abbiamo deciso di farlo ancora sul lato SQL prima di passare al data warehouse. Ma stiamo adottando questo approccio molto migliorato (basato sulle nostre esigenze e sulla nuova comprensione di come funziona la maschera).
Otteniamo un elenco dei nomi delle colonne e delle loro posizioni ordinali con questa query. Il ritorno ritorna in un formato XML in modo da poter passare a CLR SQL.
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);
Passiamo quindi quel blocco XML come variabile e il campo maschera a una funzione CLR che restituisce una stringa delimitata da virgole delle colonne che sono cambiate nel campo binario _ $ update_mask. Questa funzione clr interroga il campo maschera per il bit di modifica per ogni colonna nell'elenco xml e quindi restituisce il nome dal relativo ordinale.
SELECT cdc.udf_clr_ChangedColumns(@colListXML,
CAST(__$update_mask AS VARCHAR(MAX))) AS changed
FROM cdc.dbo_OurCaptureTableName
WHERE NOT __$update_mask IS NULL;
Il codice c # clr è simile al seguente: (compilato in un assembly chiamato 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;
}
}
}
E la funzione al CLR in questo modo:
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]
Aggiungiamo quindi questo elenco di colonne al set di righe e passiamo al data warehouse per l'analisi. Usando la query e il clr evitiamo di usare due chiamate di funzione per riga per modifica. Possiamo saltare direttamente alla carne con risultati personalizzati per la nostra istanza di acquisizione delle modifiche.
Grazie a questo post stackoverflow suggerito da Jon Seigel per il modo in cui interpretare la maschera.
Nella nostra esperienza con questo approccio siamo in grado di ottenere un elenco di tutte le colonne modificate da 10 k righe di file in meno di 3 secondi.