Generate Random values from SQL - 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

Related

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>

Function returning 2 different results - T-SQL

I have used this site before for help with various things in the past, and in this instance, I couldn't find anything in the search box, so apologies if this exists elsewhere.
In sql server 2005, I have several stored procedures that change various bits of code, and recently we have created a function that adds spaces into a defined string. So in theory, I pass a string into it, and I get a result as blocks of 4. When I run this manually, and define the actual text, it splits fine (I get #### 0000 012 returned) but when I execute the function within the SP, I get #### 0012 0012. Is there any reason why?
I have set a print command to the string before it gets passed into my function, and it prints "####0000012 " and the print after is "#### 0012 0012"
Below is the function code, with no declares:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER function [dbo].[udf_addspaces](#string varchar(255),#lengthbetween int)
returns varchar(100)
as
BEGIN
declare #i int, #stringlen float, #output varchar(255), #outputofloop varchar(4)
set #stringlen = LEN(#string)/#lengthbetween
set #output =''
set #i = 0
while #i <= #stringlen
BEGIN
set #outputofloop = left(#string,#lengthbetween)
if #lengthbetween < LEN(#string)
BEGIN
set #string = right(#string,LEN(#string)-#lengthbetween)
END
set #output = #output + #outputofloop +' '
set #i = #i+1
END
return #output
END
Here is the bit of the SP that executes this:
set #Consignment2 = (#Consignment) + rtrim(#Check14)
print #Consignment2
set #Consignment2 = dbo.udf_addspaces(#Consignment2,4)
print #Consignment2
Here are the lines it prints: (Note: #### replaces a 4 digit number, removed for security reasons)
####0000012
#### 0012 0012
Regards,
Luke M
Even though you've defined stringlen as a float, it will be an integer value, because the two values you're dividing are ints.
There's a difference between a char(14) mentioned in your comments, to a varchar(14). The char(14) is guaranteed to be 14 characters long. The varchar may not be.
I think the body of your function could be more succinctly expressed as this...
declare #result varchar(500)
select #result = ''
select
#result = #result
+ substring(#string, number*#lengthBetween+1, #lengthBetween)
+ ' '
from master..spt_values
where type='p'
and number <= (len(#string)/#lengthBetween)
return rtrim(#result)

Quickest way to replace series of characters from start and end in T-SQL

I have a requirement to replace the first and last 2 characters of a string with asterisks(*) in SQL, example:
Input : 123456
Output : **34**
This is how I've done it, is there anyway I can improve the script below?(make it shorter, call less built in functions to get the required output etc)
DECLARE #Number VARCHAR(64)
SET #Number = '123456789'
IF(LEN(#Number) >= 5)
BEGIN
--Remove the first two characters
SET #Number = SUBSTRING(#Number,3,LEN(#Number))
--Remove the last two characters
SET #Number = SUBSTRING(#Number,0,LEN(#Number)- 1)
--Add two asterisks at front and end
SEt #Number = '**' + #Number + '**'
SELECT #Number
END
Try this way:
DECLARE #Number VARCHAR(64)
SET #Number = '123456789'
select '**'+substring(#Number,3,len(#Number)-4)+'**'
SQL Fiddle Demo
You can try STUFF function
SET #Number = STUFF(STUFF(#Number,1,2,'**'),LEN(#Number)-1,2,'**')
SQLFiddle DEMO

How to get individual bytes from a SQL binary field

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.

Creating a Function in SQL Server with a Phone Number as a parameter and returns a Random Number

I am hoping someone can help me here as google is not being as forthcoming as I would have liked. I am relatively new to SQL Server and so this is the first function I have set myself to do.
The outline of the function is that it has a Phone number varchar(15) as a parameter, it checks that this number is a proper number, i.e. it is 8 digits long and contains only numbers. The main character I am trying to avoid is '+'. Good Number = 12345678 Bad Number = +12345678. Once the number is checked I would like to produce a random number for each phone number that is passed in.
I have looked at substrings, the like operator, Rand(), left(), Right() in order to search through the number and then produce a random number. I understand that Rand() will produce the same random number unless alterations are done to it but right now it is about actually getting some working code. Any hints on this would be great or even point me towards some more documentation. I have read books online and they haven't helped me, maybe I am not looking in the right places.
Here is a snippet of code I was working on the Rand
declare #Phone Varchar (15)
declare #Counter Varchar (1)
declare #NewNumber Varchar(15)
set #Phone = '12345678'
set #Counter = len(#Phone)
while #Counter > 0
begin
select case when #Phone like '%[0-9]%' then cast(rand()*100000000 as int) else 'Bad Number' end
set #counter = #counter - 1
end
return
Thanks for the help in advance
Emer
Simply use LIKE and ensure each digit is between 0 and 9.
One way to generate random numbers is CHECKSUM(NEWID()), or use this as the seed for RAND
IF #phone LIKE '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
SELECT #NewNumber = LEFT(
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)), 15)
Or the double negative LIKE with length check
IF #phone NOT LIKE '%[^0-9]%' AND LEN(#phone) = 8
SELECT #NewNumber = LEFT(
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)), 15)
I thought I would update my post with the solution I have come up with for other people who may be searching for something similar. From my research you are unable to use RAND() within a UDF. Instead you have to create a view and call it from that view.
Create Function [dbo].[AlterPhone](#Phone Varchar(15))
Returns varchar (15)
AS
BEGIN
declare #Counter int
declare #NewNumber varchar(15)
set #NewNumber = 0
select #NewNumber = case when len(#Phone)=8 and isnumeric(#Phone) = 1
then (select RandValue from dbo.vw_RandomVarchar) else 'Bad Number' end
return #NewNumber
END
/*
CREATE VIEW [dbo].[vw_RandomVarchar]
AS
SELECT cast(cast(rand()*100000000 as int)as varchar) AS RandValue
END
SELECT dbo.AlterPhone(12345678)
*/