SQL Server : how to convert binary back to int - sql

Admin of our IS stored 8 flags like '00010000' (only true and false) to SQL Server as binary(2). In this format data has values like '0x1000'.
Is possible to convert this binary back to '00010000' ?
Convert, Cast, Substring don't work.

Query returns hexadecimal number (0x... - it is hexadecimal) as its bit mask
CREATE TABLE #Temp(
Test VARBINARY(2)
)
INSERT #Temp
VALUES
(0x1001),
(0x3001),
(0x5000),
(0x6000),
(0xf000),
(0xf250)
SELECT *, REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
CONVERT(VARCHAR(32), Test, 2)
, '0', '0000')
, '1', '0001')
, '2', '0010')
, '3', '0011')
, '4', '0100')
, '5', '0101')
, '6', '0110')
, '7', '0111')
, '8', '1000')
, '9', '1001')
, 'a', '1010')
, 'b', '1011')
, 'c', '1100')
, 'd', '1101')
, 'e', '1110')
, 'f', '1111')
FROM #Temp
DROP TABLE #Temp

Your original data is most likely lost. When you convert a number to binary in SQL there is only a limited amount of "space" to store that data, the space is determined by the byte size of the field, in this case 2.
The issue occurs when the data is saved to the database, using a binary(2) means that data is truncated when saved, meaning that you have lost at least the 4 first "flags" in your data. (the 4 numbers at the start of your binary number).
for example, the below shows what SQL stores for the number 10010010 in binary 6, 4 and 2.
Binary(6) - 0x00000098BD9A
Binary(4) - 0x0098BD9A
Binary(2) - 0xBD9A
as you can see using binary 2 means you lose data from your number. That is gone and cannot be retrieved using the DB (unless you save transaction logs, which, given you have a DBA storing boolean values as an 8 bit block using a binary(2) field I doubt you have). So sorry to say this but you simply cannot do what you need to, and it's whoever thought using binary(2) for an 8 digit number was a good idea and then didn't test their decision that is to blame.

Yes you can
BINARY(2) means 2 bytes, so 16 bits
0x3000 = '0011000000000000'
declare #v int = 0x3000
;WITH
T AS ( SELECT NULL N UNION ALL SELECT NULL ),
N as (
SELECT ROW_NUMBER() OVER (ORDER BY T2.N) N
FROM T T2, T T4, T T8, T T16
),
V AS (
select N, POWER(2,N-1) P, CAST(#v as binary(2)) b2, #v V
from N
),
B AS (
SELECT N, B2, CAST((V/P) % 2 AS char(1)) B
from V
)
SELECT B2, [16]+[15]+[14]+[13]+[12]+[11]+[10]+[9]+[8]+[7]+[6]+[5]+[4]+[3]+[2]+[1] BASE2
FROM B
PIVOT ( MIN(B) FOR N IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]) ) P
output
B2 BASE2
0x3000 0011000000000000

Related

SQL Coalesce with missing values

