Mainting Identity value across multiple tables - sql-server

We have a situation where we have a column called Customer_Number in multiple tables. This column is identity column in all the tables, but is there a way that I can make this column unique among all the tables.
for example if I add a row in table_one and identity column assigns it value 1 now if someone add another row in Customer_Number column of table_two , table_three or table_four it should be assigned 2.
how can I do this, I have been reading online and it seems I have to create a table to keep record of the last generated value for any of the table and get MAX() of values from that table and add 1 to it to get the next available value, is there a simpler way of doing this?

I have not used it myself but I think you need the new Sequence Object
You would Create a Sequence Object and rather then using Identity values just get the next value from your sequence object.
Create Sequence Object
CREATE SEQUENCE Sqnc_Number_Generator AS INT --<-- This can be Bigint as well
START WITH 1 -- Start with value 1
INCREMENT BY 1 -- Increment with value 1
MINVALUE 1 -- Minimum value to start is 1
MAXVALUE 50000 -- Maximum it can go to 5000
NO CYCLE -- Do not go above 5000
CACHE 500 -- Increment 500 values in memory rather than incrementing from IO
Getting Next value
SELECT NEXT VALUE FOR dbo.Sqnc_Number_Generator AS NxtValue;
SQL FIDDLE

Related

SQL create new unique random ID

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.

How can this expression reach the NULL expression?

