I started to have memories from my Digital Logic class where we had to do something similar using modulus with a constrained feedback loop. I pseudo-coded an example function, but found that SQL doesn't support arrays (thinking now, I probably could have used a table variable, but that would have needlessly complicated the situation). Regardless of SQL's deficiencies, using the modulus/feedback method does work, but wasn't going to fit my vision of the solution.
function BitFlags(int val) { bool[] arr = new bool[8]; int modulus; for(int i=7; i >= 0; i--) { int pwr = (2^i); if (val == pwr) { arr[i] = true; } else if (val > pwr ){ modulus = val % (2 ^ i); if (modulus != 0) { arr[i] = true; val = modulus; } } } return arr; }
For those who haven't used their math skills in a while, here is an example. My Digital Logic skills are a bit rusty, so I may be incorrect, feel free to correct me if I am wrong. We want to find the bit flags for the number 76; using the modulus/feedback method, we get something like:
// Background 2^7 = 128 2^6 = 64 2^5 = 32 2^4 = 16 2^3 = 8 2^2 = 4 2^1 = 2 2^0 = 1 // Now the fun part, cycle through the range 0-7 for 2^i: // Calc Output Description 76 % 128 = 0 [0] // Feedback 76 76 % 64 = 12 [1] // Feedback 12 12 % 32 = -- [0] // 12 is smaller than 32, Feedback 12 12 % 16 = -- [0] // 12 is smaller than 16, Feedback 12 12 % 8 = 4 [1] // Feedback 4 4 % 4 = -- [1] // 4 == 4, so the we get a 1 for the output and there is nothing to feedback, so everything past that point is 0 0 % 2 = -- [0] // 0 is smaller than 2, Feedback 0 0 % 2 = -- [0] // 0 is smaller than 2, Feedback 0
Something just did not settle well with me. You just had to be able to do bitwise operations; it seems like elementary funcitonality. I took my C skills and tried to them out in SQL. It threw me for a loop when after evaluating the @val & <number> expression that I would get back the <number> where I was expecting a 1, but 0's everywhere else. I new I was on the write path and was almost there. I did some googling and found:
http://www.sqlusa.com/articles2005/binarypattern/
Which was doing something similar, but didn't exactly fit the situation. It gave me a DUH! moment when I realized that I should just divide by the <number> and POOF, I had the elegant solution I was expecting.
Try it out for your self.
DECLARE @val as tinyint SET @val = 128+64+16+1 SELECT (@val&128)/128 , (@val&64)/64 , (@val&32)/32 , (@val&16)/16 , (@val&8)/8 , (@val&4)/4 , (@val&2)/2 , (@val&1)/1 --RESULT: --11010001
UPDATE: My good friend Christopher Lauer used my code and showed me how he used it. I did not find anything like this when I googled and thinking that it would be useful for other people he gave me permission to include it.
USE MSDB; if not exists (select * from sys.schemas where name = 'my_utility') exec('create schema my_utility') go /***************************************************************************************** DESCRIPTION: The MSDB.dbo.sysSchedules has a column named freq_interval, this column may contain a packed bit that needs unpacked to understand what days the schedule is set to run on. See books on line for information on this table and column. This function will unpack the bit and return a string of weekday names that the bit respresents. Base code provided by Brock Moeller --SELECT [Monday] = (@bitWise&2)/2, [Tuesday] = (@bitWise&4)/4, [Wednesday]= (@bitWise&8)/8, [Thursday] = (@bitWise&16)/16, [Friday] = (@bitWise&32)/32, [Saturday] = (@bitWise&64)/64, [Sunday] = (@bitWise&128)/128 MAINTENANCE: [date:name] [description of the change/maintenance being done] ============================== ================================================== 8/19/2009: Christopher M. Lauer Created function. ******************************************************************************************/ CREATE FUNCTION [my_utility].[udf_getDayNameFromBitWise](@bitWise AS INTEGER) RETURNS Varchar(62) AS BEGIN --SET NOCOUNT ON; /* DECLARE @bitWise INT; --SET @bitWise = 63; --Monday, Tuesday, Wednesday, Thursday, Friday */ /***** LOCALS ******/ DECLARE @ErrorCode INT, @RowsAffected INT; SELECT @ErrorCode = 0, @RowsAffected = 0; -- default /***** END LOCALS ******/ DECLARE @WeekTable TABLE(dName VARCHAR(10), isUsed bit, dNumber tinyint, bitWise int) INSERT INTO @WeekTable(dName, isUsed, dNumber, bitWise) SELECT 'Monday', (@bitWise&2)/2, 1, 2 UNION ALL SELECT 'Tuesday', (@bitWise&4)/4, 2, 4 UNION ALL SELECT 'Wednesday', (@bitWise&8)/8, 3, 8 UNION ALL SELECT 'Thursday', (@bitWise&16)/16, 4, 16 UNION ALL SELECT 'Friday', (@bitWise&32)/32, 5, 32 UNION ALL SELECT 'Saturday', (@bitWise&64)/64, 6, 64 UNION ALL SELECT 'Sunday', (@bitWise&128)/128, 7, 128 --SELECT * FROM @WeekTable DECLARE @ReturnValue as VARCHAR(50) SELECT @ReturnValue = COALESCE(@ReturnValue, '') + dName + '; ' FROM @WeekTable WHERE isUsed = 1; IF(LEN(@ReturnValue) > 1) begin SET @ReturnValue = substring(@ReturnValue,1,len(@ReturnValue) -1) end RETURN @ReturnValue ENDUPDATE: 8/20/2009 - I had a little free time and discovered an odd occurrence with bitwise operations.
DECLARE @val as tinyint SET @val = 128+64+16+1 SELECT (@val&10000001) --RESULT 129 SELECT (@val&01000001) --RESULT 65 SELECT (@val&00100000) -- @val&32 --RESULT 128 ---- Incorrect! Should be '0'.It appears that SQLServer 2005 implicitly interprets the number as binary. I can't find any documentation on this issue. The results are accurate for scenarios where what you are looking for matches. However, if you test on something that doesn't match the left value, it doesn't return the correct number. I suppose an answer will have to wait.
On a side note, hexadecimal numbers work as expected like their decimal counterparts.
DECLARE @val as tinyint SET @val = 128+64+16+1 SELECT (@val&0x40) --RESULT 64 SELECT (@val&0x20) --RESULT 0It still appears that sticking with base 10 is easiest, but if you are good with hex you may find that usefull.
No comments:
Post a Comment