I have two tables, master and child. The master's primary key MM is an INT. The child table has a compound key of two columns and value column:
MM (INT)
POS (INT, values 1-32)
VV (INT, values 1-9)
Sample master table data:
(1, other data)
(2, other data)
(3, other data)
Sample child table data
(1, 1,2)
(1, 2,2)
(1, 4,1)
(1,15,1)
(2, 4,5)
(2, 5,3)
(2,31,7)
(3,3,1)
(4,18,2)
{4,19,5)
For a report I could like to de-normalize the data with an output like this:
(1,'22010000000000010000000000000000')
(2,'00053000000000000000000000000070')
(3,'00100000000000000000000000000000')
(4,'00000000000000000025000000000000')
I was thinking to use a select query with coalesce like this but the output is not not exactly what I want:
(1,'22110')
(2,'537')
(3,'1')
(4,'25')
How do I fill in the missing data with zeros?
One way I can think to do this uses a decimal value with a precision of 32 and sum() and then convert back to a zero-padded string:
select mm,
right(replicate('0', 32) + cast(sum(val) as varchar(32)), 32)
from (select c.*,
cast(cast(val as varchar(32)) + replicate('0', 32 - pos) as decimal(32, 0)) as val
from child c
) c
group by mm;
EDIT:
The above isn't generalizable (say, above 38 characters or to use letters as well as digits). Here is a more generalizable, but longer version:
select c.mm,
(max(case when pos = 1 then valc else '0' end) +
max(case when pos = 2 then valc else '0' end) +
max(case when pos = 3 then valc else '0' end) +
. . .
max(case when pos = 32 then valc else '0' end) +
)
from (select c.*, cast(val as varchar(255)) as valc
from child c
) c
group by c.mm;
I should note that if you want to handle a master with no children, then use a left join. That aspect of the problem seems less interesting than combining the values in the appropriate positions.
Try it like this
DECLARE #master TABLE(MM INT,OtherData VARCHAR(100));
INSERT INTO #master VALUES
(1, 'Other Data 1')
,(2, 'Other Data 2')
,(3, 'Other Data 3');
DECLARE #child TABLE(MM INT, POS INT, VV INT)
INSERT INTO #child VALUES
(1, 1,2)
,(1, 2,2)
,(1, 4,1)
,(1,15,1)
,(2, 4,5)
,(2, 5,3)
,(2,31,7)
,(3,3,1)
,(4,18,2)
,(4,19,5);
--One CTE to get 32 numbers
WITH Numbers(Nr) AS
(SELECT TOP 32 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.objects) --get 32 numbers
--another CTE to get distinct MMs
,MMs AS
(
SELECT c.MM
,m.OtherData
FROM #child AS c
LEFT JOIN #master AS m ON c.MM=m.MM
GROUP BY c.MM,m.OtherData
)
--In this CTE The CROSS JOIN with the Numbers will create a list of 32 rows, which carry in all positions with a corresponding child its number. COALESCE will set a zero in the place of all NULLs
,Masked AS
(
SELECT MMs.MM
,MMs.OtherData
,Nr
,COALESCE(VV,0) AS Val
FROM MMs
CROSS JOIN Numbers
LEFT JOIN #child AS c1 ON c1.MM=MMs.MM AND c1.POS=Nr
)
-The final SELECT uses FOR XML PATH to get the 32 numbers in rows back to a string
SELECT *
,(
SELECT Masked.Val AS [*]
FROM Masked
WHERE Masked.MM=MMs.MM
FOR XML PATH('')
)
FROM MMs
The result
1 22010000000000100000000000000000
2 00053000000000000000000000000070
3 00100000000000000000000000000000
4 00000000000000000250000000000000

value substitution/replacement in a string

