如何在Sql Server中的两个任意长的VARBINARY(MAX)值之间进行按位OR?

时间:2016-04-19 19:41:02

标签: sql-server tsql

动机:

我希望有一个触发器可以在更新或插入任何表的任何文本字段时执行某些操作。

代码不得硬编码任何表知识。即它应该适用于任意表。

到目前为止我在哪里

  1. CREATE_TABLE上的DDL触发器,为新表添加了FOR INSERT,UPDATE DML触发器
  2. _ColumnMask表将基于1的整数映射到与COLUMNS_UPDATED格式兼容的相应VARBINARY(MAX)掩码。
  3. 拥有_ColumnMask表和一些aaaCulture表,我可以编写以下查询:

    ;WITH aux AS (
        SELECT (MAX(column_id) + 7) / 8 mask_len
        FROM sys.columns
        WHERE object_id = OBJECT_ID('aaaCulture')
    )
    SELECT c.name, column_id, CASE WHEN pad.zero IS NULL THEN cm.mask ELSE cm.mask + pad.zero END mask
    FROM sys.columns c 
    JOIN aux ON 1 = 1
    JOIN sys.types t ON t.user_type_id = c.user_type_id
    JOIN _ColumnMask cm ON cm.n = c.column_id
    LEFT JOIN _ColumnMask pad ON pad.n = aux.mask_len - (c.column_id + 7) / 8
    WHERE object_id = OBJECT_ID('aaaCulture') AND (t.name LIKE '%char' OR t.name LIKE '%text')
    ORDER BY c.column_id
    

    屈服

    name        column_id   mask
    Text1       2           0x020000000000
    Text2       4           0x080000000000
    Text3       6           0x200000000000
    Text4       8           0x800000000000
    Text5       10          0x000200000000
    Text7       14          0x002000000000
    Text8       16          0x008000000000
    Text9       18          0x000002000000
    Text10      20          0x000008000000
    Text11      21          0x000010000000
    Text21      24          0x000080000000
    Text22      26          0x000000020000
    Text23      28          0x000000080000
    Text24      30          0x000000200000
    Text25      32          0x000000800000
    Text26      34          0x000000000200
    Text27      36          0x000000000800
    Text28      38          0x000000002000
    Text29      40          0x000000008000
    XRefCode    42          0x000000000002
    Text12      44          0x000000000008
    

    例如,假设我更新字段Text9Text23

    UPDATE dbo.aaaCulture SET Text9 = 'haha', Text23 = 'xoxo'
    

    DML触发器报告的相应COLUMNS_UPDATED值为:

    0x000002080000
    

    实际上,根据上述查询结果:

    • Text9的掩码为0x000002000000
    • Text23的掩码为0x000000080000

    在两个掩码之间执行按位OR会产生0x000002080000

    因此,我似乎拥有拼图的所有部分,以便能够识别COLUMNS_UPDATED()包含任何文本列。除此之外,我不知道怎么做有效(我想可以写一些丑陋的WHILE循环)。

    有什么想法吗?

    编辑1

    我们通过向某个sql脚本(在版本控制下)添加数据库升级步骤来更新数据库。我的目标是阻止开发人员检查更新或插入任何xyzCulture表中任何文本字段的任何数据库步骤,包括那些新数据库步骤创建的那些。我们不讨论为什么我们首先要有xyzCulture表。有叹息,这是给予的。

    我可能会采取措施阻止对这些表格中的任何字段进行更新,但首先我要真正理解更具体的更难。

1 个答案:

答案 0 :(得分:0)

Helas,除了以下实现之外,我无法想出任何其他内容:

CREATE FUNCTION VarBinaryBitwiseOR(@x VARBINARY(MAX), @y VARBINARY(MAX))
RETURNS VARBINARY(MAX)
AS
BEGIN
    DECLARE @pos INT = 0
    DECLARE @res VARBINARY(MAX)
    DECLARE @tmp VARBINARY(MAX)
    WHILE @pos + 8 <= DATALENGTH(@x)
    BEGIN
        SET @tmp = CAST(CAST(SUBSTRING(@x,@pos + 1,8) AS BIGINT) | CAST(SUBSTRING(@y,@pos + 1,8) AS BIGINT) AS varbinary(MAX))
        SET @res = ISNULL(@res + @tmp, @tmp)
        SET @pos = @pos + 8
    END
    IF @pos + 4 <= DATALENGTH(@x)
    BEGIN
        SET @tmp = CAST(CAST(SUBSTRING(@x,@pos + 1,4) AS INT) | CAST(SUBSTRING(@y,@pos + 1,4) AS INT) AS varbinary(MAX))
        SET @res = ISNULL(@res + @tmp, @tmp)
        SET @pos = @pos + 4
    END
    IF @pos + 2 <= DATALENGTH(@x)
    BEGIN
        SET @tmp = CAST(CAST(SUBSTRING(@x,@pos + 1,2) AS SMALLINT) | CAST(SUBSTRING(@y,@pos + 1,2) AS SMALLINT) AS varbinary(MAX))
        SET @res = ISNULL(@res + @tmp, @tmp)
        SET @pos = @pos + 2
    END
    IF @pos + 1 <= DATALENGTH(@x)
    BEGIN
        SET @tmp = CAST(CAST(SUBSTRING(@x,@pos + 1,1) AS TINYINT) | CAST(SUBSTRING(@y,@pos + 1,1) AS TINYINT) AS varbinary(MAX))
        SET @res = ISNULL(@res + @tmp, @tmp)
        SET @pos = @pos + 1
    END
    RETURN @res
END

仅当两个参数的DATALENGTH相同时才有效,这完全是我的情况。

因此,它既缓慢又不通用(datalength必须相同)。