SQL create new unique random ID - sql-server

I'm creating a Inventory application which uses an SQL database to keep track of products.
The ProductNumber is in the format yyyy-xxxx (i.e. 8024-1234), where the first 4 digits describe a category and the last 4 digits describe the an increasing integer, together creating the productnumber.
When creating a new product, the category should first be approved by an administrator, and therefor all new products will be added as 9999-xxxx. Then later, when the product is approved in the category, it's product number will change to the correct ProductNumber.
What I need for this is when creating a new product, to generate a random number for the last 4 digits, and then check if they don't exist already in the database (together with the first 4 digits). So, when creating a new product, some SQL query should create for example 9999-0123 and then double check if this one doesn't exist already.
How could one achieve this?
Thanks in advance!

you didn't precise the SGBD you are using, but here is a potential solution using Oracle PL/SQL:
declare
temp varchar2(10);
any_rows_found number;
row_exist boolean := true;
begin
WHILE row_exist = true
LOOP
temp := '9999-' || ceil(DBMS_RANDOM.value(low => 999, high => 9999));
select count(*) into any_rows_found from my_table where my_column = temp;
if any_rows_found = 1 then
else
row_exist := false;
insert into my_table values (..................., temp);
end if;
end loop;
end;
we use DBMS_RANDOM to generate the random value , concatenate it to 9999- and then check if it exists we loop to generate another value, if it doesn't exist we insert the value.
regards

You can generate your product number with a sequence, if you'd like an incremental number:
CREATE SEQUENCE product_number
START WITH 1000
INCREMENT BY 1
NOCACHE
NOCYCLE;
Whenever you insert or update a new product and need a valid number just call (sequence.nextVal). Then in your product table set (year, product_number) as a primary key (or the product number itself). If you can't set the primary key as said and want to check if the item already exist with the serial number you can generate the sequence number using:
SELECT sequence.nextVal FROM DUAL;
Then check if the product with the generated number exists.
Didn't know what dialect of SQL you are using, this is Oracle SQL but it can appliead in other dialects too.

