Entonces, después de algunas investigaciones, decidimos seguir haciendo esto en el lado de SQL antes de entregarlo al almacén de datos. Pero estamos adoptando este enfoque mejorado (basado en nuestras necesidades y una nueva comprensión de cómo funciona la máscara).
Obtenemos una lista de los nombres de columna y sus posiciones ordinales con esta consulta. El retorno vuelve en un formato XML para que podamos pasar a 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);
Luego pasamos ese bloque XML como variable y el campo de máscara a una función CLR que devuelve una cadena delimitada por comas de las columnas que cambió según el campo binario _ $ update_mask. Esta función clr interroga el campo de máscara para el bit de cambio para cada columna en la lista xml y luego devuelve su nombre desde el ordinal relacionado.
SELECT cdc.udf_clr_ChangedColumns(@colListXML,
CAST(__$update_mask AS VARCHAR(MAX))) AS changed
FROM cdc.dbo_OurCaptureTableName
WHERE NOT __$update_mask IS NULL;
El código c # clr se ve así: (compilado en un ensamblaje llamado 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;
}
}
}
Y la función para el CLR es la siguiente:
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]
Luego agregamos esta lista de columnas al conjunto de filas y la pasamos al almacén de datos para su análisis. Al usar la consulta y el clr, evitamos tener que usar dos llamadas de función por fila por cambio. Podemos pasar directamente a la carne con resultados personalizados para nuestra instancia de captura de cambios.
Gracias a esta publicación de stackoverflow sugerida por Jon Seigel por la manera de interpretar la máscara.
En nuestra experiencia con este enfoque, podemos obtener una lista de todas las columnas modificadas de 10k filas de cdc en menos de 3 segundos.