Roll your own bitmasks
SQL's bitwise operators only work for integer datatype ranges – even if one of your args is (VAR)BINARY, the other can be no larger than a BIGINT. If you need more than 63 positions in a bitmask, you need to roll your own.
This approach supports the storage of bitmasks VARCHAR fields, in this implementation up to 255 characters long. Each (non-Unicode) character occupies one byte, and the character's ASCII code serves as a TINYINT that can be be used as a native bitwise operand.
Print bitmask
This is just for convenience/confidence! It prints out the bitmask left to right. The index of the first bit position is zero.
IF OBJECT_ID('dbo.fn_PrintBitmask') IS NOT NULL DROP FUNCTION dbo.fn_PrintBitmask GO CREATE FUNCTION dbo.fn_PrintBitmask ( @str VARCHAR(255) ) RETURNS VARCHAR(2040) AS BEGIN DECLARE @bitstring VARCHAR(MAX) = @str + ':' SELECT @bitstring += ' ' -- space for ease of reading + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 1 = 1 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 2 = 2 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 4 = 4 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 8 = 8 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 16 = 16 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 32 = 32 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 64 = 64 THEN '1' ELSE '0' END + CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 128 = 128 THEN '1' ELSE '0' END --, N --, SUBSTRING(@str, N, 1) --, ASCII(SUBSTRING(@str, N, 1)) FROM dbo.Numbers WHERE N <= LEN(@str) ORDER BY N RETURN @bitstring END
Check if a bit is set
IF OBJECT_ID('dbo.fn_IsBitSet') IS NOT NULL DROP FUNCTION dbo.fn_IsBitSet GO CREATE FUNCTION dbo.fn_IsBitSet ( @str VARCHAR(255) , @bitpos INT ) RETURNS BIT AS BEGIN DECLARE @charPos INT = (@bitpos / 8) + 1 DECLARE @bitVal INT = POWER(2, @bitpos % 8) IF ASCII(SUBSTRING(@str, @charPos, 1)) & @bitVal = @bitVal RETURN 1 RETURN 0 END
Flipping bits
This function is a generic bit (un)setter. It takes a @set BIT parameter and is idempotent.
IF OBJECT_ID('dbo.fn_ChangeBit') IS NOT NULL DROP FUNCTION dbo.fn_ChangeBit GO CREATE FUNCTION dbo.fn_ChangeBit ( @str VARCHAR(255) , @bitpos INT , @set BIT ) RETURNS VARCHAR(255) AS BEGIN DECLARE @charPos INT = (@bitpos / 8) + 1 DECLARE @bitVal INT = POWER(2, @bitpos % 8) DECLARE @target INT = @bitVal DECLARE @incr INT = @bitVal IF @set = 0 BEGIN SET @target = 0 SET @incr *= -1 END IF ASCII(SUBSTRING(@str, @charPos, 1)) & @bitVal = @target RETURN @str RETURN SUBSTRING(@str, 1, @charPos - 1) + CHAR(ASCII(SUBSTRING(@str, @charPos, 1)) + @incr) + SUBSTRING(@str, @charPos + 1, LEN(@str)) END
Set bit
This is a wrapper for dbo.fn_ChangeBit.
IF OBJECT_ID('dbo.fn_SetBit') IS NOT NULL DROP FUNCTION dbo.fn_SetBit GO CREATE FUNCTION dbo.fn_SetBit ( @str VARCHAR(255) , @bitpos INT ) RETURNS VARCHAR(255) AS BEGIN RETURN dbo.fn_ChangeBit(@str, @bitPos, 1) END
Unset bit
Like dbo.fn_SetBit, a wrapper for dbo.fn_ChangeBit.
IF OBJECT_ID('dbo.fn_UnsetBit') IS NOT NULL DROP FUNCTION dbo.fn_UnsetBit GO CREATE FUNCTION dbo.fn_UnsetBit ( @str VARCHAR(255) , @bitpos INT ) RETURNS VARCHAR(255) AS BEGIN RETURN dbo.fn_ChangeBit(@str, @bitPos, 0) END