{"id":1905,"date":"2014-11-18T00:00:00","date_gmt":"2014-11-14T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/bitwise-operations-in-t-sql\/"},"modified":"2021-09-29T16:21:32","modified_gmt":"2021-09-29T16:21:32","slug":"bitwise-operations-in-t-sql","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/bitwise-operations-in-t-sql\/","title":{"rendered":"Bitwise Operations in T-SQL"},"content":{"rendered":"<div id=\"pretty\">\n<p>Nowadays, you are unlikely to need any bit-level manipulation of data for general work in SQL  Server, since most of the places where it was once necessary are no longer so. You can see some of the problems we used  to face in deprecated parts of the system such as <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms188370.aspx\"> sys.syspermissions <\/a>&#160;and some system stored procedures (e.g. <b>sys.sp_MSdependencies<\/b>) that used bitwise flags as parameters.&#160; Since the introduction of the BIT Datatype to SQL Server in 2005, the last reason for doing routine bit  manipulation,&#160; economy of storage, &#160;disappeared.  Nowadays, the SQL Server Database Engine can index bit columns , and&#160;  optimize the storage of them&#160; if you use enough of them in a table. If there  are eight or fewer bit columns in a table, the columns are stored as one byte. If there are from nine up to sixteen&#160; bit columns, the columns are stored as two bytes, and so on.&#160; You can  now do easily-SARGable queries on bit-fields. The practice of using bitmaps in tables for indicating sequence has been  rendered unnecessary by the introduction of window functions. <\/p>\n<p>However, once in the while, it is useful:&#160; Bitwise  manipulation can solve some types of problem that are awkward to do any other way such as assessing check or parity bits  in constraints, doing simple encryption or data-compression (or both at the same time!). It is also interesting when you  are <a href=\"http:\/\/improve.dk\/sql-server-corruption-recovery-when-all-else-fails\/\">exploring the way that data is stored on disk<\/a> or investigating &#160;<a href=\"http:\/\/rusanu.com\/2014\/03\/10\/how-to-read-and-interpret-the-sql-server-log\/\">the  intimate secrets of the transaction log<\/a>.&#160; Again, certain applications  will throw up special cases that still require a bitmap; particularly where information is heavily sequential in nature,  but this is rare. I once used a bitmap in SQL Server&#160; for an effective  algorithm for allocating seats, or blocks of adjacent seats&#160; in a football  stadium according to a number of different rules. Occasionally, you&#8217;ll find tables from &#8216;legacy&#8217; databases that use  bitmaps coded in integer or binary columns and which will need shredding. Yes, it still pays to keep up your knowledge  of bit-manipulation.<\/p>\n<h1>Datatypes and their representation or notation.<\/h1>\n<p>In any traditional storage system, there is a clear distinction between&#160; the storage form of a datatype, its representation form and&#160; its  transmission form. &#160;A datatype is usually stored in the compact form&#160; of its binary value, ranging in size from byte, through various widths of integer and strings. Encryption and  compression merely translates these values.&#160; Datatype notations are string  versions of the datatypes, in a format designed to be readable, and most computer languages have &#8216;format&#8217; commands to do  this conversion.&#160; Although there is usually a clear distinction between the  two,&#160; there are hybrid types such as BCD, Binary-Coded-Decimal.&#160; The &#8216;transmission&#8217; representation of a datatype is an unambiguous international standard for transferring  information between different systems, to be read by machines rather than humans. Here, in this article that aims to  show bitwise manipulation, we will need to&#160; tackle the topic of&#160; converting between the storage form of&#160; data, and its representation  form as binary notation, a string comprising a series of &#8216;0&#8217;s and &#8216;1&#8217;s. The hex notation of the value of a datatype  usually has a prefix &#8216;0x&#8217;.<a href=\"http:\/\/en.wikipedia.org\/wiki\/Binary_number#Representation\">There is no general  agreement on the binary equivalent<\/a>, so I leave it out in this article, though I have a liking for the &#8216;0b&#8217; prefix<\/p>\n<h1>An example of Bitwise manipulation<\/h1>\n<p>Just to kick off, here is an example of a &#8216;legacy&#8217; SQL Server function that returns a bitmap: <code>columns_Updated()<\/code>. It is only usable or relevant in  a trigger, and&#160; tells you what columns were updated (or inserted) in a table.  It is used most often perform whatever trigger-based action you need only when particular relevant columns are updated. &#160;<\/p>\n<p>If you have a column that is rarely changed, but, if it does, it requires&#160; data manipulation elsewhere in the database, then it makes sense to do this. You know the rows that have changed  from the INSERTED and DELETED temporary, memory-resident table. If there is a change in the value of data in&#160; a particular column and row, you have to do something; maybe just an action to maintain referential integrity, or  a complex check on the integrity of the new data that can&#8217;t be done in a constraint. <\/p>\n<p>The data in <code>columns_Updated()<\/code> &#160;is simply a bitmap of the columns. If a particular column in the map was altered,  then the bit was set to 1. If you know, by patient experiment, which bit corresponds with each column, and you don&#8217;t  make rash changes to the metadata, then you&#8217;re probably home and dry at this point until someone unwittingly inserts or  deletes a column, and thereby changes the order! &#160;Just to make things more  interesting for you, Microsoft chose to make the bitmap a <a href=\"http:\/\/en.wikipedia.org\/wiki\/Endianness\">LittleEndian<\/a>  one.&#160; MSDN&#8217;s description of this is a classic<\/p>\n<p class=\"indented\"><i> &#8216;COLUMNS_UPDATED returns one or more bytes that are ordered from left to right, with the least  significant bit in each byte being the rightmost. The rightmost bit of the leftmost byte represents the first column in  the table; the next bit to the left represents the second column, and so on. COLUMNS_UPDATED returns multiple bytes if  the table on which the trigger is created contains more than eight columns, with the least significant byte being the  leftmost.<span class=\"apple-converted-space\">&#8216;<\/span><\/i><\/p>\n<p><span class=\"apple-converted-space\"> So that&#8217;s clear then.  Mercifully there are some examples.<\/span><\/p>\n<p><span class=\"apple-converted-space\"> Here is an example of using it to find out what columns have changed. We are using AdventureWorks, and  I&#8217;ve chosen a table with more than sixteen values. You may need to temporarily disable the existing trigger to try this  out.<\/span><\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF EXISTS (\n&#160; SELECT * FROM sys.triggers WHERE name LIKE 'SalesOrderHeaderAudit' \n&#160; ) --trigger names must be unique in the DB\n&#160; DROP TRIGGER&#160; sales.SalesOrderHeaderAudit \nGO\nCREATE TRIGGER Sales.SalesOrderHeaderAudit ON Sales.SalesOrderHeader\n\/**\nsummary:&#160;&#160; &gt;\n&#160; This is a simple sample trigger that sends a list of the columns \n&#160; affected by the UPDATE of a table (Sales.SalesOrderHeader) via\n&#160; the message stream. It uses a temporary table because of a bug\n&#160; in the way BIGINTs use bitwise operators, and to provide a safe\n&#160; way of doing the string concatenation trick\nAuthor: Phil Factor\nRevision: 1.1\ndate: 10 November 2014\n**\/ \n&#160;\n&#160;AFTER UPDATE AS\nDECLARE @TheInt bigint, @ListOfColumns VARCHAR(8000), @Digits CHAR(16),\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; @end INT, @ii INT, @LittleEndianHex VARCHAR(64);\nDECLARE @temp TABLE (Name VARCHAR(2000),Bits bigint,Column_ID INT PRIMARY KEY);\nSELECT @LittleEndianHex=CONVERT(VARCHAR(100),columns_Updated(),2);--get the bitmap of cols\n\/*turn this wierd thing into a proper bitmask so it is easy to use (so column 1 is bit 1,\ncolumn 2 is bit 2, column 3 is bit 3 ...and so on... *\/\nSELECT @end=LEN (@LittleEndianHex);, because we work backwards from the end\nSELECT @Theint =0, @ii=@end, @digits='123456789ABCDEF'--0 (and any rubbish) is returned as 0!\nWHILE @ii&gt;0\n&#160; BEGIN\n&#160; SELECT @TheInt=CHARINDEX(SUBSTRING(@LittleEndianHex,@ii-1,1),@digits)+(@TheInt*16);\n&#160; SELECT @TheInt=CHARINDEX(SUBSTRING(@LittleEndianHex,@ii,1),@digits)+(@TheInt*16);\n&#160; SELECT @ii-=2;\n&#160; END\n--OK. We know what we are dealing with. A conventional bitmap. Now we simply join with\n-- the system table to get the names of the columns affected \nINSERT INTO @temp (Bits, name, column_ID)\n&#160; SELECT CONVERT(bigint,POWER(2,Column_ID-1) &amp; @TheInt ) AS bits,Name, column_ID \n&#160; FROM sys.columns\n&#160; WHERE OBJECT_NAME(OBJECT_ID) ='SalesOrderHeader';\n-- and produce a comma-delimited list.&#160; \nSELECT @ListOfColumns=COALESCE(@ListOfColumns+',','')+Name \n&#160; FROM @temp \n&#160; WHERE Bits&gt;0;\n&#160; \n&#160;&#160;&#160;&#160;&#160; PRINT @ListOfColumns\n<\/pre>\n<p>With a bit of extra code, this would give you a log of all the changes, with the before and after  values, but you can do this without the bitmap. The <code>columns_Updated<\/code>() function is better for using to  implement column-based trigger actions, or for specifying actions to be executed only &#160;when  one or more specified columns change. &#160;<\/p>\n<h1>Converting between Hex, decimal and binary notations of integers and varbinaries.<\/h1>\n<p>&#160;Before you do any serious experimenting with bitwise manipulations in SQL Server, there are two  useful functions you&#8217;ll need for converting to and from binary notation. This is because the CAST and CONVERT functions  will convert HEXT to INT and INT to HEX, but will&#160; do neither Octal nor  Binary notations. We&#8217;d like to see individual bits just to see what is happening with bitwise logic. One utility  converts an integer or varbinary &#160;into a string binary-notation &#160;representation, and there is another utility to convert back the other way, from  a string binary value to an BigInt.<\/p>\n<p>Although the functions pass BigInt, beware that the MSDN documentation lists only ints, smallints  and tinyints as being suitable for use with the bitwise operators. Although I found one or two very strange quirks,  Bigint Seems to work as you&#8217;d expect.<\/p>\n<p>&#160;There is a complication with converting between HEX or Binary to decimal, or the other way. HEX or  binary has no intrinsic way of representing negative numbers. This is done by &#8216;Two&#8217;s Complement&#8217;. Although this makes  arithmetic easy, it complicates the conversion,&#160; and can cause difficulties  when using maths functions to manipulate bits.&#160; However, this is only used  with SMALLINTs, INTs and BIGINTs. Both &#8216;1111111111111111&#8217; and &#8216;11111111111111111111111111111111&#8217;, for example, &#160;mean -1, as does 64 &#8216;1&#8217;s;&#160; because  they represent&#160; SMALLINTs, INTs and BIGINTs accordingly. It will all become  clear by experiment.<\/p>\n<p>Firstly, we&#8217;ll create our functions that convert to or from the binary notation (e.g.&#160; 9 to&#160; 1001, or 1100 to 12).&#160; We start with <b>ToBinaryString<\/b>. We are able to make use of the conversion routine from a varbinary to hex to make this  easier and more versatile. If you are stuck with SQL 2005 or early versions of 2008, you&#8217;ll need to adopt the XML trick  that I&#8217;ve included, commented out.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF OBJECT_ID (N'ToBinaryString') IS NOT NULL\n&#160; DROP FUNCTION ToBinaryString\nGO\nCREATE FUNCTION ToBinaryString\n&#160;&#160;&#160;&#160;&#160; \/**\nsummary:&#160; &gt;\n&#160;Converts any type of integer that can be implicitly \n&#160;converted to a bigint into a binary string (e.g. 3 = 11)\nAuthor: Phil Factor\nRevision: 1.0\ndate: 20 May 2013\nexample:\ncode: |\n&#160; Select dbo.ToBinaryString (CAST(0xFF AS tinyint))--11111111\n&#160; Select dbo.ToBinaryString (8045)--1111101101101\n&#160; Select dbo.ToBinaryString (0xFFFF)\n&#160; Select dbo.ToBinaryString (0xAFDC1111)\nreturns:&#160; &gt;\n&#160;The number as a binary string\n**\/\n&#160; (\n&#160;&#160;&#160; @InputInteger VARBINARY(8)\n&#160; )\nRETURNS VARCHAR(70)\n&#160;&#160;&#160;&#160;&#160; AS\n&#160; BEGIN\n&#160; DECLARE @representation VARCHAR(70);\n&#160; SELECT @Representation=\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; REPLACE(\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; REPLACE(\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CONVERT(VARCHAR(64), @InputInteger, 2), --convert to HEX\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; '0','0000'),\n&#160;&#160;&#160;&#160;&#160; '1','0001'); --you need to do 0 and 1 first, and separately. \n&#160;&#160;&#160; --before 2008, one had to use this to get the hex conversion...\n&#160;&#160;&#160; --select cast('' as xml).value('xs:hexBinary(sql:variable(\"@InputInteger\") )', 'varchar(64)');\n&#160; SELECT @Representation=\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; REPLACE(@Representation,HexDigit,BinaryDigits)\n&#160;&#160;&#160; FROM (\n&#160;&#160;&#160;&#160;&#160; VALUES('2','0010'), ('3','0011'),('4','0100'),('5','0101'),('6','0110'),\n&#160;&#160;&#160;&#160;&#160; ('7','0111'),('8','1000'), ('9','1001'),('A','1010'),('B','1011'),\n&#160;&#160;&#160;&#160;&#160; ('C','1100'),('D','1101'),('E','1110'),('F','1111'))f(HexDigit,BinaryDigits);\n&#160;&#160; &#160;&#160;&#160;&#160;&#160;\/* this simply replaces the ASCII HEX notation with its ASCII binary equivalent. I haven't performance-tested it to see if it is quicker or slower than the maths technique, but it is more like SQL! *\/\n&#160; RETURN @Representation;\n&#160; END\nGO---and we now finish off by executing some quick assertion tests to check\nIF (\n&#160; SELECT dbo.ToBinaryString (3))&lt;&gt; '00000000000000000000000000000011'\n&#160; RAISERROR('''ToBinaryString '' stopped working (1)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (287))&lt;&gt; '00000000000000000000000100011111'\n&#160; RAISERROR('''ToBinaryString'' stopped working (2)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (56))&lt;&gt; '00000000000000000000000000111000'\n&#160; RAISERROR('''ToBinaryString'' stopped working (3)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (-170))&lt;&gt; '11111111111111111111111101010110'\n&#160; RAISERROR('''ToBinaryString'' stopped working (4)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (CONVERT(smallint,-1)))&lt;&gt; '1111111111111111'\n&#160; RAISERROR('''ToBinaryString'' stopped working (5)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (CONVERT(INT,-1)))&lt;&gt; '11111111111111111111111111111111'\n&#160; RAISERROR('''ToBinaryString'' stopped working (6)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (CONVERT(Bigint,-1)))&lt;&gt; (\n&#160; SELECT REPLICATE('1',64))\n&#160; RAISERROR('''ToBinaryString'' stopped working (7)',16,1);\nIF (\n&#160; SELECT dbo.ToBinaryString (0xAFDC))&lt;&gt; '1010111111011100'\n&#160; RAISERROR('''ToBinaryString '' stopped working (1)',16,1);\nGO\n\t<\/pre>\n<p>In order to convert the other way, faced, for example with a string of &#8216;0&#8217;s and &#8216;1&#8217;s, we need to  know what we want to convert to, and determine the shortest length of integer datatype that can accommodate the results.  and we have the complication of having to follow the twos complement convention where appropriate. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF OBJECT_ID (N'BinaryStringToInt') IS NOT NULL\n&#160; DROP FUNCTION BinaryStringToInt\nGO\n&#160; --and create BinaryStringToInt\nCREATE FUNCTION BinaryStringToInt\n&#160;&#160;&#160;&#160;&#160; \/**\nsummary:&#160; &gt;\n&#160;Converts a string that is a binary number into a bigint (e.g. 3 = '11')\nAuthor: Phil Factor\nRevision: 1.0\ndate: 24 May 2013\nexample:\ncode: |\n&#160; Select dbo.BinaryStringToInt('10101010') \n&#160; Select dbo.BinaryStringToInt('1110'+'1111') \nreturns:&#160; &gt;\n&#160;the binary string as an integer\n**\/\n&#160; (\n&#160;&#160;&#160; @InputBinaryString VARCHAR(2000)\n&#160; )\nRETURNS bigint\n&#160;&#160;&#160;&#160;&#160; AS\n&#160; BEGIN\n&#160; DECLARE @ii INT, @len INT,@Output Bigint,\n&#160;&#160;&#160; @length INT, @Negative bit,@CurrentBit INT;\n&#160; SELECT @InputBinaryString=REPLACE(@InputBinaryString,' ','');\n&#160;&#160;&#160; --remove any extra spaces\n&#160; SELECT @Len=LEN(@InputBinaryString), @Output=0,@ii=1\n&#160;&#160;&#160; --determine what sort of value it is\n&#160; SELECT @length=CASE\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len=1 THEN 2000 --no two's complement\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=8 THEN 2000 --no two's complement\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=16 THEN 16 -- -2^15 (-32,768) to 2^15-1 (32,767)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=32 THEN 32 -- -2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=64 THEN 64 -- -2^63 (-9,223,372,036,854,775,808) to 2^63-1 \n&#160;&#160;&#160; ELSE 2000 END; --no two's complement\n&#160;&#160;&#160; --checkk to see if we have to do twos complement maths on it.\n&#160; IF (@Len=@Length \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND @length IN (16,32,64))\n&#160;&#160;&#160; IF SUBSTRING(@InputBinaryString,@ii,1)='1'\n&#160;&#160;&#160; SELECT @Negative=1; --flag that we need to 'twos complement' \n&#160; IF (@InputBinaryString NOT LIKE '%[^01]%')--check for errors\n&#160;&#160;&#160; BEGIN --doing repeated multiplication to do the conversion\n&#160;&#160;&#160; WHILE(@ii &lt;= @len \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND @@Error=0) \n&#160;&#160;&#160;&#160;&#160; BEGIN\n&#160;&#160;&#160;&#160;&#160; SELECT @currentBit=CAST(SUBSTRING(@InputBinaryString,@ii,1) AS INT);\n&#160;&#160;&#160;&#160;&#160; IF @negative&lt;&gt;0 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT @CurrentBit= CASE @CurrentBit WHEN 0 THEN 1 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE 0 END;\n&#160;&#160;&#160;&#160;&#160; SELECT @Output = (@output*2)+@CurrentBit;\n&#160;&#160;&#160;&#160;&#160; SELECT @ii = @ii + 1;\n&#160;&#160;&#160;&#160;&#160; END\n&#160;&#160;&#160; IF @negative&lt;&gt;0 \n&#160;&#160;&#160;&#160;&#160; SELECT @Output = -(@Output+1); --twos complement \n&#160;&#160;&#160; END\n&#160; ELSE\n&#160; SELECT @Output=NULL; --unknown or error\n&#160; RETURN @output;\n&#160; END\nGO --and do our unit 'assertion' test to check that nothing obvious is broken\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('10101010')) &lt;&gt; 170\n&#160; RAISERROR('''BinaryStringToInt '' stopped working (1)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('1111111111111111')) &lt;&gt; -1\n&#160; RAISERROR('''BinaryStringToInt '' stopped working (2)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('11111111111111111111111111111111')) &lt;&gt; -1\n&#160; RAISERROR('''BinaryStringToInt '' stopped working (3)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('100011111'))&lt;&gt; 287\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (4)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('111111111'))&lt;&gt; 511\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (5)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('NotAnumber'))IS NOT NULL\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (6)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt (NULL))IS NOT NULL\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (7)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('10000000000000000000000000000000')) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;&gt; -2147483648\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (8)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringToInt ('10001000100010001000100010001000'))\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;&gt; -2004318072\n&#160; RAISERROR('''BinaryStringToInt'' stopped working (9)',16,1);\nGO\n<\/pre>\n<p>The third utility that we need for doing bitwise manipulation is only of value in going from binary  notation to HEX. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF OBJECT_ID (N'BinaryStringToHex') IS NOT NULL\n&#160; DROP FUNCTION BinaryStringToHex;\nGO\nCREATE FUNCTION BinaryStringToHex\n&#160;&#160;&#160;&#160;&#160; \/**\nsummary:&#160; &gt;\n&#160;Converts a string that is a binary number into a Hex string\nRevision: 1.0\ndate: 20 June 2013\nexample:\ncode: |\n&#160; Select dbo.BinaryStringToHex('10101010') \n&#160; Select dbo.BinaryStringToHex('10001000100010001000100010001000') \nreturns:&#160; &gt;\n&#160;the binary string as a hex string\n**\/\n&#160; (\n&#160;&#160;&#160; @InputBinaryString VARCHAR(2000)\n&#160; )\nRETURNS VARCHAR(252)\n&#160;&#160;&#160;&#160;&#160; AS\n&#160; BEGIN\n&#160;&#160;DECLARE @ii INT, @len INT,@Output Bigint, @byte tinyint,@Hex VARCHAR(100),\n&#160;&#160;&#160; @length INT, @Negative bit, @CurrentBit INT;\n&#160; SELECT @InputBinaryString=REPLACE(@InputBinaryString,' ','');\n&#160; SELECT @Len=LEN(@InputBinaryString), @Output=0, @ii=1;\n&#160; SELECT @length=CASE\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=4 THEN 4 -- NIBBLE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=8 THEN 8 -- TINYINT \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=16 THEN 16 -- smallint\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=32 THEN 32 -- int\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN @len&lt;=64 THEN 64 -- Bigint \n&#160;&#160;&#160; ELSE 2000 END --crikey\n&#160; IF (@Length &lt;&gt; 2000)\n&#160;&#160;&#160; SELECT @InputBinaryString=REPLICATE('0',@Length-@len)+@InputBinaryString\n&#160; ELSE \n&#160; IF @Len % 4 &gt;0 --round up to a 4-bit boundary \n&#160;&#160;&#160; SELECT @InputBinaryString=REPLICATE('0',4-(@len % 4))+@InputBinaryString;\n&#160; SELECT @byte=0, @ii=1;\n&#160; IF (@InputBinaryString NOT LIKE '%[^01]%')--check for errors\n&#160;&#160;&#160; WHILE @ii&lt;=LEN(@InputBinaryString)\n&#160;&#160;&#160; BEGIN --do each representation of a bit value in turn. \n&#160;&#160;&#160; SELECT @currentBit=CAST(SUBSTRING(@InputBinaryString,@ii,1) AS INT);\n&#160;&#160;&#160; SELECT @byte=(@byte*2)+@CurrentBit;\n&#160;&#160;&#160;&#160;&#160; --Select @ii,@byte,@currentbit,@hex,@CurrentBit\n&#160;&#160;&#160; IF @ii % 4=0 \n&#160;&#160;&#160;&#160;&#160; SELECT @hex=COALESCE(@hex,'0x')+SUBSTRING('0123456789ABCDEF',@byte+1,1),@Byte=0;\n&#160;&#160;&#160; SELECT @ii=@ii+1;\n&#160;&#160;&#160; END\n&#160; ELSE \n&#160; SELECT @Hex=NULL;\n&#160; RETURN @hex;\n&#160; END\nGO\n&#160; --here we do our assertion tests as a first-line check of brokenness\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('1')) &lt;&gt; '0x1'\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (1)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('11111')) &lt;&gt; '0x1F'\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (2)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('1010101111001101')) &lt;&gt; '0xABCD'\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (3)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('1')) &lt;&gt; '0x1'\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (4)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('11111')) &lt;&gt; '0x1F'\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (5)',16,1);\nIF (\n&#160; SELECT dbo.BinaryStringTohex ('BADSTRING')) IS NOT NULL\n&#160; RAISERROR('''BinaryStringToHex '' stopped working (6)',16,1);\n<\/pre>\n<p>Now we&#8217;ve got that out of the way, we can demonstrate some of the operations that are possible.<\/p>\n<p>First off, the top bit of an integer (bit 32 in an INT) is different in that it represents the sign  bit. Our function can&#8217;t easily deal with this because it doesn&#8217;t know what sort of integer (Int, smallint or tinyint)  you&#8217;ve passed to it, so it determines the smallest integer&#160; that can  accommodate &#160;the&#160; value. &#160;It then checks to see if the &#8216;sign bit&#8217; is set. This can produce results that are  counter-intuitive.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT dbo.BinaryStringToInt ('11111111111111111111111111111111')\n&#160; --&#160;&#160; -1&#160; \nSELECT dbo.BinaryStringToInt ('1111111111111111111111111111111')\n&#160; --&#160;&#160;&#160; 2147483647&#160; \n<\/pre>\n<p>We can convert from decimal to hex without a special utility <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT CONVERT(VARBINARY(8), 2147483647)--convert the result to HEX\n&#160; --&#160;&#160;&#160; 0x7FFFFFFF\n&#160; --Hex input is implicitly converted\nSELECT dbo.ToBinaryString (0xFFFFFFFF)\n&#160; --11111111111111111111111111111111\n&#160;\n--if you are stuck on slightly old versions of SQL Server, you can still use this function\nIF OBJECT_ID (N'DecToHex') IS NOT NULL DROP FUNCTION DecToHex;\nGO\nCREATE FUNCTION DecToHex (@InputInteger VARBINARY(8)) RETURNS VARCHAR(64)\nAS BEGIN \n&#160; RETURN CAST('' AS XML).value('xs:hexBinary(sql:variable(\"@InputInteger\") )', 'varchar(64)'); \nEND\nGO\nSELECT dbo.dectohex (12345)\n-- 00003039&#160; \n<\/pre>\n<p>&#160;We can easily see what can be stored in each of the different integer formats by doing a conversion  from a varbinary. This is because it truncates the excess.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">--and we can use up to 64 bit on a BIGINT\nSELECT LEN(dbo.ToBinaryString (0xFFFFFFFFFFFFFFFF));--64 bits\n&#160; --so lets just check on what is available for each type\nSELECT CONVERT(VARBINARY(8), CONVERT(BIGINT, 0xFFFFFFFFFFFFFFFFFFFF));\n&#160; --0xFFFFFFFFFFFFFFFF-64 bit\nSELECT CONVERT(VARBINARY(8), CONVERT(INT, 0xFFFFFFFFFFFFFFFFFFFF));\n&#160; --0xFFFFFFFF--32 bit\nSELECT CONVERT(VARBINARY(8), CONVERT(SMALLINT, 0xFFFFFFFFFFFFFFFFFFFF));\n&#160; --0xFFFF --16-bit \nSELECT CONVERT(VARBINARY(8), CONVERT(TINYINT, 0xFFFFFFFFFFFFFFFFFFFF));\n&#160; --0xFF --a 8-bit byte\nSELECT CONVERT(VARBINARY(8), CONVERT(bit, 0xFFFFFFFFFFFFFFFFFFFF));\n&#160; --0x01 --a 1-bit bit\nSELECT CONVERT(VARBINARY(8), 2147452927); --int to hex conversion\nSELECT dbo.toBinaryString(2147452927); -- int to binary notation&#160;&#160;&#160; \n\t<\/pre>\n<p>Having seen the size of the different integer datatypes,&#160; let&#8217;s now look at the bitwise operations.  &#160;we&#8217;ll start by demonstrating what happens when we use the various bitwise operators, using the utilities I&#8217;ve  introduced to you<\/p>\n<p>There are three that operate on two integer or binary expressions. These are &amp; (Bitwise AND)| (Bitwise OR) and ^ (Bitwise Exclusive OR).<\/p>\n<p>~ (Bitwise NOT) Performs a bitwise logical NOT operation on a single integer value.<\/p>\n<p>We can now peer at what is going on at the bit level. If you experiment with different values, it  is a great way of getting a feel for what is going on.<\/p>\n<p>&#160;We&#8217;ll run through examples using an INT integer.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">&#160; --results of a bitwise AND. (try flipping bits to convince yourself)\nSELECT dbo.toBinaryString\n&#160; (CONVERT\n&#160;&#160;&#160; (INT, -- first we do a bitwise AND\n&#160;&#160;&#160;&#160;&#160; ( dbo.BinaryStringToInt ('01111111111110111101111101111111')--AND this ...\n&#160;&#160;&#160;&#160;&#160; &amp; dbo.BinaryStringToInt ('01111111011111111000011111100111')--... with this ...\n&#160;&#160;&#160;&#160;&#160; )&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 01111111011110111000011101100111 --to get this.\n&#160;&#160;&#160; )\n&#160; );\n&#160; -- only where there were bits set in the same position in both numbers were the bits set \n&#160; --results of using a bitwise OR. \nSELECT dbo.toBinaryString\n&#160; (CONVERT\n&#160;&#160;&#160; (INT, -- then we do a bitwise OR\n&#160;&#160;&#160;&#160;&#160; ( dbo.BinaryStringToInt ('01111111111110111101111101111111')--OR this ...\n&#160;&#160;&#160;&#160;&#160; | dbo.BinaryStringToInt ('01111111011111111000011111100111')--... with this ...\n&#160;&#160;&#160;&#160;&#160; )&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 01111111111111111101111111111111 --to get this.\n&#160;&#160;&#160; )\n&#160; );\n&#160; -- 01111111111111111101111111111111&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; --to get this.\n&#160; -- where there were bits set in the same position in either numbers were the bits set \n&#160; --results of using a bitwise XOR (exclusine OR) \nSELECT dbo.toBinaryString\n&#160; (CONVERT\n&#160;&#160;&#160; (INT, -- and the last we do a bitwise XOR\n&#160;&#160;&#160;&#160;&#160; ( dbo.BinaryStringToInt ('01111111111110111101111101111111')--XOR this ...\n&#160;&#160;&#160;&#160;&#160; ^ dbo.BinaryStringToInt ('01111111011111111000011111100111')--... with this ...\n&#160;&#160;&#160;&#160;&#160; )&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 00000000100001000101100010011000 --to get this.\n&#160;&#160;&#160; )\n&#160; );\n&#160; --only where one but not both bits set in the that position in the numbers were the bits set \n&#160; --useful for toggling individual bits\n&#160;\n--and finally theresults of using a bitwise NOT. \nSELECT dbo.toBinaryString \n&#160;&#160; (Convert\n&#160;&#160;&#160;&#160; (int, -- and the last we do a bitwise XOR\n&#160;&#160;&#160;&#160;&#160; ~ dbo.BinaryStringToInt ('01111111111110111101111101111111')--NOT this ...\n&#160;&#160;&#160;&#160;&#160; )&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 10000000000001000010000010000000 --to get this.\n&#160;&#160; );\n--only where one but not both bits set in the that position in the numbers were the bits set \n&#160;\n<\/pre>\n<p>These are by no means the only useful ways of manipulating  bits. Beware, though, that you can easily get in a muddle with the effect of two&#8217;s complement  that is used to represent negative integers.<\/p>\n<p>You can shift left and right easily. You&#8217;ll get an &#8216;overflow&#8217;  error if you try to shift into the left-most bit.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT dbo.ToBinaryString ( --shift right\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; CONVERT(tinyint, dbo.BinaryStringToInt ('00100001')\/2) --shift this right\n&#160; );&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 00010000&#160;&#160;&#160; to get this&#160;&#160; \nSELECT dbo.ToBinaryString ( --shift left\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; CONVERT(tinyint, dbo.BinaryStringToInt ('00100001')*2) --shift this left\n&#160; ); &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;-- 01000010&#160;&#160;&#160; to get this\nSELECT dbo.ToBinaryString ( --shift left\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; CONVERT(tinyint, dbo.BinaryStringToInt ('00100001')*4) --shift this left\n&#160; );&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- 01000010&#160;&#160;&#160; to get this\n&#160; --What if you want to set a particular bit? Lets say you want to set the seventh bit?\nSELECT dbo.ToBinaryString (CONVERT (tinyint,POWER(2,7-1)))&#160;&#160; --Set bit 7\n&#160; -- 01000000&#160;&#160;&#160; to get this\n&#160; --so you can set any bits this way.\nDECLARE @BitToSet INT; \nSELECT @BitToSet =20;\nSELECT dbo.ToBinaryString (CONVERT (INT,POWER(2,@BitToSet-1)));\n&#160; --00000000000010000000000000000000 if @BitToSet is set to 20\n&#160; --What if you want to set several bits at once? Say bits 3, 4 and 10\nSELECT dbo.ToBinaryString (CONVERT (smallint,POWER(2,3-1) |POWER(2,4-1)|POWER(2,10-1)));\n&#160; -- 0000001000001100\nGO\n<\/pre>\n<p>So, with the means to convert between decimal, hex and binary notations, we can more easily explore  data. With the bitwise operators we can do stuff. What stuff? Well, the following is very much illustrative, as it isn&#8217;t  of serious use but it shows the power of bitmaps. In our illustration, the entire algorithm becomes useless if the  bicycle company were to introduce many more colours. I&#8217;ve seen it used in the past more effectively for weekdays or  weeks of the year, which aren&#8217;t going to change much. &#160;More damaging is the fact  that joins are likely to be inefficient with tables of any size. &#160;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">&#160;IF EXISTS (\n&#160; SELECT * \n&#160;&#160;&#160; FROM tempdb.information_schema.tables\n&#160;&#160;&#160; WHERE TABLE_NAME LIKE '#colours%')\n&#160; DROP TABLE #colours\nGO\nCREATE TABLE #colours(color_id INT IDENTITY,color VARCHAR(6), code INT not null default 0)\nINSERT INTO #colours(color,code)\n&#160; SELECT x,0 \n&#160; FROM (\n&#160;&#160;&#160; VALUES\n&#160;&#160;&#160; ('Aqua'),('Azure'),('Beige'),('Black'),('Blue'),('Brown'),('Cyan'),('Gold'),\n&#160;&#160;&#160; ('Gray'),('Green'),('Ivory'),('Khaki'),('Lime'),('Maroon'),('Navy'),('Olive'),\n&#160;&#160;&#160; ('Orange'),('Orchid'),('Pink'),('Plum'),('Purple'),('Red'),('Salmon'),('Sienna'),\n&#160;&#160;&#160; ('Silver'),('Snow'),('Tan'),('Teal'),('Violet'),('White'),('Yellow'))f(x);\nUPDATE #colours SET code=POWER(2,color_id-1);--OK it isn't real data!\nALTER TABLE #colours ADD PRIMARY KEY (code)\nIF EXISTS (\n&#160; SELECT * \n&#160;&#160;&#160; FROM tempdb.information_schema.tables\n&#160;&#160;&#160; WHERE TABLE_NAME LIKE '#bicycles%')\n&#160; DROP TABLE #bicycles;\nCREATE TABLE #bicycles(BicycleName VARCHAR(20) PRIMARY KEY, colourmap INT);\nINSERT INTO #bicycles(BicycleName,colourmap)\n&#160; SELECT x,CAST( RAND(CHECKSUM(NEWID()))*2147483647 AS INT) \n&#160; FROM (\n&#160;&#160;&#160; VALUES\n&#160;&#160;&#160; ('Hercules'),('Tartar'),('Sussex'),('Canute'),('Saxon'),('Sondes'),('Tiger'),\n&#160;&#160;&#160; ('Echo'),('Chaplin'),('Minerva'),('Nelson'),('Nile'),('Tweed'),('Undine'),\n&#160;&#160;&#160; ('Clyde'),('Gem'),('Eagle'),('Falcon'),('Lion'),('Volcano'),('Vesuvius'),\n&#160;&#160;&#160; ('Meteor'),('Swale'),('Magnus'),('Aeolus'),('Os'),('Brigand'),('Ruby'),\n&#160;&#160;&#160; ('Aas'),('Adrian'),('Huz'),('Rose'),('Dawn'),('Bluebell'),('Reindeer'),\n&#160;&#160;&#160; ('Enigma'),('Europa'),('Terrier'),('Belgravia'),('Lyons'),('Richmond'),\n&#160;&#160;&#160; ('Jumbo'),('Gladstone'),('Special'),('Pioneer'),('Precursor'),('Cowes'),\n&#160;&#160;&#160; ('Osborne'),('Newport'))f(x);\n&#160; --find out what colours are available for each bicycle\nSELECT BicycleName,color \n&#160; FROM #bicycles\n&#160;&#160;&#160;&#160;&#160; INNER JOIN #colours\n&#160;&#160;&#160;&#160;&#160; ON colourmap &amp; code &lt;&gt;0\n&#160; ORDER BY bicyclename;\n&#160; \n&#160; --find out what colours are not available for each bicycle\nSELECT BicycleName,color \n&#160; FROM #bicycles\n&#160;&#160;&#160;&#160;&#160; INNER JOIN #colours\n&#160;&#160;&#160;&#160;&#160; ON colourmap &amp; code =0\n&#160; ORDER BY bicyclename;\n&#160; \n&#160; --How many colours are available for each bicycle\nSELECT BicycleName, COUNT(*) AS ColourCount \n&#160; FROM #bicycles\n&#160;&#160;&#160;&#160;&#160; INNER JOIN #colours\n&#160;&#160;&#160;&#160;&#160; ON colourmap &amp; code &lt;&gt;0\n&#160; GROUP BY bicyclename\n&#160; ORDER BY COUNT(*) DESC\n&#160;go--------------------------------------------------\n&#160; \n&#160;&#160;--now for the next trick we need fewer bicycles\nIF EXISTS (\n&#160; SELECT * \n&#160;&#160;&#160; FROM tempdb.information_schema.tables\n&#160;&#160;&#160; WHERE TABLE_NAME LIKE '#bicycles%')\n&#160; DROP TABLE #bicycles;\nGO\nCREATE TABLE #bicycles(BicycleName VARCHAR(20) PRIMARY KEY, colourmap INT)\nINSERT INTO #bicycles(BicycleName,colourmap)\n&#160; SELECT x,CAST( RAND(CHECKSUM(NEWID()))*2147483647 AS INT) \n&#160; FROM (\n&#160;&#160;&#160; VALUES\n&#160;&#160;&#160; ('Hercules'),('Tartar'),('Sussex')\n&#160; )f(x);\n&#160;\n--what colours aren't chosen by any bicycle?\nDECLARE @bitmap INT=0 --sadly there is no OR aggregation is SQL\nSELECT @Bitmap=@bitmap | colourmap \n&#160; FROM #bicycles;\nSELECT color \n&#160; FROM #colours \n&#160; WHERE @Bitmap &amp; code = 0;\n<\/pre>\n<p>For some bitwise operations, it makes sense to have a helper table, though for our bicycle example,  it has proved unnecessary. Here is a simple way of creating the table. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF EXISTS (\n&#160; SELECT * \n&#160;&#160;&#160; FROM information_schema.tables\n&#160;&#160;&#160; WHERE TABLE_NAME LIKE 'Bithelper')\n&#160; DROP TABLE Bithelper\nGO\nCREATE TABLE BitHelper (\n&#160;TheBit INT IDENTITY,\n&#160;TheMask AS POWER(CONVERT(bigint,2),CONVERT(bigint,TheBit-1)) PERSISTED\n&#160; )\nDECLARE @ii INT\nSELECT @ii=63\nWHILE @ii&gt;0 \n&#160; BEGIN \n&#160; INSERT INTO BitHelper DEFAULT \n&#160;&#160;&#160;&#160;&#160; VALUES; \n&#160; SELECT @ii=@ii-1; END\n&#160; \n&#160; --and we can then check that it is OK\nSELECT dbo.ToBinaryString(TheMask) \n&#160; FROM BitHelper ORDER BY TheBit\n<\/pre>\n<h1>Conclusions<\/h1>\n<p>The evolution of SQL Server has gradually reduced the requirement for the database developer to be  familiar with data at BIT level &#160;or to understand bitwise operations. It hasn&#8217;t  entirely removed the requirement, though. &#160;Like a lot of the capabilities of SQL  Server, they are there when you need them and the requirement can come up unexpectedly. &#160;I  still find myself occasionally needing to work with data at the machine level, and I keep my aging HP 16C programmers&#8217;  calculator close at hand. <\/p>\n<p>Just occasionally you&#8217;ll stumble over a&#160; requirement!<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>How can you, in T-SQL, get a list of columns that have changed within a trigger? How can you see what bits are set within a varbinary or integer? How would you pass a bitmap parameter to a system stored procedure? Phil Factor shows how, introduces the bitwise operations, illustrates ways of peeking at bit-level operations, and explains the concepts behind the coding of integer datatypes.&hellip;<\/p>\n","protected":false},"author":154613,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4178,4150,4183,4252],"coauthors":[],"class_list":["post-1905","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-bi","tag-sql","tag-t-sql","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1905","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/154613"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1905"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1905\/revisions"}],"predecessor-version":[{"id":41096,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1905\/revisions\/41096"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1905"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1905"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1905"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1905"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}