ASCII increment with defined range - sql-server

Client wants to append a field with a literal increment based on a count.
The range goes from 'aa' to 'zz'.
'aa' represents a count of 1 and 'zz' represents the max value in the range: 676
I have sql that almost works but would appreciate an expert eye to get me over the last hurdle.
--Constants
DECLARE #START_ASCII INT = 97
DECLARE #ASCII_OFFSET INT = 1
DECLARE #ALPHABET_LETTER_COUNT INT = 26
--Variables
DECLARE #RecordCount INT = 0
DECLARE #FirstLetter VARCHAR(1) = NULL
DECLARE #SecondLetter VARCHAR(1) = NULL
SET #RecordCount = 1 --Range is 1 to 676 (e.g. 'aa' to 'zz')
SET #FirstLetter = CHAR(round(#RecordCount / #ALPHABET_LETTER_COUNT, 2, 1) + #START_ASCII)
SET #SecondLetter = CHAR((((#RecordCount - #ASCII_OFFSET) % #ALPHABET_LETTER_COUNT) + #START_ASCII))
SELECT #FirstLetter + #SecondLetter
The problem with the above sql involves the first letter. It works till the end of the alphabet is reached for the second letter. For example, at a count of 26, I expect 'az', but instead get 'bz'.
I want to keep the SQL small and tight (e.g. no CASE statements). Is there a small tweak I can make to the above code so that it will work?
Or, if there is just a smarter way to skin this cat, I'd like to know that.

I would think of this as computing the base-26 representation of #RecordCount-1 (range 0 to 675). Then map the two-digits of the base-26 number to the ASCII characters:
SET #FirstLetter = CHAR(floor((#RecordCount-1) / #ALPHABET_LETTER_COUNT) + #START_ASCII)
SET #SecondLetter = CHAR(((#RecordCount-1) % #ALPHABET_LETTER_COUNT) + #START_ASCII)

Related

Generate 6 digit alpha numeric value

I have the following code; the only issue with it is that sometimes it generates a digit alphanumeric code.
I am trying to modify it such that it always returns a 6 digit code. Any thoughts on how to tweak this?
I have another procedure the purges duplicates and it re-runs, so that is not an issue.
SET #HashCode = NULL
SELECT
#HashCode = ISNULL(#HashCode, '') + SUBSTRING('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', (ABS(CHECKSUM(NEWID())) % 36) + 1, 1)
FROM
[master]..[spt_values] AS [spt_values] WITH (NOLOCK)
WHERE
[spt_values].[type] = 'P'
AND [spt_values].[number] < 6
A very simple way of achieving this is selecting the first 6 LEFT characters in the string generated by the NEWID() function:
SELECT LEFT(NEWID(), 6)
Examples:
4DF32A
DC70D5
8793B2
3D8416
EE2838
Note, because the NEWID() function generates a unique identifier (GUID), consisting of hexadecimal characters (A-F & 0-9), there is a chance the output will consist of only numbers or only letters; which may or may not fit your purpose if you require a strict alphanumeric value.
Examples:
946983
831814
DDFDBB
You can use it like this:
SET #HashCode = NULL
SELECT
#HashCode = LEFT(NEWID(), 6)

Is there a way in SQL server to interpret the underlying varchar(4) bits as an INT?