I have a string x-y+z. The values for x, y and z will be stored in a table. Say
x 10
y 15
z 20
This string needs to be changed like 10-15+20.
Anyway I can achieve this using plsql or sql?
using simple Pivot we can do
DECLARE #Table1 TABLE
( name varchar(1), amount int )
;
INSERT INTO #Table1
( name , amount )
VALUES
('x', 10),
('y', 15),
('Z', 25);
Script
Select CAST([X] AS VARCHAR) +'-'+CAST([Y] AS VARCHAR)+'+'+CAST([Z] AS VARCHAR) from (
select * from #Table1)T
PIVOT (MAX(amount) FOR name in ([X],[y],[z]))p
An approach could be the following, assuming a table like this:
create table stringToNumbers(str varchar2(16), num number);
insert into stringToNumbers values ('x', 10);
insert into stringToNumbers values ('y', 20);
insert into stringToNumbers values ('zz', 30);
insert into stringToNumbers values ('w', 40);
First tokenize your input string with something like this:
SQL> with test as (select 'x+y-zz+w' as string from dual)
2 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
3 FROM test
4 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
5 UNION ALL
6 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
7 FROM test
8 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
9 order by lev asc, typ desc;
TYP LEV TOKEN
------- ---------- --------------------------------
value 1 x
operand 1 +
value 2 y
operand 2 -
value 3 zz
operand 3 +
value 4 w
In the example I used lowercase literals and only +/- signs; you can easily edit it to handle something more complex; also, I assume the input string is well-formed.
Then you can join your decoding table to the tokenized string, building the concatenation:
SQL> select listagg(nvl(to_char(num), token)) within group (order by lev asc, typ desc)
2 from (
3 with test as (select 'x+y-zz+w' as string from dual)
4 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
5 FROM test
6 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
7 UNION ALL
8 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
9 FROM test
10 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
11 order by lev asc, typ desc
12 ) tokens
13 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(NVL(TO_CHAR(NUM),TOKEN))WITHINGROUP(ORDERBYLEVASC,TYPDESC)
--------------------------------------------------------------------------------
10+20-30+40
This assumes that every literal in you input string has a corrensponding value in table. You can even handle the case of strings with no corrensponding number, for example assigning 0:
SQL> select listagg(
2 case
3 when typ = 'operand' then token
4 else to_char(nvl(num, 0))
5 end
6 ) within group (order by lev asc, typ desc)
7 from (
8 with test as (select 'x+y-zz+w-UNKNOWN' as string from dual)
9 SELECT
.. ...
16 ) tokens
17 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(CASEWHENTYP='OPERAND'THENTOKENELSETO_CHAR(NVL(NUM,0))END)WITHINGROUP(ORD
--------------------------------------------------------------------------------
10+20-30+40-0
Create a function like this:
create table ttt1
( name varchar(1), amount int )
;
INSERT INTO ttt1 VALUES ('x', 10);
INSERT INTO ttt1 VALUES ('y', 15);
INSERT INTO ttt1 VALUES ('z', 25);
CREATE OR REPLACE FUNCTION replace_vars (in_formula VARCHAR2)
RETURN VARCHAR2
IS
f VARCHAR2 (2000) := UPPER (in_formula);
BEGIN
FOR c1 IN ( SELECT UPPER (name) name, amount
FROM ttt1
ORDER BY name DESC)
LOOP
f := REPLACE (f, c1.name, c1.amount);
END LOOP;
return f;
END;
select replace_vars('x-y+z') from dual
Here's another way to approach the problem that attempts to do it all in SQL. While not necessarily the most flexible or fastest, maybe you can get some ideas from another way to approach the problem. It also shows a way to execute the final formula to get the answer. See the comments below.
Assumes all variables are present in the variable table.
-- First build the table that holds the values. You won't need to do
-- this if you already have them in a table.
with val_tbl(x, y, z) as (
select '10', '15', '20' from dual
),
-- Table to hold the formula.
formula_tbl(formula) as (
select 'x-y+z' from dual
),
-- This table is built from a query that reads the formula a character at a time.
-- When a variable is found using the case statement, it is queried in the value
-- table and it's value is returned. Otherwise the operator is returned. This
-- results in a row for each character in the formula.
new_formula_tbl(id, new_formula) as (
select level, case regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
when 'x' then
(select x from val_tbl)
when 'y' then
(select y from val_tbl)
when 'z' then
(select z from val_tbl)
else regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
end
from formula_tbl
connect by level <= regexp_count(formula, '.')
)
-- select id, new_formula from new_formula_tbl;
-- This puts the rows back into a single string. Order by id (level) to keep operands
-- and operators in the right order.
select listagg(new_formula) within group (order by id) formula
from new_formula_tbl;
FORMULA
----------
10-15+20
Additionally you can get the result of the formula by passing the listagg() call to the following xmlquery() function:
select xmlquery(replace( listagg(new_formula) within group (order by id), '/', ' div ')
returning content).getNumberVal() as result
from new_formula_tbl;
RESULT
----------
15

Matching multiple key/value pairs in SQL

I have metadata stored in a key/value table in SQL Server. (I know key/value is bad, but this is free-form metadata supplied by users, so I can't turn the keys into columns.) Users need to be able to give me an arbitrary set of key/value pairs and have me return all DB objects that match all of those criteria.
For example:
Metadata:
Id Key Value
1 a p
1 b q
1 c r
2 a p
2 b p
3 c r
If the user says a=p and b=q, I should return object 1. (Not object 2, even though it also has a=p, because it has b=p.)
The metadata to match is in a table-valued sproc parameter with a simple key/value schema. The closest I have got is:
select * from [Objects] as o
where not exists (
select * from [Metadata] as m
join #data as n on (n.[Key] = m.[Key])
and n.[Value] != m.[Value]
and m.[Id] = o.[Id]
)
My "no rows exist that don't match" is an attempt to implement "all rows match" by forming its contrapositive. This does eliminate objects with mismatching metadata, but it also returns objects with no metadata at all, so no good.
Can anyone point me in the right direction? (Bonus points for performance as well as correctness.)
; WITH Metadata (Id, [Key], Value) AS -- Create sample data
(
SELECT 1, 'a', 'p' UNION ALL
SELECT 1, 'b', 'q' UNION ALL
SELECT 1, 'c', 'r' UNION ALL
SELECT 2, 'a', 'p' UNION ALL
SELECT 2, 'b', 'p' UNION ALL
SELECT 3, 'c', 'r'
),
data ([Key], Value) AS -- sample input
(
SELECT 'a', 'p' UNION ALL
SELECT 'b', 'q'
),
-- here onwards is the actual query
data2 AS
(
-- cnt is to count no of input rows
SELECT [Key], Value, cnt = COUNT(*) OVER()
FROM data
)
SELECT m.Id
FROM Metadata m
INNER JOIN data2 d ON m.[Key] = d.[Key] AND m.Value= d.Value
GROUP BY m.Id
HAVING COUNT(*) = MAX(d.cnt)
The following SQL query produces the result that you require.
SELECT *
FROM #Objects m
WHERE Id IN
(
-- Include objects that match the conditions:
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value = d.Value
-- And discount those where there is other metadata not matching the conditions:
EXCEPT
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value <> d.Value
)
Test schema and data I used:
-- Schema
DECLARE #Objects TABLE (Id int);
DECLARE #Metadata TABLE (Id int, [Key] char(1), Value char(2));
DECLARE #data TABLE ([Key] char(1), Value char(1));
-- Data
INSERT INTO #Metadata VALUES
(1, 'a', 'p'),
(1, 'b', 'q'),
(1, 'c', 'r'),
(2, 'a', 'p'),
(2, 'b', 'p'),
(3, 'c', 'r');
INSERT INTO #Objects VALUES
(1),
(2),
(3),
(4); -- Object with no metadata
INSERT INTO #data VALUES
('a','p'),
('b','q');

how to custom sort data in sql server

I am using Sql server 2008 r2, I need to order by the following data:
CardNo
R-1
R-2
R-12
R-1A
R-3
R-2B
Result should look like this
CardNo
R-1
R-1A
R-2
R-2B
R-3
R-12
I have tried different combinations in order by clause but of no use like:
select * from [Coll2012-13] where
SUBSTRING(CardNo, 1, 1) IN ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z')
AND SUBSTRING(CardNo, 2, 1) IN ('-')
AND SUBSTRING(CardNo, 3, 1) IN ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0')
and Landmark='Anandbagh' order by LEN(CardNo),CardNo ASC
Assumption: Values are always of the format 'Letter-Alphanumeric string'
Try this:
select card_no
from [Coll2012-13]
order by left(card_no,1),
case
when isnumeric(substring(card_no,3, len(card_no))) = 1
then cast(substring(card_no,3, len(card_no)) as int)
else cast(substring(card_no,3, patindex('%[A-Z]%',substring(card_no,3, len(card_no)))-1) as int)
end,
case
when patindex('%[A-Z]%', substring(card_no,3,len(card_no))) > 0
then substring(card_no,patindex('%[A-Z]%', substring(card_no,3,len(card_no)))+2,1)
end
How this works: First check the starting letter. Next, check if the alphanumeric part is in fact only numeric. If so, get the integer value of that part. If it is not, get the numeric part of it and use that as the sort value. Finally, if the alphanumeric part does contain a letter, use that as another sort value.
Demo here.
You should replace all non digit chars with "" and convert the rest in a number. Then sort by the number.
You can try to use a function as described here SQL Server 2000: how do I return only the number from a phone number column
for this
A simple - but 'should be maintained' - solution is to create a sorting table and join that table to your result set. The table contains all CardNo values and associates a sort order to them.
EDIT:
CREATE TABLE CardNoOrderHelper (
CardNo VARCHAR(16)
, OrderRank INT CONSTRAINT DF_CardNoOrderHelper_OrderRank DEFAULT 0
, PRIMARY KEY CLUSTERED (
CardNo ASC
)
);
-- Fill your table with the expected sort order (dinstinct insert, than adjust the order ranks)
SELECT
*
FROM
[Coll2012-13] AS T
LEFT JOIN CardNoOrderHelper CH
ON T.CardNo = CH.CardNo
ORDER BY
T.OrderRank
As I said, you have to maintain this table. When your resultset is small this can be done manually.
Try this
with cte
AS
(
select
*
,substring(CardNo, 1, charindex('-')-1) RealRank1
,substring(CardNo, charindex('-')+ 1, 10) RealRank2
from
[Coll2012-13]
)
select
*
from
cte
order by
RealRank1
,RealRank2
I have the similar problem. I have used this query below.
To use this query u must know the ID (string) maximum length and I have adjusted the format in this query to use Mark-Number+Alphabet (example R-1,R-1A, R-11, R-11A, R-1AA,R-1B)
Query:
select
b.CardNo, b.separatorIndex
,b.Mark, b.Mark_length
,case b.isNumericMark1 + b.isNumericMark2 + b.isNumericMark3
when 1 then cast (b.Mark1 as int)
when 2 then cast (b.Mark1 + b.Mark2 as int)
when 3 then cast (b.Mark1 + b.Mark2 + b.Mark3 as int)
end as Mark1
from
(
select
a.CardNo
,charindex('-',a.cardNo,0) as separatorIndex
, len(a.cardNo) - charindex('-',a.cardNo,0) as Mark_length
, substring(a.CardNo,0,charindex('-',a.cardNo,0)) as Mark
, substring(a.CardNo,charIndex('-',a.cardNo,0)+1,1) as Mark1
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+1,1*1)) as isNumericMark1
, substring(a.CardNo,charIndex('-',a.cardNo,0)+2,1) as Mark2
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+2,1)) as isNumericMark2
, substring(a.CardNo,charIndex('-',a.cardNo,0)+3,1) as Mark3
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+3,1)) as isNumericMark3
from [Coll2012-13] a
) b
order by Mark,Mark1,Mark_length
Result:
CardNo separatorIndex Mark Mark_length Mark1
-------------------- -------------- -------------------- ----------- -----------
R-1 2 R 1 1
R-1A 2 R 2 1
R-2 2 R 1 2
R-2B 2 R 2 2
R-3 2 R 1 3
R-12 2 R 2 12
Hope this help.

