Auto Increment Values in Oracle Column - database

I have a table in my oracle DB which has a column with random values. Screenshot is attached below :
I had manually updated the first row to "V0001". Is there any way I can update the rest of the rows to "V0002", "V0003" and so on without manual intervention.

You could use a sequence for this. Create a sequence, convert the sequence's .NEXTVAL to a string, use CONCAT() and UPDATE eg:
Table
create table demo
as
select dbms_random.string( 'x', 11 ) as vehicleid
from dual
connect by level <= 100 ;
select * from demo fetch first 10 rows only ;
-- output
VEHICLEID
LS23XFRNH5N
47DUDNOIRO9
POS5GQSQLMO
BBEEZJMQZI4
2Q8QE30HM2E
S7M5V40YNTD
N2X1YN0OIE3
...
Sequence
create sequence vehicleid_seq start with 1 increment by 1 ;
Update
update demo
set vehicleid = concat( 'V', to_char( vehicleid_seq.nextval, 'FM00000' ) ) ;
Result
select * from demo order by vehicleid fetch first 10 rows only ;
VEHICLEID
V00001
V00002
V00003
V00004
V00005
V00006
V00007
V00008
V00009
V00010
dbfiddle

The identifier code of a table is recommended to be a numeric data, what you could do is an extra field that works as a second code, perhaps called secondary_code. You can do it with Stored Procedure, I give you a small example:
DELIMITER$$
DROP PROCEDURE IF EXISTS sp_genwrar_code$$
CREATE PROCEDURE sp_genwrar_code(
OUT p_secondary_code VARCHAR(4)
)
BEGIN
DECLARE accountant INT;
BEGIN
SET accountant = (SELECT COUNT(*)+1 FROM product);
IF(accountant <10)THEN
SET p_secondary_code= CONCAT('V00',accountant );
ELSE IF(accountant<100) THEN
SET p_secondary_code= CONCAT('V0',accountant);
ELSE IF(accountant<1000)THEN
SET p_secondary_code= CONCAT('V',accountant );
END IF;
END IF;
END IF;
END;
END$$
With that you can generate codes as you need with the structure 'V001'

Related

How to Add a Set of Keys (UniqueIDs) to a Temp table to later INSERT into Production Table