I have data harvested from a binary file that has been encoded as a SQL column with type varchar(4). This is not changeable. The 4 bytes used to create this varchar need to be interpreted sometimes as an int value (big endian). It would be nice if we could do this entirely inside SQL.
Printing the values in this varchar(4) column is not helpful as most of the bytes get interpreted as unprintable control characters.
I can't figure out how CAST or CONVERT can help since they seem to be tailored to converting a varchar like "0054" to int 54. Instead, I need the underlying bits to be interpreted as an int (big endian)--not the varchar characters as an int.
For example, one record prints this column as no visible characters, but STRING_ESCAPE(#value,'json')
will display
\u0000\u0000\u0000\u0007
This needs to be interpreted somehow to be the int 7
Here's a few more examples of what STRING_ESCAPE returns and what the int value should be:
\u0000\u0000\u0000\b ==> 8
\u0000\u0000\u0000\t ==> 9
\u0000\u0000\u0000\n ==> 10
\u0000\u0000\u0000\u000b ==> 11
\u0000\u0000\u0000\f ==> 12
\u0000\u0000\u0000\r ==> 13
\u0000\u0000\u0000\u000e ==> 14
\u0000\u0000\u0000\u000f ==> 15
\u0000\u0000\u0000\u0010 ==> 16
Thanks for your brain!
So, here is a table of sample data. The first row represents your main example. But you don't have any examples where any one of the first 3 characters is not character 0. So I threw in another row where this is the case.
declare #values table (value char(4))
insert #values values
(char(0) + char(0) + char(0) + char(7)),
(char(13) + char(9) + char(14) + char(8));
In the query below, I isolate each character using substring. Then I call ascii to retrieve the character code. What is not clear, however, is how you would take those integer values and combine them. I give 3 possibilities. 'Option1' concatenates them. 'Option2' sums them together. 'Option3' concatenates them like option1, but pads them first so that there is a leading '0' if it is only one digit long.
select escapedVal = string_escape(value,'json'),
ap.*,
option1 = convert(int,concat(pos1, pos2, pos3, pos4)),
option2 = pos1 + pos2 + pos3 + pos4,
option3 = convert(int,
right('00' + convert(varchar(2),pos1),2) +
right('00' + convert(varchar(2),pos2),2) +
right('00' + convert(varchar(2),pos3),2) +
right('00' + convert(varchar(2),pos4),2)
)
from #values v
cross apply (select
pos1 = ascii(substring(value,1,1)),
pos2 = ascii(substring(value,2,1)),
pos3 = ascii(substring(value,3,1)),
pos4 = ascii(substring(value,4,1))
) ap;
This produces:
escapedVal
pos1
pos2
pos3
pos4
option1
option2
option3
\u0000\u0000\u0000\u0007
0
0
0
7
7
7
7
\r\t\u000e\b
13
9
14
8
139148
44
13091408
CAST(CAST(#value as BINARY(4)) as INT)
The part I was missing is specifying the size of binary as 4. Without the size, it always casts to 0!

How can I return the first N number of characters ending with a complete word from a dash-delimited string?

I need a function that outputs first N number of characters in a dash-delimited input string.
Requirements:
If N drops in the middle of a word, include the last word in the output even if total gets more than N
If the output ends with "-" AND LEN(output) == N , then include next word
Example: (N = 70)
declare #Text varchar(1000) = 'this-is-product-url-prepared-for-better-Google-Search-Engnine-SEO-totalLength-should-be-70-characters'
I have already taken the first 70 characters, which results in the following (I remove ending dash - in a separate step not included here):
SELECT LEFT(#Text + '-', CHARINDEX('-',#Text, 70))
this-is-product-url-prepared-for-better-Google-Search-Engnine-SEO-tot
I need a function that returns the following in this case:
this-is-product-url-prepared-for-better-Google-Search-Engnine-SEO-totalLength
Any solutions will be appreciated.
UPDATED ANSWER: Fixed your expression
This will work and is based on what #Lukstroms posted; his does not handle your second requirement (when #N is the position of a hyphen.)
SELECT SUBSTRING(#Text,1,CHARINDEX('-',#Text,#N+1)-1);
PREVIOUS ANSWER:
Here's how you could do something like this using ngrams8k This will handle situations where #N represents the middle of the word or a dash -.
DECLARE
#Text VARCHAR(1000) = 'this-is-product-url-prepared-for-better-Google-Search-Engnine-SEO-totalLength-should-be-70-characters',
#N INT = 70;
SELECT SUBSTRING(#Text,0,MAX(ng.nxt))
FROM
(
SELECT ng.position, nxt = LEAD(ng.Position,1) OVER (ORDER BY ng.Position), ng.Token
FROM samd.ngrams8k(#Text,1) AS ng
WHERE ng.Token = '-'
) AS ng
WHERE ng.Position <= #N;
FINAL UPDATE (NOTE ABOUT N-GRAMS)
As Martin correctly mentioned, the N-Grams solution is overly complex but I was in a rush and couldn't fix the OP's original CHARINDEX expression. That said, ngrams8k solution is nasty fast allows a bunch of flexibility.
For example, let's say the requirement included an upper and lower bound parameter; e.g. we needed everything between the first hyphen higher than #Low and the last hyphen lower than #High. This can be handled using a minor tweak to the answer above.
DECLARE
#Text VARCHAR(1000) = 'this-is-product-url-prepared-for-better-Google-Search-Engnine-SEO-totalLength-should-be-70-characters',
#Low INT = 16,
#high INT = 70;
SELECT NewString = SUBSTRING(#Text,MIN(ng.position)+1, MAX(ng.nxt)-MIN(ng.position)-1)
FROM
(
SELECT ng.position, nxt=LEAD(ng.Position,1) OVER (ORDER BY ng.Position)
FROM samd.ngrams8k(#Text,1) AS ng
WHERE ng.Token = '-'
) AS ng
WHERE ng.Position <= #high AND ng.Position > #low;
Returns:
prepared-for-better-Google-Search-Engnine-SEO-totalLength
Thanks #LukStorms
I achieved this by the following statement:
LEFT(#Text,CHARINDEX('-',#Text+'-',70)-1)

How to convert an EBCDIC zoned decimal to a decimal in MS SQL Server 2014?

Hi everyone this is major chunk for me I have been trying lot but not getting success
there is data in db as [AH_TRAN_CV] of length 9 as Integer. and I want that in varchar with 9 char and 2 decimals.
I guess this whole code may be wrong as I don't have knowledge just used internet to get till here.. The sample is like
123D is +1234
678{ is +6780
468K is -4682
Digit Positive Negative
0 { }
1 A J
2 B K
3 C L
4 D M
5 E N
6 F O
7 G P
8 H Q
9 I R
So kindly help. Below is just part of code
Code :
Case When (substring(convert(varchar,[AH_TRAN_CV] * 0.0000001), 9, 1) = '4'
and ([AH_TRAN_CV] >= 0.00))
Then concat (substring(convert(varchar,[AH_TRAN_CV]*0.0000001), 3, 8), 'D')
end [AH_TRAN_CV]
This encoding isn't a numeric format that can be parsed to a number. It's an EBCDIC zoned decimal encoding found only in mainframes.
You can create a function that splits the string and generates the appropriate sign and last digit from the input string, eg :
create function ZonedToNumber(#somenum varchar(20))
returns varchar(20)
as
begin
declare #c char = right(#somenum,1);
declare #digit char = case when #c between 'A' and 'I' then (ascii(#c) -64)
when #c between 'J' and 'R' then (ascii(#c) -73)
when #c in ('{','}') then 0
else null end
declare #sign char = case when #c between 'A' and 'I' or #c = '{' then '+'
when #c between 'J' and 'R' or #c = '}' then '-'
else null end
RETURN CONCAT(#sign,left(#somenum,len(#somenum)-1) ,#digit)
end
Instead of performing any clever arithmetic, the function uses CASE and subtracts a base ASCII value for each case to generate the last digit and sign.
You can apply that function to a zoned decimal string to convert it to an actual number, eg: SELECT dbo.ZonedToNumber('1234') will return =1234. You can also use it in a query :
declare #t table (num varchar(20))
insert into #t
values
('123D'),
('123K'),
('123{'),
('123}')
select cast(dbo.ZonedToNumber(num) as int)
from #t x
This will return :
1234
-1232
1230
-1230
You should use this function only to convert zoned decimals to actual numbers when loading the data. Almost nobody (outside mainframes) uses this format anymore.

How do I generate a random number for each row in a T-SQL select?

I need a different random number for each row in my table. The following seemingly obvious code uses the same random value for each row.
SELECT table_name, RAND() magic_number
FROM information_schema.tables
I'd like to get an INT or a FLOAT out of this. The rest of the story is I'm going to use this random number to create a random date offset from a known date, e.g. 1-14 days offset from a start date.
This is for Microsoft SQL Server 2000.
Take a look at SQL Server - Set based random numbers which has a very detailed explanation.
To summarize, the following code generates a random number between 0 and 13 inclusive with a uniform distribution:
ABS(CHECKSUM(NewId())) % 14
To change your range, just change the number at the end of the expression. Be extra careful if you need a range that includes both positive and negative numbers. If you do it wrong, it's possible to double-count the number 0.
A small warning for the math nuts in the room: there is a very slight bias in this code. CHECKSUM() results in numbers that are uniform across the entire range of the sql Int datatype, or at least as near so as my (the editor) testing can show. However, there will be some bias when CHECKSUM() produces a number at the very top end of that range. Any time you get a number between the maximum possible integer and the last exact multiple of the size of your desired range (14 in this case) before that maximum integer, those results are favored over the remaining portion of your range that cannot be produced from that last multiple of 14.
As an example, imagine the entire range of the Int type is only 19. 19 is the largest possible integer you can hold. When CHECKSUM() results in 14-19, these correspond to results 0-5. Those numbers would be heavily favored over 6-13, because CHECKSUM() is twice as likely to generate them. It's easier to demonstrate this visually. Below is the entire possible set of results for our imaginary integer range:
Checksum Integer: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Range Result: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5
You can see here that there are more chances to produce some numbers than others: bias. Thankfully, the actual range of the Int type is much larger... so much so that in most cases the bias is nearly undetectable. However, it is something to be aware of if you ever find yourself doing this for serious security code.
When called multiple times in a single batch, rand() returns the same number.
I'd suggest using convert(varbinary,newid()) as the seed argument:
SELECT table_name, 1.0 + floor(14 * RAND(convert(varbinary, newid()))) magic_number
FROM information_schema.tables
newid() is guaranteed to return a different value each time it's called, even within the same batch, so using it as a seed will prompt rand() to give a different value each time.
Edited to get a random whole number from 1 to 14.
RAND(CHECKSUM(NEWID()))
The above will generate a (pseudo-) random number between 0 and 1, exclusive. If used in a select, because the seed value changes for each row, it will generate a new random number for each row (it is not guaranteed to generate a unique number per row however).
Example when combined with an upper limit of 10 (produces numbers 1 - 10):
CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1
Transact-SQL Documentation:
CAST(): https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql
RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx
CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx
NEWID(): https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql
Random number generation between 1000 and 9999 inclusive:
FLOOR(RAND(CHECKSUM(NEWID()))*(9999-1000+1)+1000)
"+1" - to include upper bound values(9999 for previous example)
Answering the old question, but this answer has not been provided previously, and hopefully this will be useful for someone finding this results through a search engine.
With SQL Server 2008, a new function has been introduced, CRYPT_GEN_RANDOM(8), which uses CryptoAPI to produce a cryptographically strong random number, returned as VARBINARY(8000). Here's the documentation page: https://learn.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql
So to get a random number, you can simply call the function and cast it to the necessary type:
select CAST(CRYPT_GEN_RANDOM(8) AS bigint)
or to get a float between -1 and +1, you could do something like this:
select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0
The Rand() function will generate the same random number, if used in a table SELECT query. Same applies if you use a seed to the Rand function. An alternative way to do it, is using this:
SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]
Got the information from here, which explains the problem very well.
Do you have an integer value in each row that you could pass as a seed to the RAND function?
To get an integer between 1 and 14 I believe this would work:
FLOOR( RAND(<yourseed>) * 14) + 1
If you need to preserve your seed so that it generates the "same" random data every time, you can do the following:
1. Create a view that returns select rand()
if object_id('cr_sample_randView') is not null
begin
drop view cr_sample_randView
end
go
create view cr_sample_randView
as
select rand() as random_number
go
2. Create a UDF that selects the value from the view.
if object_id('cr_sample_fnPerRowRand') is not null
begin
drop function cr_sample_fnPerRowRand
end
go
create function cr_sample_fnPerRowRand()
returns float
as
begin
declare #returnValue float
select #returnValue = random_number from cr_sample_randView
return #returnValue
end
go
3. Before selecting your data, seed the rand() function, and then use the UDF in your select statement.
select rand(200); -- see the rand() function
with cte(id) as
(select row_number() over(order by object_id) from sys.all_objects)
select
id,
dbo.cr_sample_fnPerRowRand()
from cte
where id <= 1000 -- limit the results to 1000 random numbers
select round(rand(checksum(newid()))*(10)+20,2)
Here the random number will come in between 20 and 30.
round will give two decimal place maximum.
If you want negative numbers you can do it with
select round(rand(checksum(newid()))*(10)-60,2)
Then the min value will be -60 and max will be -50.
try using a seed value in the RAND(seedInt). RAND() will only execute once per statement that is why you see the same number each time.
If you don't need it to be an integer, but any random unique identifier, you can use newid()
SELECT table_name, newid() magic_number
FROM information_schema.tables
You would need to call RAND() for each row. Here is a good example
https://web.archive.org/web/20090216200320/http://dotnet.org.za/calmyourself/archive/2007/04/13/sql-rand-trap-same-value-per-row.aspx
The problem I sometimes have with the selected "Answer" is that the distribution isn't always even. If you need a very even distribution of random 1 - 14 among lots of rows, you can do something like this (my database has 511 tables, so this works. If you have less rows than you do random number span, this does not work well):
SELECT table_name, ntile(14) over(order by newId()) randomNumber
FROM information_schema.tables
This kind of does the opposite of normal random solutions in the sense that it keeps the numbers sequenced and randomizes the other column.
Remember, I have 511 tables in my database (which is pertinent only b/c we're selecting from the information_schema). If I take the previous query and put it into a temp table #X, and then run this query on the resulting data:
select randomNumber, count(*) ct from #X
group by randomNumber
I get this result, showing me that my random number is VERY evenly distributed among the many rows:
It's as easy as:
DECLARE #rv FLOAT;
SELECT #rv = rand();
And this will put a random number between 0-99 into a table:
CREATE TABLE R
(
Number int
)
DECLARE #rv FLOAT;
SELECT #rv = rand();
INSERT INTO dbo.R
(Number)
values((#rv * 100));
SELECT * FROM R
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]
has always worked for me
Use newid()
select newid()
or possibly this
select binary_checksum(newid())
If you want to generate a random number between 1 and 14 inclusive.
SELECT CONVERT(int, RAND() * (14 - 1) + 1)
OR
SELECT ABS(CHECKSUM(NewId())) % (14 -1) + 1
DROP VIEW IF EXISTS vwGetNewNumber;
GO
Create View vwGetNewNumber
as
Select CAST(RAND(CHECKSUM(NEWID())) * 62 as INT) + 1 as NextID,
'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'as alpha_num;
---------------CTDE_GENERATE_PUBLIC_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_PUBLIC_KEY;
GO
create function CTDE_GENERATE_PUBLIC_KEY()
RETURNS NVARCHAR(32)
AS
BEGIN
DECLARE #private_key NVARCHAR(32);
set #private_key = dbo.CTDE_GENERATE_32_BIT_KEY();
return #private_key;
END;
go
---------------CTDE_GENERATE_32_BIT_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_32_BIT_KEY;
GO
CREATE function CTDE_GENERATE_32_BIT_KEY()
RETURNS NVARCHAR(32)
AS
BEGIN
DECLARE #public_key NVARCHAR(32);
DECLARE #alpha_num NVARCHAR(62);
DECLARE #start_index INT = 0;
DECLARE #i INT = 0;
select top 1 #alpha_num = alpha_num from vwGetNewNumber;
WHILE #i < 32
BEGIN
select top 1 #start_index = NextID from vwGetNewNumber;
set #public_key = concat (substring(#alpha_num,#start_index,1),#public_key);
set #i = #i + 1;
END;
return #public_key;
END;
select dbo.CTDE_GENERATE_PUBLIC_KEY() public_key;
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))
Number between 1 and 10.
Try this:
SELECT RAND(convert(varbinary, newid()))*(b-a)+a magic_number
Where a is the lower number and b is the upper number
If you need a specific number of random number you can use recursive CTE:
;WITH A AS (
SELECT 1 X, RAND() R
UNION ALL
SELECT X + 1, RAND(R*100000) --Change the seed
FROM A
WHERE X < 1000 --How many random numbers you need
)
SELECT
X
, RAND_BETWEEN_1_AND_14 = FLOOR(R * 14 + 1)
FROM A
OPTION (MAXRECURSION 0) --If you need more than 100 numbers

Resources