Convert Numeric into Rowversion / Timestamp - sql

Using SQL Server 2008+.
I have a rowversion column (aka timestamp) which I retrieve from the database and convert into into a numeric(20,0) using the following code:
CONVERT(NUMERIC(20,0), RowVersionColumn + 0) AS [RowVersion]
Later on, I need to take that numeric value (which is stored in a ADO.NET DataSet as a ulong/UInt64), and convert it back into a rowversion data type.
This creates problems, though. It seems as though while you can convert out to numeric(20,0), the reverse operation fails to yield the correct value. For instance:
DECLARE #MyNumericValue NUMERIC(20,0)
DECLARE #MyRowVersionValue ROWVERSION
SET #MyNumericValue = 12345
SET #MyRowVersionValue = CONVERT(rowversion, #MyNumericValue)
PRINT CONVERT(NUMERIC(20,0), #MyRowVersionValue + 0)
Running this code prints out a value of 959447040, not 12345. Removing the "+ 0" from the CONVERT statement yeilds the correct result, but I'm still unable to reliably take a numeric value that used to be a rowversion and turn it back into a rowversion whose value is what it should be.
Below is an even better demonstration of this issue:
DECLARE #MyNumericValue NUMERIC(20,0)
DECLARE #MyRowVersionValue ROWVERSION
DECLARE #MyRowVersionValue2 ROWVERSION
SET #MyRowVersionValue = ##DBTS
SET #MyNumericValue = CONVERT(NUMERIC(20,0), #MyRowVersionValue + 0)
SET #MyRowVersionValue2 = CONVERT(rowversion, #MyNumericValue)
SELECT #MyRowVersionValue
SELECT #MyNumericValue
SELECT #MyRowVersionValue2
Your results will vary depending on your inputs, but as an example my output was:
0x0000000003ADBB2F
61717295
0x140000012FBBAD03
The first and last values should match, but they don't. I'm guessing this has something to do with the fact that binary data conversions often result in padding, and this padding changes the value. See:
http://msdn.microsoft.com/en-us/library/aa223991(v=SQL.80).aspx
Thoughts?
EDIT: To clarify, the fact I'm dealing with rowversion/timestamp is incidental. This same issue happens with BINARY(8) instead of ROWVERSION, as they're equivalent data types.

While I don't understand why you would be converting a meaningless ROWVERSION value to a number and then back again, did you try with BIGINT - which matches the 8 bytes required by UInt64 and not the variable number of bytes (depending on the actual value) that forcing NUMERIC(20,0) would change it to?
CREATE TABLE dbo.rv(a INT, rv ROWVERSION);
INSERT dbo.rv(a) SELECT 1 UNION SELECT 2;
WITH x AS
(
SELECT
rv,
rv_as_bigint = CONVERT(BIGINT, rv)
FROM dbo.rv
)
SELECT
rv,
rv_as_bigint,
rv_back_to_rv = CONVERT(ROWVERSION, rv_as_bigint)
FROM x;
DROP TABLE dbo.rv;
Or taking your original sample and just swapping BIGINT in for NUMERIC(20,0):
DECLARE #MyNumericValue BIGINT
DECLARE #MyRowVersionValue ROWVERSION
DECLARE #MyRowVersionValue2 ROWVERSION
SET #MyRowVersionValue = ##DBTS
SET #MyNumericValue = CONVERT(BIGINT, #MyRowVersionValue) -- removed the +0 hack
SET #MyRowVersionValue2 = CONVERT(rowversion, #MyNumericValue)
SELECT #MyRowVersionValue
SELECT #MyNumericValue
SELECT #MyRowVersionValue2

When you convert data to binary (rowversion is essentially binary(8)), you get its internal representation rather than the logical value. You can verify the behavior with the following snippet:
DECLARE #MyNumericValue NUMERIC(20,0) = 0
SELECT #MyNumericValue, CONVERT(binary(8), #MyNumericValue)
Or you can try
DECLARE #MyBinary binary(8) = 0x00
SELECT #MyBinary, CONVERT(numeric(20,0), #MyBinary)
It won't convert at all.
So, what is really going on with your script is:
SET #MyNumericValue = 12345
-- #MyRowVersionValue gets the internal representation of the numeric value 12345
SET #MyRowVersionValue = CONVERT(rowversion, #MyNumericValue)
-- +0 converts the binary value to int, then performs int-to-numeric conversion
PRINT CONVERT(NUMERIC(20,0), #MyRowVersionValue + 0)
-- Real binary-to-numeric conversion.
PRINT CONVERT(NUMERIC(20,0), #MyRowVersionValue)
The real binary-to-numeric conversion is safe, but in your case, you may want to convert the rowversion to a bigint (or forget about the numeric value and just store it as a binary string) rather than numeric, since CONVERT(NUMERIC(20,0), #MyRowVersionValue) doesn't really get you the numeric value.

Related

SQL find error value on table or skip error

I have over thousands data on my table, in this case i want to change data type hexadecimal value into integer. here's table example :
hex int
000001CA |
000001D3 |
000001F5 |
this is my query :
UPDATE table
SET int = CONVERT(int, CONVERT(varbinary, hex, 2))
when i execute the query i get error message that some value error failed converting data type but i dont know which one because there are many data on the table. Is there a way to find the error value or just skip the error and continue other value ?
Thanks for the help.
You can simply change it to:
UPDATE table
SET int = TRY_CONVERT(int, TRY_CONVERT(varbinary, hex, 2))
TRY_CONVERT will attempt to convert the value and if it cannot, return a NULL.
Another option is to select the hex values from your table and use
WHERE TRY_CONVERT(int, TRY_CONVERT(varbinary, hex, 2)) IS NOT NULL
to get all the valid, convert-able values. Then you can run your update knowing it is only using valid values.
You can use TRY_CONVERT to find the records that are failing to convert. This function will return NULL if conversion fails.
Returns a value cast to the specified data type if the cast succeeds; otherwise, returns null.
https://learn.microsoft.com/en-us/sql/t-sql/functions/try-convert-transact-sql?view=sql-server-ver15
select t.*
from table t
where try_convert(int, CONVERT(varbinary, t.hex, 2)) is null;
There's no problem with your Update statement except for the table name(table). Even a table with this name cannot be created, but if you convert it to [table], then the statement will be succesfull :
UPDATE [table]
SET int = CONVERT(int, CONVERT(varbinary, hex, 2)); -- case hex is type VARCHAR
SELECT *
FROM [table];
int hex
458 000001CA
467 000001D3
501 000001F5
OR
UPDATE [table]
SET int = CONVERT(int, hex) -- case hex is type VARBINARY
SELECT *
FROM [table];
int hex
458 0x000001CA
467 0x000001D3
501 0x000001F5
Demo
P.S.:TRY_CONVERT(int, TRY_CONVERT(varbinary, hex, 2)),which's stated within the other answers, would return the same result.

Conversion failed when converting the varchar value '28,29,30,31,32' to data type smallint

Trying to do some custom dumps to csv files for a customer's access control system, having trouble assigning a string to a variable.
Each Badge Reader would be something along the lines of machine 1-20 and reader 1-20 so essentially I could do all of this with or statements but I would need 1 for each reader.
I think I know the issues, and its having trouble discerning the different numbers with the comma and trying to convert.
It works perfectly when I change it to single variables.
Declare #Panels as varchar;
Declare #Readers as varchar;
Set #Panels = ('28,39,30,31,32')
Set #Readers = ('1,2,3,4,5,6')
SELECT READER.READERDESC, EVENTS.CARDNUM, EVENT.EVDESCR, EVENTS.EVENT_TIME_UTC
FROM EMP
INNER JOIN EVENTS ON EMP.ID = EVENTS.EMPID
INNER JOIN EVENT
ON EVENTS.EVENTTYPE = EVENT.EVTYPEID
AND EVENTS.EVENTID = EVENT.EVID
INNER JOIN READER
ON EVENTS.MACHINE = READER.PANELID
AND EVENTS.DEVID = READER.READERID
WHERE EVENT_TIME_UTC >= DATEADD (DAY, -1, (CONVERT (date, GETDATE())))
AND EVENT_TIME_UTC <= CONVERT (date, GETDATE())
AND (MACHINE = #Panels AND DEVID = #Readers)
Msg 245, Level 16, State 1, Line 4
Conversion failed when converting the varchar value '1,2,3,4,5,6' to data type int.
First, in SQL Server, your code will not generate this error.
This code:
Declare #Panels as varchar;
Declare #Readers as varchar;
Set #Panels = ('28,39,30,31,32')
Set #Readers = ('1,2,3,4,5,6')
assigns the values '2' and '1' to the variables respectively. Why? Because you need the length when declaring a string.
Second, strings are strings and they are compared in their entirety. This code:
MACHINE = #Panels
says "I'm comparing an integer to a string. They are not same type, so I'd better convert. Let me check. Oh, the rules say to convert the string to an integer. Oops, can't do that. ERROR!".
You seem to want to split the string. You would do that as:
MACHINE IN (SELECT * FROM SPLIT(#Panels, ',') s)
This still has the implicit type conversion, but it would normally work. In production code, I would be more pedantic:
MACHINE IN (SELECT TRY_CONVERT(INT, VALUE) FROM SPLIT(#Panels, ',') s)

Conversion error despite using CAST?

I have a cursor which fetches reference codes from a table and increments the code by one if it fits certain criteria. The reference is alphanumeric so it is declared as nvarchar.
To keep things simple, assume that #RefNo = 'v1', with the intention of changing this to v2:
DECLARE #versionNo INT
DECLARE #RefNo nvarchar(50)
DECLARE #NewVersionNo INT
DECLARE #NewRefNo nvarchar(50)
set #VersionNo = Right(#RefNo, 1)
set #NewVersionNo = #versionNo + 1
set #NewRefNo = Left(#RefNo, Len(#RefNo - 1)) + cast(#NewVersionNo as nvarchar)
print #NewRefNo
The final line fails with error Conversion failed when converting the nvarchar value 'v1' to data type int. To an extent I get why this is happening - the '+' operator can't handle nvarchar and int values at the same time - but I would have thought the cast to nvarchar on #NewVersionNo would have avoided that.
Also note that I am using 2008R2 so am unable to use the CONCAT function.
you have miss place closing bracket, change your code of line as below
set #NewRefNo = Left(#RefNo, Len(#RefNo) - 1) + cast(#NewVersionNo as nvarchar)
-----------^
If #RefNo='V1'
Output:

how to sum up value within one cell SQL

I have some binary values such as 00, 0000, 001000.11111000, 1111100000
I need to sum it up so it turns into 0, 0, 1, 5, 5 ( sum 0s and 1s up)
how can we do that in SQL please?
Thanks
Assumption:
The binary values are stored as string.
Each value is in its own cell in a table. Something like:
BinaryValues (Consider it a column name)
00
0000
001000
and so on.
You want to add up the individual digits to get the sum.
SQL Product you are usind supports functions, looping, string manipulation like substring, extracting string length etc.
As per my best knowledge these are primitives available in all SQL products.
Solution:
Write a function (call it by any name. Ex: AddBinaryDigits) which will take the binary value in string format as input.
Inside the function and do a string manipulation. Extract each digit and add it up. Return the sum as result.
Call the function:
If using binary values stored in a table:
SELECT AddBinaryDigits(BinaryValues) FROM <WhatEverTableName>
If using fixed value:
SELECT AddBinaryDigits('00')
SELECT AddBinaryDigits('0000')
SELECT AddBinaryDigits('001000')
and so on.
Edited to include the request to create function.
CREATE FUNCTION <funtionName>
(
#ParameterName AS VARCHAR(expected string length like 10/15/20 etc.)
)
RETURNS INT
BEGIN
SQL Code to sum
RETURN SummedUpValue
END
Use the below query. If needed convert it into function.
create function dbo.fnSumChars(#someInt VARCHAR(20))
RETURNS INT
AS
BEGIN
DECLARE #count INT = LEN(#someInt),
#counter INT = 1
DECLARE #Sum INT = 0
WHILE #counter <= #count
BEGIN
SELECT #sum += CAST(SUBSTRING(CAST(#someInt AS VARCHAR), #counter, 1) AS int)
SELECT #counter += 1
END
RETURN #sum --5
END
This is the function and you can call this function like below
SELECT dbo.fnSumChars('1111100000')
If these are already in string format, this is the easiest:
select len(replace('1111100000', '0', ''))
No need for a function either, because it can be inlined in the query. Functions, even the light ones, incure perf penalty.

How to do bitwise exclusive OR in sql server between two binary types?

According this link:
Bitwise Operators (Transact-SQL)
we can do bitwise operation between binary and int, smallint, tinyint or vice versa.
But how can I make a bitwise exclusive OR in sql server between two binary types?
Or if this is not possible how can I split a binary/varbinary to individual bytes?
The reason I'm asking for this is because I need to xor two numbers bigger than max int value.
Thanks.
All comments in code block
-- variables
declare #vb1 binary(16), #vb2 binary(16), #lo binary(8), #hi binary(8)
-- 2 guids to compare
declare #guid1 uniqueidentifier set #guid1 = '96B4316D-1EA7-4CA3-8D50-FEE8047C1329'
declare #guid2 uniqueidentifier set #guid2 = 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
-- split every 8 bytes into a binary(8), which is a bigint, the largest size usable with XOR
select #vb1 = #guid1, #vb2 = #guid2
-- xor the high and low parts separately
select #hi = convert(binary(8), substring(#vb1,1,8)) ^ convert(bigint, substring(#vb2,1,8))
select #lo = convert(binary(8), substring(#vb1,9,8)) ^ convert(bigint, substring(#vb2,9,8))
-- the final result, concatenating the bytes using char(8) - binary -> uniqueidentifier
select 'A', #guid1 union all
select 'B', #guid2 union all
select 'A XOR B = ', convert(uniqueidentifier, convert(binary(16),convert(char(8),#hi) + convert(char(8),#lo)))
Per the Bitwise Exclusive OR documentation:
Note
Only one expression can be of either binary or varbinary data type
in a bitwise operation.
The comment in the question from Martin, gave me an idea how to split binary so I can XOR the values.
Originally I wanted to XOR two GUIDs in sql. So here is the code I came with:
declare #guid1 uniqueidentifier
declare #guid2 uniqueidentifier
declare #guid3_hi binary(8)
declare #guid3_lo binary(8)
declare #guid3_temp varchar(32)
declare #guid3_char varchar(36)
declare #guid3 uniqueidentifier
set #guid1 = '96B4316D-1EA7-4CA3-8D50-FEE8047C1329'
set #guid2 = 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
set #guid3_hi = CAST(SUBSTRING(CAST(#guid1 as binary(16)),1,8) as bigint) ^ CAST(SUBSTRING(CAST(#guid2 as binary(16)),1,8) as bigint)
set #guid3_lo = CAST(SUBSTRING(CAST(#guid1 as binary(16)),9,8) as bigint) ^ CAST(SUBSTRING(CAST(#guid2 as binary(16)),9,8) as bigint)
set #guid3_temp = SUBSTRING(dbo.sp_hexadecimal(#guid3_hi), 3, 16) + SUBSTRING(dbo.sp_hexadecimal(#guid3_lo), 3, 16)
select #guid3_temp
set #guid3_char = SUBSTRING(#guid3_temp, 1, 8) + '-' + SUBSTRING(#guid3_temp, 9, 4) + '-' + SUBSTRING(#guid3_temp, 13, 4) + '-' + SUBSTRING(#guid3_temp, 17, 4) + '-' + SUBSTRING(#guid3_temp, 21, 12)
select #guid3_char
set #guid3 = convert(uniqueidentifier, #guid3_char)
select #guid3
--result 92CE4B69-58E1-5CB3-72AF-0117FB83ECD6
The function to convert binary to hex string is from: Converting Binary Data to Hexadecimal String
I know that in SQL 2008 we can use convert function to do this as this post explained: SQL Server 2008 : new binary – hex string conversion, but this was not an option in my case.
However it will be good if someone has a better idea how we can use SQL bitwise operations on binary data type.
EDIT:
Thanks to cyberkiwi for providing the correct algorithm and for point the error in my code.
This code could be good for XOR-ing binary but not for GUIDs, as GUIDs have different byte order for first and last 8 bytes. Please look at the wikipedia explanation for this: GUID Basic structure. Note that if you are going to use the XOR-ed result as real GUID you should take and into account the version bits.
I used bigints for storing both values. This way you'll get more range. If the value is bigger than bigint, you might need to split the values in two bigint and use AND / OR operator combination.