Ainsi, après quelques recherches, nous avons décidé de continuer à le faire côté SQL avant de passer à l'entrepôt de données. Mais nous adoptons cette approche bien améliorée (basée sur nos besoins et une nouvelle compréhension du fonctionnement du masque).
Nous obtenons une liste des noms de colonnes et leurs positions ordinales avec cette requête. Le retour revient dans un format XML afin que nous puissions passer à 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);
Nous passons ensuite ce bloc XML en tant que variable et le champ de masque à une fonction CLR qui renvoie une chaîne délimitée par des virgules des colonnes modifiées par le champ binaire _ $ update_mask. Cette fonction clr interroge le champ de masque pour le bit de changement pour chaque colonne de la liste xml, puis renvoie son nom à partir de l'ordinal associé.
SELECT cdc.udf_clr_ChangedColumns(@colListXML,
CAST(__$update_mask AS VARCHAR(MAX))) AS changed
FROM cdc.dbo_OurCaptureTableName
WHERE NOT __$update_mask IS NULL;
Le code c # clr ressemble à ceci: (compilé dans un assembly appelé 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;
}
}
}
Et la fonction du CLR comme ceci:
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]
Nous ajoutons ensuite cette liste de colonnes à l'ensemble de lignes et passons à l'entrepôt de données pour analyse. En utilisant la requête et le clr, nous évitons d'avoir à utiliser deux appels de fonction par ligne par changement. Nous pouvons passer directement à la viande avec des résultats personnalisés pour notre instance de capture de changement.
Merci à ce post stackoverflow suggéré par Jon Seigel pour la manière d'interpréter le masque.
D'après notre expérience avec cette approche, nous sommes en mesure d'obtenir une liste de toutes les colonnes modifiées à partir de 10 000 lignes cdc en moins de 3 secondes.