Also not sure about the target DB - but worked it out for MS-SQL.
In the first step I would not reccommend the approach of generating a random number first and then check if this one exist and potentially doing this over and over again.
Instead you could go by and get the current max productnumber and work from there on. Even with a varchar you will retrieve the max int - since your syntax is always - (c = category / p = product). In addition to that you will get your desired value straight away since the target category is "9999".
You could work with something like this:
DECLARE #newID int;
-- REPLACE to remove the hyphen so we are facing an actual integer
-- Cast to be able to calculate with the value i.E. adding 1 on top of it
-- MAX for retrieving the max value
SELECT #newID = MAX(CAST(replace(ProductNumber,'-','') as int)) + 1 from Test
-- Set the ID by default to 99990000 in case there are no values with the 9999-prefix
IF #newID < 99990000
BEGIN
SET #newID = 99990000
END
-- Push back the hyphen into the new ID given you the final new productNumber
-- 5 is the starting index
-- 0 since no chars from the original ID shall be removed
Select STUFF(#newID, 5,0,'-')
So in case you currently have a product with 9999-1423 as your product with the highest number this would return "9999-1424".
If there are no products with the prefix of "9999" you would simply get "9999-0000".

The ProductNumber is in the format yyyy-xxxx (i.e. 8024-1234), where the first 4 digits describe a category and the last 4 digits describe the an increasing integer, together creating the productnumber.
We will implement this with a calculated column with puts together the category and the product number which will be in their own individual fields.
When creating a new product, the category should first be approved by an administrator, and therefor all new products will be added as 9999-xxxx. Then later, when the product is approved in the category, it's product number will change to the correct ProductNumber.
Put simply, by default every new product is automatically assigned product category 9999
What I need for this is when creating a new product, to generate a random number for the last 4 digits, and then check if they don't exist already in the database (together with the first 4 digits). So, when creating a new product, some SQL query should create for example 9999-0123 and then double check if this one doesn't exist already.
This can be implemented as an identity. This is not random, but I assume that is not really a requirement right?
Keep in mind there are many holes in these requirements.
If your product number changes from 9999-1234 to 8024-1234 but, has already appeared on reports / documents as 9999-1234, that's a problem
This format only supports at most 1,000 products. Then your system breaks
Again, does the number really need to be random?
I won't go into the actual mechanism for approval and assignment, you'll need to ask that in another question once this one is solved.
ProductNumber is in fact not a number, it's a code, so I don't agree with that column name
On to the code.
Create a table by running this:
CREATE TABLE dbo.Products
(
ProductID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
ProductName VARCHAR(100),
ProductCategoryID INT NOT NULL DEFAULT (9999),
ProductNumber AS (FORMAT(ProductCategoryID,'0000') + '-' + FORMAT(ProductID,'0000'))
)
Some explanation of the columns:
ProductID will autogenerate an incrementing number, starting at 1, incrementing by 1 each time. It's guaranteed to be unique. It's also defined as the primary key
ProductCategoryID will default to 9999 if you don't specify anything for it
ProductNumber is the special value you were after calculated from two individual columns
Now create a new product and see what happens
INSERT INTO dbo.Products(ProductName)
VALUES ('Brown Shoes')
SELECT * FROM dbo.Products
You can see Product Number 9999-0001
Add some more and note that the product code increments. It is not random. Carefully consider if you actually really need this to be random.
Now set the actual product category:
UPDATE dbo.Products
SET ProductCategoryID = 7 WHERE ProductID = 1
SELECT * FROM dbo.Products
and note that the product number updates.
Important to note that the real product id is actually just ProductID. The ProductCode column is just something to satisfy your requirements.

Related

Filtering SQL rows based on certain alphabets combination

I have a column that store user input text field from a frontend website. User can input any kind of text in it, but they will also put in a specific alphabets combination to represent a job type - for example 'dri'. As an example:
Row 1: P49384; Open vehicle bonnet-BO-dri 22/10
Row 2: P93818; Vehicle exhaust-BO 10/20
Row 3: P1933; battery dri-pu-103/2
Row 4: P3193; screwdriver-pu 423
Row 5: X939; seats bo
Row 6: P9381-vehicle-pu-bo dri
In this case, I will like to filter only rows that contain dri. From the example, you can see the text can be in any order (user behaviour, they will key whatever they like without following any kind of format). But the constant is that for a particular job type, they will put in dri.
I know that I can simply use LIKE in SQL Server to get these rows. Unfortunately, row 4 is included inside when I use this operator. This is because screwdriver contains dri.
Is there any way in SQL Server I can do to strictly only obtain rows that has dri job type, while excluding words like screwdriver?
I tried to use PATINDEX but it failed too - PATINDEX('%[d][r][i]%', column) > 0
Thanks in advance.
Your data is the problem here. Unfortunately even for denormalised data it doesn't appear to have a reliable/defined format, making parsing your data in a language like T-SQL next to impossible. What problems are there? Based on the original sample data, at a glance the following problems exist:
The first data value's delimiter isn't consistent. Rows 1-5 use a semicolon (;), but row 6 uses a hyphen (-)
The last data value's delimiter isn't consistent. Row 1, 2 & 4 use a space ( ), but row 3 uses a hyphen (-).
Internal data doesn't use a consistent delimiter. For example:
Row 1 has a the value Open vehicle bonnet-BO-dri, which appears to be the values Open vehicle bonnet, BO and dri; so the hyphen(-) is the delimiter.
Row 5 has seats bo, which appears to be the values seats and bo, so uses a space ( ) as a delimiter.
The fact that row 6 has vehicle as its own value (vehicle-pu-bo-dri), however, implies that Open vehicle bonnet and Vehicle Exhaust (on rows 1 and 2 respectively) could actually be the values Open, vehicle, & bonnet and Vehicle & Exhaust respectively.
Honestly, the solution is to fix your design. As such, your tables should likely look something like this:
CREATE TABLE dbo.Job (JobID varchar(6) CONSTRAINT PK_JobID PRIMARY KEY NONCLUSTERED, --NONCLUSTERED Because it's not always ascending
YourNumericalLikeValue varchar(5) NULL); --Obviously use a better name
CREATE TABLE dbo.JobTypeCompleted(JobTypeID int IDENTITY (1,1) CONSTRAINT PK_JobTypeID PRIMARY KEY CLUSTERED,
JobID varchar(6) NOT NULL CONSTRAINT FK_JobType_Job FOREIGN KEY REFERENCES dbo.Job (JobID),
JobType varchar(30) NOT NULL); --Must likely this'll actually be a foreign key to an actual job type table
GO
Then, for a couple of your rows, the data would be inserted like so:
INSERT INTO dbo.Job (JobID, YourNumericalLikeValue)
VALUES('P49384','22/10'),
('P9381',NULL);
GO
INSERT INTO dbo.JobTypeCompleted(JobID,JobType)
VALUES('P49384','Open vehicle bonnet'),
('P49384','BO'),
('P49384','dri'),
('P9381','vehicle'),
('P9381','pu'),
('P9381','bo'),
('P9381','dri');
Then you can easily get the jobs you want with a simple query:
SELECT J.JobID,
J.YourNumericalLikeValue
FROM dbo.Job J
WHERE EXISTS (SELECT 1
FROM dbo.JobTypeCompleted JTC
WHERE JTC.JobID = J.JobID
AND JTC.JobType = 'dri');
You can apply like operator in your query as column_name like '%-dri'. It means find out records that end with "-dri"

Trigger selecting child records, multiplying their values and updating parent record

I am a PL/SQL newbie and I'm struggling with a trigger.
Description:
I have three objects - PRODUCT, CONTAINS, ORDER. One product can have many CONTAINS and one ORDER can have many CONTAINS (basically it used to be Many-to-many relationship between PRODUCT and ORDER).
Each Product has a column "value", each CONTAINS has a column "amount" and each ORDER has a column "total".
When I add a new PRODUCT to ORDER via creating new CONTAINS, I want to recalculate field "total" on ORDER.
Example: PRODUCT X has "value" of 100. PRODUCT Y has "value" of 200. We have an ORDER O. Now I create CONTAINS between Product X and ORDER O with column "amount" of 5. Now the trigger should multiply 5 * 100 and update the ORDER column "total" to 500. Then I create CONTAINS between PRODUCT Y and ORDER O with column "amount" of 10. Now the trigger should recalculate 5 * 100 + 10 * 200 and update the "total" column on ORDER O to 2500.
My faulty trigger:
create or replace TRIGGER TRIGGER1
AFTER DELETE OR INSERT OR UPDATE OF AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER ON CONTAINS
REFERENCING NEW AS n
FOR EACH ROW
DECLARE
value number;
amount number;
total number;
BEGIN
LOOP
FOR emp IN (SELECT AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER FROM CONTAINS WHERE ORDER_ID_ORDER = :n.ORDER_ID_ORDER)
LOOP
(SELECT SUM(VALUE) into product FROM PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT);
amount:= emp.AMOUNT;
total:= total + (product * amount);
UPDATE ORDER SET ORDER.TOTAL = total WHERE ID_ORDER = :n.ORDER_ID_ORDER;
END LOOP;
END LOOP;
END;
EDIT: The error shows on here:
(SELECT SUM(VALUE) into product FROM PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT)
saying I can't use "emp".
EDIT2: Error message:
10/2 PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe <an alternat
10/89 PLS-00103: Encountered the symbol ")" when expecting one of the following: . ( * # % & - + ; / at for mod remainder rem <an exponent (**)> and or group having intersect minus order start union where connect || indicator multiset
15/5 PLS-00103: Encountered the symbol "LOOP" when expecting one of the following: ;
Simplified the trigger by removing loops/cursors that isn't actually required.
create or replace TRIGGER TRIGGER1
AFTER DELETE OR INSERT OR UPDATE OF AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER
ON CONTAINS
REFERENCING NEW AS n
FOR EACH ROW
DECLARE
lv_total number;
BEGIN
SELECT SUM(prdt.VALUE * :n.amount) into lv_total
FROM PRODUCT prdt where prdt.ID_PRODUCT = :n.PRODUCT_ID_PRODUCT;
UPDATE ORDERs SET TOTAL = lv_total WHERE ID_ORDER = :n.ORDER_ID_ORDER;
END;
Refer DB Fiddle link for solution :https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3be867f6ab2e93978ae45a7d305434a1
PS:Triggers can cause performance bottleneck at time when the DMLs in the triggers are not tuned well enough.Recommendation is to check the explain plan for SELECT,INSERT,UPDATE statements inside a trigger and tune them as desired.If Indexes are not available for CONTAINS.ORDER_ID_ORDER and PRODUCTS.ID_PRODUCT creating one would be beneficial but would recommend consulting with DBA in-charge.
UPDATE :
Now since you need to Select from the table on which trigger is fired we have to live with famous Mutating trigger error ORA-04091: table MYTABLE.CONTAINS is mutating, trigger/ and luckily Oracle has an easy solution for it using Compound trigger that was added from Oracle Database 11g Release1 version onwards.
For more details and technical explanation on Compound Trigger you may refer http://stevenfeuersteinonplsql.blogspot.com/2016/12/get-rid-of-mutating-table-trigger.html
Trigger Code goes like this, ta.da..
So we take rows to a pl/sql table for row operation and perform statement operation for each of the rows from the pl/sql table.
CREATE OR REPLACE TRIGGER trigger2
FOR UPDATE OR INSERT ON contains
COMPOUND TRIGGER
TYPE typ_contains IS TABLE OF contains%rowtype INDEX BY PLS_INTEGER;
tab_contains typ_contains;
AFTER EACH ROW IS
BEGIN
tab_contains (tab_contains.COUNT + 1).amount :=
:NEW.amount;
tab_contains (tab_contains.COUNT).product_id_product := :NEW.product_id_product;
tab_contains (tab_contains.COUNT).order_id_order := :NEW.order_id_order;
END AFTER EACH ROW;
AFTER STATEMENT IS
lv_total number;
BEGIN
FOR indx IN 1 .. tab_contains.COUNT
LOOP
SELECT SUM(prdt.VALUE * tab_contains(indx).amount) into lv_total
FROM PRODUCT prdt,contains cnts
where cnts.order_id_order = tab_contains(indx).order_id_order
and prdt.id_product = cnts.product_id_product;
UPDATE ORDERs SET TOTAL = lv_total
WHERE ID_ORDER = tab_contains(indx).ORDER_ID_ORDER;
END LOOP;
END AFTER STATEMENT;
END trigger2;
/
Updated solution can be found in DBfiddle link https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=1fb40eef7cf3a647bc5560ed19490240
You have several issues, but the most fundamental one is that you should not be doing this at all. Trying to store - and keep in synch - a value that can always be calculated is a fundamental design flaw.
Now, to the code itself.
You have
SELECT SUM(VALUE) into product
The target of your INTO must be a declared variable. Looks like you are trying to SELECT .. INTO a column name.
You should name local variables to distinguish between them an column names. Thus, instead of
DECLARE
value number;
amount number;
total number;
You should have
DECLARE
v_value number;
v_amount number;
v_total number;
Conversely, you should think about standard naming conventions for your tables and columns. For columns I use and recommend names in the form of <adjective_noun>, thus ORDER_ID, PRODUCT_NAME, etc. What's this with PRODUCT_ID_PRODUCT, ORDER_ID_ORDER ? Repeating the table name in the column names is usually not beneficial. Though there are times it makes sense because it still follows the adjective_noun format, like the id column of the ORDERS table being named ORDER_ID. Think about table names also, I usually make my table names a plural noun because tables track multiple instances of some entity. If the table name makes sense for a column name (like ORDER_ID) it would be singular, because an individual row tracks a single instance of the entity.
Lastly, it is hard to recommend coding modification without knowing the tables. You've given a vague description of them, but better to lay everything on the table. See minimal-reproducible-example
I think there is a mistake:
SELECT SUM(VALUE) into product FROM PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT
Instead of :emp. you should use :n.
--- UPDATE
Remove brackets in this SQL statement. You don't need them
Add variable PRODUCT
Change the name of variable TOTAL eg nTotal (it mismatch with column name)

SQL Server Insert Random

i have this table:
Create Table Person
(
Consecutive Integer Identity(1,1),
Identification Varchar(15) Primary Key,
)
The Identification column can contain letters, numbers, and is optional, i.e., the customer can enter it or not, if not, creates a number automatic.. how can i do to insert a random number that does not exist before?, preferably a lower number.
A example could be:
Select Random From Person Where Random Not Exists In Identification
This is my code:
Select Min(Convert(Integer,Identification)) - 1
From Person
Where IsNumeric(Identification) = 1
Or
Select Max(Convert(Integer,Identification)) + 1
From Person
Where IsNumeric(Identification) = 1
Works well, but if the customer enter a number high, for example 1000, or higher, then the number will begin from there could have an overflow error
But if there is not a number below Identification and greater than 0 then well be -1, -2, -3.. etc.
Thanks in advance..
I agree with what M.Ali said. But you can just make use of the below code, but still I don't recommend beyond what M.Ali said.
The loop with continue until a random number is generated which is not in your table. You can change the precision to 5 digits by changing 1000 to 10000 and so on.
DECLARE #I INT = 0
DECLARE #RANDOM INT;
WHILE(#I=0)
BEGIN
SELECT #RANDOM = 1000 + (CONVERT(INT, CRYPT_GEN_RANDOM(3)) % 1000);
IF NOT EXISTS(SELECT Identification FROM YOURTABLE WHERE Identification = CAST(#RANDOM AS VARCHAR(4)))
BEGIN
-- Do your stuff here
BREAK;
END
ELSE
BEGIN
-- The ELSE part
END
END
Maintaining a Random number of VARCHAR(15), which depends on end user's input can be a very expensive approach when you also want it to be unique.
Imagine a scenario when you have some decent amount of rows say 10,000 rows in this table and a user comes in trying to insert a Random number, chances are the user maybe try 5, 10 or even maybe 15 times to get a unique random value.
On each failed attempt a call will be made to server, a search will be done on table (more rows more expensive this query will become), and the more (failed) attempts a user makes more disappointment/poor application experience user will have.
Would you ever go back to an application(web/windwos) where just for registeration you had struggle this many time? obviously not.
The moral of the story is if you are asking a user to enter some random value, do not expect users to maintain your database integrity and keep that column unique, take control and pair that value with another column which will definately be random. In your case it can be the Identity column. Or alternately you can generate that value for user yourself, using guid.
select count(*) +1 from Person
This generates a logical ID for Identification that sets the ID to what it 'would have been' with a simple incrementor.
However, you then cannot delete records; instead you must deactivate them, or clear the row.
Alternately, have a separate (hidden) column that only auto-increments, and if Identification is left empty, use the value from the hidden column. Same result, but less risk if deletion is relevant.

Hierarchy in SQL

We have a sql database at work with a table, employees that has a column, report_to, which contains the username of the person that that employee reports to. What we want to do is change this representation to a numerical representation. For instance:
'a' reports to 'b' reports to 'c'. So the representation would be something like 'a' = 49, 'b' = 50, 'c' = 51. if 'd' becomes 'c''s boss, then 'd' = 52. If 'a' becomes the supervisor of interns 'e' and 'f', then 'e' and 'f' both are equal to 48.
As shown, starting the numbers at a non zero number allows for expansion not only upwards but also down the hierarchical chain.
The main question is, how do I convert from the current structure ("report_to"), to a numerical representation?
NOTE: this is in MSSQL
You can add a new column (rank) that should be 0.
Then the first step is to find the BIG BOSS - this should be the user who doesn't have a boss - report_to is null. His rank will be 1.
The second step is to find his first directs. They will rank as 2. Something like:
UPDATE TABLE SET RANK = 2
WHERE report_to IN
(SELECT username FROM TABLE WHERE RANK = 1)
The third step is to find directs's directs. Something like:
UPDATE TABLE SET RANK = 3
WHERE report_to IN
(SELECT username FROM TABLE WHERE RANK = 2)
The next steps are identical with step 2 and 3, until no RANK = 0 is found.
All these steps can be done in a procedure, within a WHILE statement.
In the end, if you would like to start the ranking from 50 instead on 1, then you can make an update:
UPDATE TABLE SET RANK = 50 - RANK
or to be sure you don't miss anything:
UPDATE TABLE SET RANK = (SELECT MAX(RANK) FROM TABLE) + 1 - RANK
If you have a field that contains the supervisor of the employee in the table, you can use a recusive CTE to get the hierarchy. Looks that up in Books ONline and get back to us if you have any qquestions.
wow... so do you have a users table? if not, then suggestion 1 is to create one.
users_table
------------
username
user_id
name_first
name_last
other_stuff_?
then populate that with all the existing usernames - possibly by querying the table you are describing for unique names. the user_id will be populated as a sequenced id value during this step.
then you can add a new table,
user_user
-----------
user_id_1
user_id_2
relationship
begin_dt
end_dt
then you can populate this new table with each user to user relationship and when it was valid. e.g. user 48 was related to user 50 beginning on someday with relationship = 'Manages'
the relationship should be probably a fk to yet another table... but i leave that to you as an excercise.
In my opinion, you don't need to use numeric counters, just use positions, because you can't have unlimited position, its gonna stop somewhere. Each username should have a position, like intern, supervisor, employer, project manager or whatever. When you change lets say interns position higher then supervisor's it becomes employer, or something similar. You get the idea. :)

Creating a unique id (PIN) for each record of a table

I want to create a PIN that is unique within a table but not incremental to make it harder for people to guess.
Ideally I'd like to be able to create this within SQL Server but I can do it via ASP.Net if needed.
EDIT
Sorry if I wasn't clear: I'm not looking for a GUID as all I need is a unique id for that table; I just don't want it to be incremental.
Add a uniqueidentifier column to your table, with a default value of NEWID(). This will ensure that each column gets a new unique identifier, which is not incremental.
CREATE TABLE MyTable (
...
PIN uniqueidentifier NOT NULL DEFAULT newid()
...
)
The uniqueidentifier is guaranteed to be unique, not just for this table, but for all tables.
If it's too large for your application, you can derive a smaller PIN from this number, you can do this like:
SELECT RIGHT(REPLACE((SELECT PIN from MyTable WHERE UserID=...), '-', ''), 4/*PinLength*/)
Note that the returned smaller PIN is not guaranteed to be unique for all users, but may be more manageable, depending upon your application.
EDIT: If you want a small PIN, with guaranteed uniqueness, the tricky part is that you need to know at least the maximum number of users, in order to choose the appropriate size of the pin. As the number of users increases, the chances of a PIN collision increases. This is similar to the Coupon Collector's problem, and approaches n log n complexity, which will cause very slow inserts (insert time proportional to the number of existing elements, so inserting M items then becomes O(N^2)). The simplest way to avoid this is to use a large unique ID, and select only a portion of that for your PIN, assuming that you can forgo uniqueness of PIN values.
EDIT2:
If you have a table definition like this
CREATE TABLE YourTable (
[id] [int] IDENTITY(1,1) NOT NULL,
[pin] AS (CONVERT(varchar(9),id,0)+RIGHT(pinseed,3)) PERSISTED,
[pinseed] [uniqueidentifier] NOT NULL
)
This will create the pin from the pinseed a unique ID and the row id. (RAND does not work - since SQL server will use the same value to initialize multiple rows, this is not the case with NEWID())
Just so that it is said, I advise that you do not consider this in any way secure. You should consider it always possible that another user could guess someone else's PIN, unless you somehow limit the number of allowed guesses (e.g. stop accepting requests after 3 attempts, similar to a bank witholding your card after 3 incorrect PIN entries.)
What you want is a GUID
http://en.wikipedia.org/wiki/Globally_unique_identifier
Most languages have some sort of API for generating this... a google search will help ;)
How about a UNIQUEIDENTIFIER type column with a default value of NEWID()?
That will generate a new GUID for each row.
Please have in mind that by requiring an unique PIN (which is uncommon) you will be limiting the max number of allowed users to the the PIN specification. Are you sure you want this ?
A not very elegant solution but which works is to use an UNIQUE field, and then loop attempting to insert a random generated PIN until the insert is successful.
You can use the following to generate a BIGINT, or other datatype.
SELECT CAST(ABS(CHECKSUM(NEWID()))%2000000000+1 as BIGINT) as [PIN]
This creates a number between 1 and 2 billion. You will simulate some level of randomness since it's derived from the NEWID function. You can also format the result as you wish.
This doesn't guarantee uniqueness. I suggest that you use a unique constraint on the PIN column. And, your code that creates the new PIN should check that the new value is unique before it assigns the value.
Use a random number.
SET #uid = ROUND(RAND() * 100000)
The more sparse your values are in the table, the better this works. If the number of assigned values gets large is relationship to the number of available values, it does not work as well.
Once the number is generated you have a couple of options.
1) INSERT the value inside of a retry loop. If you get a dupe error, regenerate the value (or try the value +/-1) and try again.
2) Generate the value and look for the MAX and MIN existing unique identifiers.
DECLARE
#uid INTEGER
SET #uid = ROUND(RAND() * 10000, 1)
SELECT #uid
SELECT MAX(uid) FROM table1 WHERE uid < #uid
SELECT MIN(uid) FROM table1 WHERE uid > #uid
The MIN and MAX value give you a range of available values to work from if the random value is already assigned.

Resources