I'm trying to randomly populate a column with values from another table using this statement:
UPDATE dbo.SERVICE_TICKET
SET Vehicle_Type = (SELECT TOP 1 [text]
FROM dbo.vehicle_typ
WHERE id = abs(checksum(NewID()))%21)
It seems to work fine, however the value NULL is inserted into the column. How can I get rid of the NULL and only insert the values from the table?
This can happen when you don't have an appropriate index on the ID column of your vehicle_typ table. Here's a smaller query that exhibits the same problem:
create table T (ID int null)
insert into T(ID) values (0),(1),(2),(3)
select top 1 * from T where ID = abs(checksum(NewID()))%3
Because there's no index on T, what happens is that SQL Server performs a table scan and then, for each row, attempts to satisfy the where clause. Which means that, for each row it evaluates abs(checksum(NewID()))%3 anew. You'll only get a result if, by chance, that expression produces, say, 1 when it's evaluated for the row with ID 1.
If possible (I don't know your table structure) I would first populate a column in SERVICE_TICKET with a random number between 0 and 20 and then perform this update using the already generated number. Otherwise, with the current query structure, you're always relying on SQL Server being clever enough to only evaluate abs(checksum(NewID()))%21once for each outer row, which it may not always do (as you've already found out).
#Damien_The_Unbeliever explained why your query fails.
My first variant was not correct, because I didn't understand the problem in full.
You want to set each row in SERVICE_TICKET to a different random value from vehicle_typ.
To fix it simply order by random number, rather than comparing a random number with ID. Like this (and you don't care how many rows are in vehicle_typ as long as there is at least one row there).
WITH
CTE
AS
(
SELECT
dbo.SERVICE_TICKET.Vehicle_Type
CA.[text]
FROM
dbo.SERVICE_TICKET
CROSS APPLY
(
SELECT TOP 1 [text]
FROM dbo.vehicle_typ
ORDER BY NewID()
) AS CA
)
UPDATE CTE
SET Vehicle_Type = [text];
At first we make a Common Table Expression, you can think of it as a temporary table. For each row in SERVICE_TICKET we pick one random row from vehicle_typ using CROSS APPLY. Then we UPDATE the original table with chosen rows.

TSQL: getting next available ID

Using SQL Server 2008, have three tables, table a, table b and table c.
All have an ID column, but for table a and b the ID column is an identity integer, for table c the ID column is a varchar type
Currently a stored procedure take a name param, following certain logic, insert to table a or table b, get the identity, prefix with 'A' or 'B' then insert to table c.
Problem is, table C ID column potentially have the duplicated values, i.e. if identity from table A is 2, there might already have 'A2','A3','A5' in the ID column for table C, how to write a T-SQL query to identify the next available value in table C then ensure to update table A/B accordingly?
[Update]
this is the current step,
1. depends on input parameter, insert to table A or table B
2. initialize seed value = ##Identity
3. calculate ID value to insert to table C by prefix 'A' or append 'B' with the seed value
4. look for record match in table C by ID value from step 3, if didn't find any record, insert it, else increase seed value by 1 then repeat step 3
The issue being at a certain value range, there could be a huge block of value exists in table C ID, i.e. A3000 to A500000 existed now in table C ID, the database query is extemely slow if follow the existing logic. Needs to figure out a logic to smartly get the minimum available number (without the prefix)
it is hard to describe, hope this make more sense, I truly appreciate any help on this Thanks in advance!
This should do the trick. Simple self extracting example will work in SSMS. I even made it out of order just in case. You would just change your table to be where #Data is and then change Identifier field to replace 'ID'.
declare #Data Table ( Id varchar(3) );
insert into #Data values ('A5'),('A2'),('B1'),('A3'),('B2'),('A4'),('A1'),('A6');
With a as
(
Select
ID
, cast(right(Id, len(Id)-1) as int) as Pos
, left(Id, 1) as TableFrom
from #Data
)
select
TableFrom
, max(Pos) + 1 as NextNumberUp
from a
group by TableFrom
EDIT: If you want to not worry about production data you could add this last part amending what I wrote:
Select
TableFrom
, max(Pos) as LastPos
into #Temp
from a
group by TableFrom
select TableFrom, LastPos + 1
from #Temp
Regardless if this was production environment you are going to have to hit part of it at some time to get data. If the datasets are not too large and just varchar(256) or less and only 5 million rows or less you could dump that entire column from tableC to a temp table. Honestly query performance versus imports change vastly from system to system.
Following your design there shouldn't be any duplicates in Table C considering that A and B are unique.
A | B | C
1 1 A1
2 2 A2
B1
B2

how to increment a sequence number while triggering data from one table to another

I want to write a trigger to transfer some columns of all inserted rows in a table to another table while incrementing the maximum number in a sequence number field in the destination table. this field is not auto increment but is a primary key field.
What I used to do was find the max sequence no in destination table, increment and then insert the new value. This worked fine if data is inserted row at a time. But when many rows are inserted from a single query, how can I increment the sequence number? Sample problem follows:
insert into [mssql].mssql.dbo.destination_table (name,seq_no)
select name,?
from inserted
even few thousand rows can be inserted at once.
seq_no is part of a composite primary key. So for example if data is inserted under different name seq_no will be different. (This requirement should be considered when I can increment the seq_no without considering its part in the primary key)
Okay, I got your problem, try this
insert into [mssql].mssql.dbo.destination_table (name,seq_no)
select name, x.MaxSeq + row_number() over (order by name)
from inserted, (select Max(seq_no) As MaxSeq From source_table) x

Best way to get totally sequential int values in SQL Server

I have a business requirement that the InvoiceNumber field in my Invoices table be totally sequential - no gaps or the auditors might think our accountants are up to something fishy!
My first thought was to simply use the primary key (identity) but if a transaction is rolled back a gap appears in the sequence.
So my second thought is to use a trigger which, at the point of insert, looks for the highest InvoiceNumber value in the table, adds 1 to it, and uses it as the InvoiceNumber for the new row. Easy to implement.
Are there potential issues with near-simultaneous inserts? For example, might two near simultaneous inserts running the trigger at the same time get the same 'currently highest InvoiceNumber' value and therefore insert rows with the same InvoiceNumber?
Are there other issues I might be missing? Would another approach be better?
Create a table which keeps tracks of 'counters'.
For your invoices, you can add some record to that table which keeps track of the next integer that must be used.
When creating an invoice, you should use that value, and increase it. When your transaction is rolled back, the update to that counter will be rollbacked as well. (Make sure that you put a lock on that table, to be sure that no other process can use the same value).
This is much more reliable than looking at the highest current counter that is being used in your invoice table.
You may still get gaps if data gets deleted from the table. But if data only goes in and not out, then with proper use of transactions on an external sequence table, it should be possible to do this nicely. Don't use MAX()+1 because it can have timing issues, or you may have to lock more of the table (page/table) than required.
Have a sequential table that has only one single record and column. Retrieve numbers from the table atomically, wrapping the retrieval and usage in a single transaction.
begin tran
declare #next int
update seqn_for_invoice set #next=next=next+1
insert invoice (invoicenumber,...) value (#next, ....)
commit
The UPDATE statement is atomic and cannot be interrupted, and the double assignment make the value of #next atomic. It is equivalent to using an OUTPUT clause in SQL Server 2005+ to return the updated value. If you need a range of numbers in one go, it is easier to use the PRE-update value rather than the POST-update value, i.e.
begin tran
declare #next int
update seqn_for_invoice set #next=next, next=next+3 -- 3 in one go
insert invoice (invoicenumber,...) value (#next, ....)
insert invoice (invoicenumber,...) value (#next+1, ....)
insert invoice (invoicenumber,...) value (#next+2, ....)
commit
Reference for SQL Server UPDATE statement
SET #variable = column = expression sets the variable to the same value as the column. This differs from SET #variable = column, column = expression, which sets the variable to the pre-update value of the column.
CREATE TABLE dbo.Sequence(
val int
)
Insert a row with an initial seed. Then to allocate a range of sufficient size for your insert (call it in the same transaction obviously)
CREATE PROC dbo.GetSequence
#val AS int OUTPUT,
#n as int =1
AS
UPDATE dbo.Sequence
SET #val = val = val + #n;
SET #val = #val - #n + 1;
This will block other concurrent attempts to increment the sequence until the first transaction commits.

Resources