在 SQL Server 中使用 Change Tracking 时,您应该使用
CHANGE_TRACKING_IS_COLUMN_IN_MASK
来确定在处理更新时更改了哪一列。例如,像这样:
DECLARE @last_synchronization_version bigint = ...;
DECLARE @column_id int = ...;
-- The statement below returns 1 if the specified column (@column_id) was changed, otherwise 0.
SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK(@column_id, SYS_CHANGE_COLUMNS)
FROM CHANGETABLE(CHANGES dbo.MyTable, @last_synchronization_version) AS CT
我想知道,有没有办法自己实现
CHANGE_TRACKING_IS_COLUMN_IN_MASK
,这样我就可以在应用程序中使用 SYS_CHANGE_COLUMNS
的值,而无需事先知道我的应用程序在执行查询时对哪些列感兴趣?
例如,当我只更改ID为
11
的列的值时,SYS_CHANGE_COLUMNS
的值为0x000000000B000000
。
如何以编程方式确定此掩码包含第 11 列已更改的信息?
事实证明
SYS_CHANGE_COLUMNS
由一个字节数组组成,可以分为 4 个字节的组。更改的列越多,字节数组就越长,因此可以创建的组就越多。每组的第一个字节表示已更改的列的 ID。在我的所有测试中,每组的其他 3 个字节为空 (0)。我假设当列 ID 大于 255 时将使用这些字节。似乎列更改的顺序决定了它们在字节数组中出现的顺序。另外,第一组 4 个字节将始终为空 (0),我不知道为什么。
要在应用程序代码中使用它,您所需要做的就是获取每个列名称及其相应列 ID 的映射。上一段应该解释了如何使用
SYS_CHANGE_COLUMNS
来确定哪个列 ID 出现在字节数组中。
C# 示例:
public static IEnumerable<int> GetColumnIdsInMask(byte[] columns)
{
// TODO: deal with column IDs larger than 255
for (var i = 4; i < columns.Length; i += 4)
{
yield return columns[i];
}
}
public static bool IsColumnInMask(int columnId, byte[] columns)
{
return GetColumnIdsInMask.Any(x => x == columnId);
}
您可以完全在 SQL 中完成此操作,方法是使用递归 cte 来分割字节,然后反转它们并最终转换为整数值。我只测试了最多列 id ~ 20,因为我只有狭窄的表。我假设 255 以上的值可以使用相同的逻辑。
DECLARE @TableName sysname = 'Customer'
DECLARE @ColMask varbinary(4100) = 0x000000000200000004000000;
WITH buckets AS (
SELECT 1 a,1 b
UNION ALL
SELECT a + 1, a*4+1 FROM buckets
WHERE a < LEN(@ColMask)/4
)
SELECT a, CAST(CAST(REVERSE(SUBSTRING(@ColMask,b,4)) AS varbinary(4)) AS int) ChangedColId,c.name ColumnName
FROM buckets b
JOIN sys.columns c ON c.object_id = OBJECT_ID(@TableName) AND c.column_id = CAST(CAST(REVERSE(SUBSTRING(@ColMask,b,4)) AS varbinary(4)) AS int)