How to get individual bytes from a SQL binary field - sql

I have a binary field in SQL Server which I want to read one byte at time in a SQL function. In code I would use a byte array. Is there an equivalent in SQL?
I couldn't find anything with google.

The SUBSTRING function should be sufficient. A quick example, assuming table MyTable with column SomeData, binary(10) not null:
DECLARE
#OneByte binary(1)
,#Loop int
SET #Loop = 0
WHILE #Loop < 10
BEGIN
SET #Loop = #Loop + 1
SELECT #OneByte = substring(SomeData, #Loop, 1)
from MyTable
-- Process accordingly
END
There are fancier set-based ways to do this, but for short values this should be adequate.

You could loop through the binary field using SUBSTRING.
declare #BinaryColumn binary(5)
set #BinaryColumn = convert(binary,'abcde')
declare #Counter int, #ColumnLength int
set #Counter = 1
set #ColumnLength = LEN(#BinaryColumn)
while (#Counter <= #ColumnLength) begin
select SUBSTRING(#BinaryColumn, #Counter, 1)
set #Counter = #Counter + 1
end /* while */

Varbinary as a type will act as a byte array, and you can read an individual byte from it using substring.

Related

I want to print a sequence of letters and concatenate it with an existing string

I have a requirement where i need to add an alphabet A to Z sequentially to a string.
example :
OGCP223000 + A
OGCP223000 + B,etc
So that I get
OGCP223000A,OGCP223000B till OGCP223000Z, and start again every time I run the SP.
So one time I call the stored procedure I want A, and the next time I want B, and the time after that i want C, and so on
Please help
I used numbers by creating a sequence in SQL server, but could not for Alphabets
You can use a sequence to produce a cycle of alphabetic values. Start with a sequence that cycles through values from 1 to 26:
create sequence AlphaValue as Int MinValue 1 MaxValue 26 Cycle;
Then you can use Char to convert the returned value into the corresponding letter:
declare #Count as Int = 0;
declare #AlphaValue as Int;
while #Count < 30
begin
set #AlphaValue = next value for AlphaValue;
select #Count as Count, #AlphaValue as AlphaValueInt,
-- Convert the 1..26 value into the corresponding letter.
Char( ASCII( 'A' ) + #AlphaValue - 1 ) as AlphaValueChar;
set #Count += 1;
end;
dbfiddle.
Note that the usual warnings about sequences sometimes skipping values apply.
Thank you, was able to figure it out, with your help of course.. Please see the code below, This will print A to Z individually every time I run this block of code
Declare #Alphabet TABLE (Alpha1 VARCHAR(1), Alphaint VARCHAR(2), Count1 INT)
Declare #Count as Int = 0;
Declare #AlphaValue as Int;
while #Count < 30
begin
set #AlphaValue = next value for AlphaValue;
INSERT #Alphabet (Count1, Alphaint, Alpha1)
select #Count as Count, #AlphaValue as AlphaValueInt,
-- Convert the 1..26 value into the corresponding letter.
Char( ASCII( 'A' ) + #AlphaValue - 1 ) as AlphaValueChar;
set #Count += 1;
end;
Select Alpha1 from #Alphabet where Count1=0

Extract number from a string value

I have a varchar that always come into this format:
'PB' + multiple Leading 0 + Number + Non-Number Character(s).
For example: PB000013452S, PB000013452S3s2fss.
How do I parse the varchar value to get the "Number" (13452) in this case?
Use PATINDEX to find the position of the first number (that isn't 0) and then PATINDEX again to find the position of the first non-numerical character afterwards. Then use SUBSTRING to extract the number:
SELECT SUBSTRING(V.YourString,PI.I,PATINDEX('%[^0-9]%',STUFF(V.YourString,1,PI.I-1,''))-1)
FROM (VALUES('PB000013452S'),('PB000013452S3s2fss'))V(YourString)
CROSS APPLY (VALUES(PATINDEX('%[1-9]%',V.YourString)))PI(I)
I write an algorithme for your problem you can try it and i tested befor it works perfectly but i stored numbers in a table and if you want to concatenate them you can use cursor
declare #x varchar(30) = 'PB000013452S3s2fss' /*your string here*/
declare #_len int = len(#x) /*length of your string */
declare #array table (num varchar(30)) /*table for collecte number*/
declare #c int =1 /*counter*/
declare #_char varchar(1) /* to store one char from your string */
declare #result varchar(30)=''
while #_len>0
begin
set #_char = SUBSTRING(#x,#c,1)
if(#_char in ('1','2','3','4','5','6','7','8','9'))
begin
while #_len>0
begin
set #_char = SUBSTRING(#x,#c,1)
if(#_char in ('0','1','2','3','4','5','6','7','8','9'))
begin
insert into #array values (#_char)
set #c = #c+1
set #_len = #_len-1
end
else
set #_len = 0
end
end
set #c = #c+1
set #_len = #_len-1
end
select * from #array

How to order by postcode?

I have a small report that needs to be ordered by postcode. How do I do this?
Using ORDER BY Postcode returns
SK1
SK11
SK13
SK2
How can I return
SK1
SK2
SK11
SK13
EDIT
I should really have added more to the question, I am working with postcodes for the whole of the UK, not just ones starting with SK. So some of these postcodes will start with only 1 letter, some with 2. Also, the second part of the postcode is in the column.
Assuming MSSQL, and that your Postcode field follows a consistent pattern of Char(2) + Number, then you could add a computed query column:
postcode_num = convert(int,substring(postcode,3,len(postcode)))
And then use it instead of Postcode for sorting:
order by postcode_num
Results as desired:
Create 2 columns:
1. a VARCHAR for the first part;
2. a TINYINT for the last (numeric) part.
ORDER BY postcode_prefix, postcode_suffix
Source: https://www.sitepoint.com/community/t/order-by-postcode/50042/9
The problem you are facing is that the column you are trying to ORDER BY is of type text and not numeric, therefore SQL will perform the ordering you're seeing. Instead, if you want SQL to order it as if it was a number then you would need to substring the "SK" part of the column, cast the number characters to numeric type and then order by that.
This is what #LONG replied to you in the first comment.
The way I would approach it is to create a couple of generic functions that will strip the alpha or numeric portions from the string before you sort.
In my example the functions are in the fn schema so change this as you require.
ORDER BY fn.StripToAlpha(PostCode), fn.StripToNumeric(PostCode)
There are plenty of examples of these types of functions around, probably more efficient than the ones I wrote but below is the code to produce the ones I use.
CREATE FUNCTION [fn].[StripToAlpha]
(
#inputString nvarchar(4000)
)
RETURNS varchar(4000)
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 65 AND 90) OR (#ascii BETWEEN 97 AND 122)
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
RETURN #strReturnVal
END
and
CREATE FUNCTION [fn].[StripToNumeric]
(
#inputString nvarchar(4000)
)
RETURNS Float
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #ReturnVal Float
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
IF #inputString IS NULL
BEGIN
Return NULL
END
-- swap out comma for decimal
SET #inputString = REPLACE(#inputString, ',', '.')
IF #Len = 0 OR LEN(LTRIM(RTRIM(#inputString))) = 0
BEGIN
SET #ReturnVal=0
END
ELSE
BEGIN
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 48 AND 57) OR (#ascii IN (46,37))
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
if RIGHT(#strReturnVal,1)='%'
BEGIN
SET #strReturnVal = LEFT(#strReturnVal,len(#strReturnVal)-1)
SET #strReturnVal = CAST((CAST(#strReturnVal AS FLOAT)/100) AS nvarchar(4000))
END
SET #ReturnVal = ISNULL(#strReturnVal,0)
END
RETURN #ReturnVal
END
Notes
This will not affect your current use but the StripToNumeric checks is a percentage sign is present and converts to a decimal so it you pass it 25% it will return 0.25.
This will not work if you use full postcodes such as SK1 1AB as it would sort by SKAB and then 11
It will work on postcodes with shorter prefixes such M34 (That's Denton if I remember correctly ! :) )
You didn't specify database you use; this is an Oracle example. Hopefully, you'll be able to "convert" it to something else.
The idea is: using regular expressions (which seem to be quite handy in such cases), split postcode to two parts: letters and numbers. As REGEXP_SUBSTR returns a string, I applied the TO_NUMBER function to a "numeric" part of the postcode in order to properly sort it.
SQL> with test (postcode) as
2 (select 'sk1' from dual union
3 select 'sk11' from dual union
4 select 'sk13' from dual union
5 select 'sk2' from dual
6 )
7 select postcode
8 from test
9 order by regexp_substr(postcode, '^[[:alpha:]]+'), --> letters
10 to_number(regexp_substr(postcode, '[[:digit:]]+$')); --> numbers
POST
----
sk1
sk2
sk11
sk13
SQL>

Is there a simple way to do hexadecimal arithmetic using sql server/TSQL?

I have a column of hexadecimal values in a table. I want to add a hex value to all values in that table. If it were a simple int I would run something like this:
UPDATE myTable
SET num = num + 4000
Is there any way to do this simply using hexadecimal arithmetic? Or do I have to convert the column value to decimal, convert the value I want to add to decimal, add them, and convert the value back to hex? (And if so, what's the simplest way to do that?)
(NOTE: We are currently using sql server 2000.)
use something like :
print convert(varbinary(4),0 + 0x002E + 0x001D)
it should give you a result like :
0x0000004B
the zero in the equation fools it to believe its all numbers so it calculates the value.
Assuming that num is actually a string representation of the hexadecimal number, I think you can convert it to an integer by using a couple of User Defined Functions:
-- Based on Feodor's solution on
-- http://blog.sqlauthority.com/2010/02/01/sql-server-question-how-to-convert-hex-to-decimal/
CREATE FUNCTION fn_HexToInt(#str varchar(16))
RETURNS BIGINT AS BEGIN
SELECT #str=upper(#str)
DECLARE #i int, #len int, #char char(1), #output bigint
SELECT #len=len(#str),#i=#len, #output=case WHEN #len>0 THEN 0 END
WHILE (#i>0)
BEGIN
SELECT #char=substring(#str,#i,1)
, #output=#output
+(ASCII(#char)
-(case when #char between 'A' and 'F' then 55
else case when #char between '0' and '9' then 48 end
end))
*power(16.,#len-#i)
, #i=#i-1
END
RETURN #output
END
-- Example conversion back to hex string - not very tested
CREATE FUNCTION fn_IntToHex(#num int)
RETURNS VARCHAR(16) AS BEGIN
DECLARE #output varchar(16), #rem int
SELECT #output = '', #rem=0
WHILE (#num > 0)
BEGIN
SELECT #rem = #num % 16
SELECT #num = #num / 16
SELECT #output = char(#rem + case when #rem between 0 and 9 then 48 else 55 end) + #output
END
RETURN #output
END
select dbo.fn_HexToInt ('7FFF') -- = 32767
select dbo.fn_IntToHex(32767) -- = 7FFF
So you can try
UPDATE myTable
SET num = dbo.fn_IntToHex(dbo.fn_HexToInt(num) + 4000)
You can use the prefix 0x
eg
Select 0x3F + 2
returns 65
So
UPDATE myTable
SET num = num + 0x4000
(This works in SQL 2008 - I'm not sure if it's new since SQL 2000 - let me know!)
If you have two 0x values, they get concatenated by the + operator, so use convert to convert one of them to an int

Generate Random values from SQL

It appear that SQL Server like most other products Random Function really is not that random. So we have this nice little function to generate a 10 char value. Is there a better way to accomplish what the following does. I am betting there is.
DECLARE #SaltCount INT;
SELECT #SaltCount = COUNT(*) FROM tmp_NewLogin;
PRINT 'Set Salt values for all records' + CAST(#SaltCount AS VARCHAR(10))
DECLARE #CharPool CHAR(83);
DECLARE #Salt VARCHAR(10);
SET #CharPool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&()*+,-./:;<=>?#';
SET NOCOUNT ON;
updateSaltValue:
SET #Salt = ''
SELECT #Salt = #Salt + SUBSTRING(#CharPool, number, 1) FROM
(
SELECT TOP 10 number FROM MASTER..[spt_values] WHERE TYPE = 'p' AND Number BETWEEN 1 AND 83
ORDER BY NEWID()
) AS t
UPDATE TOP(1) [table] SET [Salt] = #Salt WHERE [Salt] IS NULL
IF (##ROWCOUNT > 0)
GOTO updateSaltValue
SET NOCOUNT OFF;
PRINT 'Completed setting salts for all records';
Most programmers make a mistake of reinventing the randomization functionality and end up with something that is not random at all. I'd recommend you to stick with built-in RAND() function. Seed it once then fetch as many values as you need.
Reinventing RAND is a recipe for disaster. Where have you ever noticed it behaving incorrectly? I don't think you even need to seed it. SQL Server should seed it on its own just fine. Seeding should just be necessary when you need to produce the same "random" sequence several times when testing algorithms or some such.
According to books-on-line for rand() function: If seed is not specified, the Microsoft SQL Server 2005 Database Engine assigns a seed value at random. For a specified seed value, the result returned is always the same.
You can avoid this with quick & dirty trick:
Create view like this:
create view [dbo].[wrapped_rand_view]
as
select rand( ) as random_value
Next create function that reads from the view:
create function [dbo].[wrapped_rand]()
returns float
as
begin
declare #f float
set #f = (select random_value from wrapped_rand_view)
return #f
In this way you have random seed each time when you call your wrapped_rand() function and distinct random value between 0 and 1.
Use the Rand() function.... and seed it with something else random like the number of millesconds in the current sysDate or current timestamp... Or a call to NewId() function...
Not the full-alphabet-randomness you have but kind of random:
select substring(replace(newid(),'-',''),0,10)
Edit: I learned from the comments that newid() isn't very good for randomness, especially in combination with substring.
Sometimes there is a need to reset a password using a temporary password or generate a random password for a new user.
The following stored procedure creates strings of random characters based on four parameters that configure the result.
> create proc [dbo].uspRandChars
> #len int,
> #min tinyint = 48,
> #range tinyint = 74,
> #exclude varchar(50) = '0:;<=>?#O[]`^\/',
> #output varchar(50) output as
> declare #char char
> set #output = ''
>
> while #len > 0 begin
> select #char = char(round(rand() * #range + #min, 0))
> if charindex(#char, #exclude) = 0 begin
> set #output += #char
> set #len = #len - 1
> end
> end