I have the data ready to Insert into my Production table however the ID column is NULL and that needs to be pre-populated with the IDs prior to Insert. I have these IDs in another Temp Table... all I want is to simply apply these IDs to the records in my Temp Table.
For example... Say I have 10 records all simply needing IDs. I have in another temp table exactly 10 IDs... they simply need to be applied to my 10 records in my 'Ready to INSERT' Temp Table.
I worked in Oracle for about 9 years and I would have done this simply by looping over my 'Collection' using a FORALL Loop... basically I would simply loop over my 'Ready to INSERT' temp table and for each row apply the ID from my other 'Collection'... in SQL Server I'm working with Temp Tables NOT Collections and well... there's no FORALL Loop or really any fancy loops in SQL Server other than WHILE.
My goal is to know the appropriate method to accomplish this in SQL Server. I have learned that in the SQL Server world so many of the DML operations are all SET Based whereas when I worked in oracle we handled data via arrays/collections and using CURSORS or LOOPs we would simply iterate thru the data. I've seen in the SQL Server world using CURSORS and/or iterating thru data record by record is frowned upon.
Help me get my head out of the 'Oracle' space I was in for so long and into the 'SQL Server' space I need to be in. This has been a slight struggle.
The code below is how I've currently implemented this however it just seems convoluted.
SET NOCOUNT ON;
DECLARE #KeyValueNewMAX INT,
#KeyValueINuse INT,
#ClientID INT,
#Count INT;
DROP TABLE IF EXISTS #InterOtherSourceData;
DROP TABLE IF EXISTS #InterOtherActual;
DROP TABLE IF EXISTS #InterOtherIDs;
CREATE TABLE #InterOtherSourceData -- Data stored here for DML until data is ready for INSERT
(
UniqueID INT IDENTITY( 1, 1 ),
NewIntOtherID INT,
ClientID INT
);
CREATE TABLE #InterOtherActual -- Prod Table where the data will be INSERTED Into
(
IntOtherID INT,
ClientID INT
);
CREATE TABLE #InterOtherIDs -- Store IDs needing to be applied to Data
(
UniqueID INT IDENTITY( 1, 1 ),
NewIntOtherID INT
);
BEGIN
/* TEST Create Fake Data and store it in temp table */
WITH fakeIntOtherRecs AS
(
SELECT 1001 AS ClientID, 'Jake' AS fName, 'Jilly' AS lName UNION ALL
SELECT 2002 AS ClientID, 'Jason' AS fName, 'Bateman' AS lName UNION ALL
SELECT 3003 AS ClientID, 'Brain' AS fName, 'Man' AS lName
)
INSERT INTO #InterOtherSourceData (ClientID)
SELECT fc.ClientID--, fc.fName, fc.lName
FROM fakeIntOtherRecs fc
;
/* END TEST Prep Fake Data */
/* Obtain count so we know how many IDs we need to create */
SELECT #Count = COUNT(*) FROM #InterOtherSourceData;
PRINT 'Count: ' + CAST(#Count AS VARCHAR);
/* For testing set value OF KeyValuePre to the max key currently in use by Table */
SELECT #KeyValueINuse = 13;
/* Using the #Count let's obtain the new MAX ID... basically Existing_Key + SourceRecordCount = New_MaxKey */
SELECT #KeyValueNewMAX = #KeyValueINuse + #Count /* STORE new MAX ID in variable */
/* Print both keys for testing purposes to review */
PRINT 'KeyValue Current: ' + CAST(#KeyValueINuse AS VARCHAR) + ' KeyValue Max: ' + CAST(#KeyValueNewMAX AS VARCHAR);
/* Using recursive CTE generate a fake table containing all of the IDs we want to INSERT into Prod Table */
WITH CTE AS
(
SELECT (#KeyValueNewMAX - #Count) + 1 AS STARTMINID, #KeyValueNewMAX AS ENDMAXID UNION ALL
/* SELECT FROM CTE to create Recursion */
SELECT STARTMINID + 1 AS STARTMINID, ENDMAXID FROM CTE
WHERE (STARTMINID + 1) < (#KeyValueNewMAX + 1)
)
INSERT INTO #InterOtherIDs (NewIntOtherID)
SELECT c.STARTMINID AS NewIntOtherID
FROM CTE c
;
/* Apply New IDs : Using the IDENTITY fields on both Temp Tables I can JOIN the tables by the IDENTITY columns
| Is there a BETTER Way to do this?... like LOOP over each record rather than having to build up common IDs in both tables using IDENTITY columns?
*/
UPDATE #InterOtherSourceData SET NewIntOtherID = oi.NewIntOtherID
FROM #InterOtherIDs oi
JOIN #InterOtherSourceData o ON o.UniqueID = oi.UniqueID
;
/* View data that is ready for insert */
--SELECT *
--FROM #InterOtherSourceData
--;
/* INSERT DATA INTO PRODUCTION TABLE */
INSERT INTO #InterOtherActual (IntOtherID, ClientId)
SELECT NewIntOtherID, ClientID
FROM #InterOtherSourceData
;
SELECT * FROM #InterOtherActual;
END
To pre-generate key values in SQL Server use a sequence rather than an IDENTITY column.
eg
drop table if exists t
drop table if exists #t_stg
drop sequence t_seq
go
create sequence t_seq start with 1 increment by 1
create table t(id int primary key default (next value for t_seq),a int, b int)
create table #t_stg(id int, a int, b int)
insert into #t_stg(a,b) values (1,2),(3,3),(4,5)
update #t_stg set id = next value for t_seq
--select * from #t_stg
insert into t(id,a,b)
select * from #t_stg

How can I use a trigger to allow an incremented, user-assigned ID?

I am moving a small database from MS Access into SQL Server. Each year, the users would create a new Access database and have clean data, but this change will put data across the years into one pot. The users have relied on the autonumber value in Access as a reference for records. That is very inaccurate if, say, 238 records are removed.
So I am trying to accommodate them with an id column they can control (somewhat). They will not see the real primary key in the SQL table, but I want to give them an ID they can edit, but still be unique.
I've been working with this trigger, but it has taken much longer than I expected.
Everything SEEMS TO work fine, except I don't understand why I have the same data in my INSERTED table as the table the trigger is on. (See note in code.)
ALTER TRIGGER [dbo].[trg_tblAppData]
ON [dbo].[tblAppData]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #NewUserEnteredId int = 0;
DECLARE #RowIdForUpdate int = 0;
DECLARE #CurrentUserEnteredId int = 0;
DECLARE #LoopCount int = 0;
--*** Loop through all records to be updated because the values will be incremented.
WHILE (1 = 1)
BEGIN
SET #LoopCount = #LoopCount + 1;
IF (#LoopCount > (SELECT Count(*) FROM INSERTED))
BREAK;
SELECT TOP 1 #RowIdForUpdate = ID, #CurrentUserEnteredId = UserEnteredId FROM INSERTED WHERE ID > #RowIdForUpdate ORDER BY ID DESC;
IF (#RowIdForUpdate IS NULL)
BREAK;
-- WHY IS THERE A MATCH HERE? HAS THE RECORD ALREADY BEEN INSERTED?
IF EXISTS (SELECT UserEnteredId FROM tblAppData WHERE UserEnteredId = #CurrentUserEnteredId)
BEGIN
SET #NewUserEnteredId = (SELECT Max(t1.UserEnteredId) + 1 FROM tblAppData t1);
END
ELSE
SET #NewUserEnteredId = #CurrentUserEnteredId;
UPDATE tblAppData
SET UserEnteredId = #NewUserEnteredId
FROM tblAppData a
WHERE a.ID = #RowIdForUpdate
END
END
Here is what I want to accomplish:
When new record(s) are added, it should increment values from the Max existing
When a user overrides a value, it should check to see the existence of that value. If found restore the existing value, otherwise allow the change.
This trigger allows for multiple rows being added at a time.
It is great for this to be efficient for future use, but in reality, they will only add 1,000 records a year.
I wouldn't use a trigger to accomplish this.
Here is a script you can use to create a sequence (op didn't tag version), create the primary key, use the sequence as your special id, and put a constraint on the column.
create table dbo.test (
testid int identity(1,1) not null primary key clustered
, myid int null constraint UQ_ unique
, somevalue nvarchar(255) null
);
create sequence dbo.myid
as int
start with 1
increment by 1;
alter table dbo.test
add default next value for dbo.myid for myid;
insert into dbo.test (somevalue)
select 'this' union all
select 'that' union all
select 'and' union all
select 'this';
insert into dbo.test (myid, somevalue)
select 33, 'oops';
select *
from dbo.test
insert into dbo.test (somevalue)
select 'oh the fun';
select *
from dbo.test
--| This should error
insert into dbo.test (myid, somevalue)
select 3, 'This is NO fun';
Here is the result set:
testid myid somevalue
1 1 this
2 2 that
3 3 and
4 4 this
5 33 oops
6 5 oh the fun
And at the very end a test, which will error.

Check for duplicate in Stored Procedure, and if then change the value

I an working on a Stored Procedure, and I am using SQL.
I asked a question Insert zero between two IDs and make it length 10 in Stored Procedure. I got answer for that and now my solution works fine and this is my sp now.
ALTER PROCEDURE [dbo].[spInsertaItemPackingDetail] #ItemID INT
,#PackingTypeID INT
,#PackingSlNo INT
,#PackingBarCode VARCHAR(25)
,#active BIT
AS
BEGIN
-- Create Barcode if #PackingBarCode is null
IF #PackingBarCode = NULL OR #PackingBarCode = ''
BEGIN
SET #PackingBarCode = (
SELECT CASE
-- Ceck If total length < 10, then add zeros in between
WHEN LEN(#ItemID) + LEN(#PackingTypeID) < 10
THEN CONCAT(#ItemID, RIGHT(CONCAT('0000000000', #PackingTypeID), 10 -( LEN(#ItemID) + LEN(#PackingTypeID)) ),#PackingTypeID)
ELSE CONCAT(#ItemID, #PackingTypeID)
END AS BarCode
)
END
INSERT INTO aItemPackingDetail (
ItemID
,PackingTypeID
,PackingSlNo
,PackingBarCode
,active
)
VALUES (
#ItemID
,#PackingTypeID
,#PackingSlNo
,#PackingBarCode
,#active
)
END
Now I want to check duplicate for #PackingBarCode, I can check duplicate by following code
WHERE NOT EXISTS (SELECT 1 from aItemPackingDetail WHERE categoryid=#cat and PackingBarCode = #PackingBarCode)
But then it get rejected and niot saved, I want to save it, but before saving add #PackingSlNo to the end and make it unique, again check for duplicate and if add #PackingSlNo once again and so on, when it is not duplicate add it into database.
The duplicate #PackingBarCode check needs to done if #PackingBarCode is generated in sp or entered by user.
How do I get there? please help

SQL SERVER Select multiple fields from If Exists

I have to do an SQL Server Statement that have to return an empty row when is null, and data otherwhise.
I am trying to do a Select from (if exisits) but have an error on parent table.
I Simplify it. But the meaning, is to retrieve a couple of fields when condition is null and other fields when it is not null.
It Works fine when I do not clouse it in another select.... I need to retrieve it as a table to do an inner Join with other clouse.
How can i resolved it?
Here is my code..
select * from
(
if exists(select isnull(SECTOR_ID_DESTINO_BAD,-1)
from workflow_Compras_detalle w
where w.id=2)
begin
select null as Sector,null as sector_id_origen
end
else
begin
select top 1 isnull(ws.sector,'') sector, wd.sector_id_origen
from workflow_Compras_detalle wd
where orden < 10
end
)Table
you should try to insert the data into a temporary table or Table Variable, then get the data from that table, here is an example with a Table Variable, if you need something more persistent you may use a #Temp Table, i recommend you take a look to this: difference between var table and #Temp Table
DECLARE #VAR_TABLE AS TABLE(
Sector varchar(25),
sector_id_origen int
)
if exists(select isnull(SECTOR_ID_DESTINO_BAD,-1)
from workflow_Compras_detalle w
where w.id=2)
begin
INSERT INTO #VAR_TABLE
Select null as Sector,null as sector_id_origen
End
Else
begin
INSERT INTO #VAR_TABLE
select top 1 isnull(ws.sector,'') sector, wd.sector_id_origen
from workflow_Compras_detalle wd
where orden < 10
End
SELECT * FROM #VAR_TABLE

Save sequence in special table or get last value?

Do you have advice or best practice or recommendation about sequence for identifiers?
I work on a database where all the identifier or document numbers are 'complex' sequence. For example the sequence for our invoices are INVCCC-2016-0000 where INV is fixed, CCC is the client reference, 2016 is the year and 0000 is a counter from 1 to 9999. This number must be unique and at this moment we keep it in a columns.
When I create a new invoice I need to check the last number created for this client this year then increment it of one then save my data in my database.
I see two way to do it
I create a special table that contain and maintain all last used number for each client. Each time I have a new invoice I check the number in this table, I increment it of one, I use this number to save my invoice then I update the sequence table. 1 READ, 1 INSERT, 1 UPDATE (may be an INSERT in sequence is new)
var keyType = "INV" + ClientPrefix + "-" + Year;
var keyValue = Context.SequenceTable.SingleOrDefault(y => y.KeyType == keyType).KeyValue;
I check the last number in my invoice table, I increment it then I save my invoice. 1 READ, 1 INSERT. I don't need tu update another table and this seems more logic to me. But my database administrator tell me this can create lock or other troubles.
var keyType = "INV" + ClientPrefix + "-" + Year;
var keyValue = Invoices.Where(y => y.InvoiceId.StartsWith(keyType)).OrderByDescending(y => y.InvoiceId).LastOrDefault();
Note I use SQL server before 2015 version and then before the SEQUENCE feature. I fear an inconsistency with solution 1. I fear performance issue with solution 2.
I suggest a table to maintain the last used value for the custom sequence. This method will also ensure there are no gaps, if that is a business requirement.
The example below uses a transactional stored procedure to avoid a race condition in the event an invoice number is generated concurrently for the same client. You'll need to change the CATCH block to use RAISERROR instead of THROW if you are using a pre SQL Server 2012 version.
CREATE TABLE InvoiceSequence (
ClientReferenceCode char(3) NOT NULL
, SequenceYear char(4) NOT NULL
, SequenceNumber smallint NOT NULL
CONSTRAINT PK_InvoiceSequence PRIMARY KEY (ClientReferenceCode, SequenceYear)
);
GO
CREATE PROC dbo.GetNextInvoiceSequence
#ClientReferenceCode char(3) = 'CCC'
, #SequenceYear char(4) = '2016'
AS
SET XACT_ABORT, NOCOUNT ON;
DECLARE #SequenceNumber smallint;
BEGIN TRY
BEGIN TRAN;
UPDATE dbo.InvoiceSequence
SET #SequenceNumber = SequenceNumber = SequenceNumber + 1
WHERE
ClientReferenceCode = #ClientReferenceCode
AND SequenceYear = #SequenceYear;
IF ##ROWCOUNT = 0
BEGIN
SET #SequenceNumber = 1;
INSERT INTO dbo.InvoiceSequence(ClientReferenceCode, SequenceYear, SequenceNumber)
VALUES (#ClientReferenceCode, #SequenceYear, #SequenceNumber);
END;
IF #SequenceNumber > 9999
BEGIN
RAISERROR('Invoice sequence limit reached for client %s year %s', 16, 1, #ClientReferenceCode, #SequenceYear) AS InvoiceNumber;
END;
COMMIT;
SELECT 'INV' + #ClientReferenceCode + '_' + #SequenceYear + '_' + RIGHT('000' + CAST(#SequenceNumber AS varchar(4)), 4);
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
GO
--sample usage:
--note that year could be assigned in the proc if it is based on the current date
EXEC dbo.GetNextInvoiceSequence
#ClientReferenceCode = 'CCC'
, #SequenceYear = '2016';

Resources