sql code to convert varchar colum to int

I am stuck on converting a varchar column schedule containing the following data 0, 1, 2, 3,4,8,9,10,11,12,15,16,17,18,19 to INT. I know, please don't ask why this schedule column was not created as INT initially, long story.
So I tried this, but it doesn't work. and give me an error:
select CAST(schedule AS int) from shift_test:
the query should check if the numbers representing days are found in the schedule filed using the sql code below
select empid, case when ((DateDiff(hour,'01-01-2014 07:00' , '01-01-2014 00:00')/ 24 )% 15) in ( CAST(schedule AS int))
then 'A' else '*' end as shift_A from Shift_test
After executing i get this error.
Conversion failed when converting the varchar value to int.
Any help will be appriciated
Use ISNUMERIC() test if you are using version 2008 or 2008R2. In SQL SERVER 2012 you can use TRY_CAST() function, which checks if the data conversion is allowed for given literal.
Mock up code for SQL Server 2008/R2:
Select col1, col2,
case
when <condition> and isnumeric(col2) then cast(col2 as int)
else <do whatever...>
end
as converted_col2
from <yourtable>;
For SQL Server 2012:
Select col1, col2,
case
when <condition> then try_cast(col2 as int)
else <do whatever...>
end
as converted_col2
from <yourtable>;
Example with SQl Server 2008
declare #T table (empid int, schedule varchar(2)) ;
insert into #T(empid, schedule) values (1, '1');
insert into #T(empid, schedule) values (2, '2');
insert into #T(empid, schedule) values (3, '03');
insert into #T(empid, schedule) values (4, '4');
insert into #T(empid, schedule) values (5, '05');
insert into #T(empid, schedule) values (6, 'A');
select empid,
case
when ISNUMERIC(schedule) = 1
and ((DateDiff(hour,'01-01-2014 07:00' , '10-01-2014 00:00')/ 24 )% 15)
in ( CAST(schedule AS int)) then 'A'
else '*'
end
as shift_A
from #T;
# Binaya Ive added my code for more help.
The schedule column contain (0, 1, 2, 3,4,8,9,10,11,12,15,16,17,18,19) which is varchar.
i want to output A if after this calculation ((DateDiff(hour,'01-01-2014 07:00',startdate)/ 24 )% 15) the result is found in the schedule column else if after the calculation and the result is not found output *
;with Shift_runover (shift_code,schedule,endd,startdate)
-- Start at the beginning of shift.
as
(select shift_code,schedule,Cast(end_date as DateTime) as endd,Cast(start_date as DateTime)as startdate from dbo.Shift_test
union all
-- Add hours up to the desired end date.
select shift_code,schedule,endd,DateAdd(hour, 1,startdate)from Shift_runover where startdate<=endd),
Extendedsamples as
(
-- Calculate the number of days since the beginning of the first shift on 1/1/2014.
select shift_code,schedule,startdate,DateDiff(hour,'01-01-2014 07:00',startdate)/ 24 as Days from Shift_runover ),
Shifts as
(
-- the schedule column contain (0, 1, 2, 3,4,8,9,10,11,12,15,16,17,18,19) which is varchar.
-- i want to output A if ((DateDiff(hour,'01-01-2014 07:00',startdate)/ 24 )% 15) is found in the schedule colume
select *,
case when (DateDiff(hour,'01-01-2014 07:00',startdate)/ 24 )% 15 in(schedule)
then 'A' else '*' end as shift_A
from ExtendedSamples
)
select *
from Shifts
option ( maxrecursion 0 )