Is there a SQL Server function equivalent to AutoNumber() of QlikView? - sql-server

First of all: this is not a kind of a IDENTITY() field.
In QlikView, it is used to generate a number based on parameters send to function.
See its documentation here: https://help.qlik.com/en-US/qlikview/November2017/Subsystems/Client/Content/Scripting/CounterFunctions/autonumber.htm
In short, you send a parameter to it and it returns an integer that will identify the same arguments for the rest of script. If you send...
AutoNumber('Name 900') -> returns 1
AutoNumber('Name 300') -> returns 2
AutoNumber('Name 001') -> returns 3
AutoNumber('Name 900') -> returns 1 ... again
and because the parameter is already in the intern list of AutoNumber
I tried to build some like that in SQL Server, but is not possible use SELECTs inside scalar functions.
My need is to get something like...
INSERT INTO FacSales (SumaryID, InvoiceID, InvoiceDate
, ProductID, SaleValue, CustomerID, VendorID)
SELECT AutoNumber(sale.VendorID, sale.CustomerID, sale.ProductID)
, sale.InvoiceID
, sale.SaleDate
, details.ProductID
, etc, etc, etc.
Is there, inside SQL Server, a "native" function that perform this?
Or, is there a way to build this using a procedure/function?
Thanks.

You could use DENSE_RANK (Transact-SQL)
Returns the rank of rows within the partition of a result set, without
any gaps in the ranking. The rank of a row is one plus the number of
distinct ranks that come before the row in question.
declare #T table
(
ID int identity,
VendorID int,
CustomerID int,
ProductID int
);
insert into #T values
(1, 2, 3),
(1, 2, 3),
(1, 2, 4),
(1, 2, 3);
select sale.ID,
sale.VendorID,
sale.CustomerID,
sale.ProductID,
dense_rank() over(order by sale.VendorID,
sale.CustomerID,
sale.ProductID) as AutoNumber
from #T as sale
order by sale.ID;
Result:
ID VendorID CustomerID ProductID AutoNumber
----------- ----------- ----------- ----------- --------------------
1 1 2 3 1
2 1 2 3 1
3 1 2 4 2
4 1 2 3 1

You basically want a key value store. There are lots of ways to make one.
Here is a possible solution. It uses a stored procedure.
However, you did not say if the values are retained indefinitely or if they are just for a single call. This example shows how to do it indefinitely.
It could be modified to be for a single call or connection via careful use of temporary tables. If it is other than a call or connection then the autoNumber.AutoNumber table and the autoNumber.NextAutoNumber will need to be cleaned up on what ever that schedule is.
-- Create the table, sequence and sproc
-- Create a schema to hold our autonumber table and sequence
CREATE SCHEMA autoNumber
GO
-- Create a sequence. This just gives us a new number when ever we want.
-- This could be replaced with an identity column.
CREATE SEQUENCE autoNumber.NextAutoNumber AS [bigint]
START WITH 1
INCREMENT BY 1
NO CACHE
GO
-- Create a table to hold the auto number key value pairs.
CREATE TABLE autoNumber.AutoNumber(KeyValue varchar(255), Number bigint)
go
-- This is the stored procedure that actually does the work of getting the autonumber
CREATE PROCEDURE autoNumber.GetAutoNumber #KeyValue varchar(255), #AutoNumber bigint = -1 output AS
BEGIN
DECLARE #Number bigint = null
-- See if we already have an autonumber created for this keyvalue
-- If we do, then set #Number to that value
SELECT #Number = autoNum.Number
FROM autoNumber.AutoNumber autoNum
WHERE autoNum.KeyValue = #KeyValue
IF (#Number is null)
BEGIN
-- If #Number was not changed, then we did not find one
-- in the table for this #KeyValue. Make a new one
-- and insert it.
SET #Number = NEXT VALUE FOR autonumber.NextAutoNumber
INSERT INTO autoNumber.AutoNumber ( KeyValue, Number)
VALUES (#KeyValue, #Number)
END
-- Return our number to the caller.
-- This uses either an output parameter or a select.
IF (#AutoNumber = -1)
BEGIN
select #Number
END ELSE
BEGIN
set #AutoNumber = #Number
END
END
GO
-- End Create
-- Testing with "select"
EXEC autoNumber.GetAutoNumber 'Name 900'
EXEC autoNumber.GetAutoNumber 'Name 300'
EXEC autoNumber.GetAutoNumber 'Name 001'
EXEC autoNumber.GetAutoNumber 'Name 900'
-- Testing with output parameter
DECLARE #AutoNumber bigint
EXEC autoNumber.GetAutoNumber 'Name 900', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 300', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 001', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 900', #AutoNumber OUTPUT
SELECT #AutoNumber
-- End Testing
-- Clean up
DROP PROCEDURE autoNumber.GetAutoNumber
GO
DROP TABLE autoNumber.AutoNumber
GO
drop SEQUENCE autoNumber.NextAutoNumber
DROP SCHEMA autoNumber
GO
-- End Cleanup

The closest SQL Server has is CHECKSUM function.
You can use it to calculate a hash value of any number of columns e.g.
SELECT CHECKSUM( 'abc', 123, 'zxc' )
UNION ALL
SELECT CHECKSUM( 'abc', 124, 'zxc' )
UNION ALL
SELECT CHECKSUM( 'abc', 123, 'zxc' )
Output:
-----------
53066784
53066832
53066784

I think you are looking for ROW_NUMBER().
With this sql function you can partition and order by all the field you need.
SELECT ROW_NUMBER() OVER(PARTITION BY sale.VendorID, sale.CustomerID, sale.ProductID ORDER BY sale.VendorID, sale.CustomerID, sale.ProductID)
, sale.InvoiceID
, sale.SaleDate
, details.ProductID FROM table

Related

increment a column in the table based on user input values

I am trying to write a stored procedure that would increment one of the column in the table by 1, everytime either of the country code (AXX,XPP,CGG) is given as user input, depending on the already existing country code value
ID CountryCode
1 AXX
2 AXX
1 XPP
3 AXX
4 AXX
2 XPP
My code below fetches a value that reads the ID like
ID
1
2
3
4
...
...
Create procedure [dbo].[sp_Incremental]
#code varchar(50)
as
begin
declare #IDbigint
set #ID= 1 ;
begin try
Select #ID=count(ID)+1 from [Incremental_tbl]
Begin
Insert into [Incremental_tbl] values (#ID,#code)
End
end
go
You can use this SP :
CREATE PROCEDURE p_Incremental #code VARCHAR(50)
AS
SET NOCOUNT ON;
INSERT INTO Incremental_tbl (ID, code)
SELECT COALESCE(MAX(ID), 0) + 1, #code
FROM Incremental_tbl
GROUP BY #code;
GO
But, do not forget that, in concurrency acesses, same values can occur for the ID !

i want to limit the data inserting into database table on give date and time only 12

create procedure SP_insert_test #name varchar(20), #emailid varchar(20), #trainer_name varchar(50), #training_date varchar(50), #training_time varchar(50), #gymname varchar(50) , #success int out as
begin
if(
select
count(id)
from
Add_Booking_Fitness_Training
where
training_time = #training_time) > 11 print N'Number of Booking Is Complete for this time and date plz book other time';
else
insert into
Add_Booking_Fitness_Training(memeber_name, member_emailid, trainer_name, training_date, training_time, gymname)
values
(
#name,
#emailid,
#trainer_name,
#training_date,
#training_time,
#gymname
)
SELECT
SCOPE_IDENTITY()
set
#success = 1;
end
begin
set
#success = 0;
end
i have an table in which i want to insert data on give time only 12 member can insert at that time after that they get message list is full plz change the time for inserting i have create procedure its working when its reach number of 12 than its show me message but when i change the time its also show me the same message and not insert any data into database
like 26/04/2018,'6:00' i want to insert this value only 12 time after 12 this show me a message about the limit of number is reach plz change (time)
Create table Add_Booking_Fitness_Training ( id int identity primary key,
memeber_name varchar(20),
member_emailid varchar(20),
trainer_name varchar(50),
training_date varchar(50),
training_time varchar(50),
gymname varchar(50))
i just want to inserting a value into this table only 12 time for a give time like (6:00) if the number of inserting value reach to 12 than its show me the message number of values insert is reach to 12 please change the time.
i want input the value into table only 12 time for a give time 6:00Am when the value is insert into table 12 time than message come up for change time than insert value for change time
Honestly, I am completely guessing here, I still don't really know what you're asking.
I think the OP's statement of "i want input the value into table only 12 time for a give time 6:00Am when the value is insert into table 12 time than message come up for change time than insert value for change time." means that they only want a time to appear in the table up to 12 times. If it appears more than that, the INSERT fails.
This can be achieved with a check constraint and a scalar function. So, as a very simple example:
USE Sandbox;
GO
--Create a very simple table
CREATE TABLE SampleTable (TrainingTime datetime2(0));
GO
--Create the scalar function
CREATE FUNCTION TrainingAtTime (#TrainingTime datetime2(0))
RETURNS INT
AS BEGIN
DECLARE #Trainees int;
SELECT #Trainees = COUNT(*)
FROM SampleTable
WHERE TrainingTime = #TrainingTime;
RETURN #Trainees;
END
GO
--Add the check constraint
ALTER TABLE SampleTable ADD CONSTRAINT MaxTrainees CHECK (dbo.TrainingAtTime(TrainingTime) <= 12) ;
GO
--Insert first trainee
INSERT INTO SampleTable
VALUES ('2018-04-26T06:00:00');
--It works
SELECT TrainingTime, COUNT(*) AS Trainees
FROM SampleTable
GROUP BY TrainingTime;
GO
--insert 11 more
INSERT INTO SampleTable
VALUES ('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00'),
('2018-04-26T06:00:00');
--It works
SELECT TrainingTime, COUNT(*) AS Trainees
FROM SampleTable
GROUP BY TrainingTime;
GO
--Try to insert another
INSERT INTO SampleTable
VALUES ('2018-04-26T06:00:00');
--It fails
SELECT TrainingTime, COUNT(*) AS Trainees
FROM SampleTable
GROUP BY TrainingTime;
GO
--Use a different time
INSERT INTO SampleTable
VALUES ('2018-04-26T08:00:00');
--it works
SELECT TrainingTime, COUNT(*) AS Trainees
FROM SampleTable
GROUP BY TrainingTime;
GO
--Clean up
DROP TABLE SampleTable;
DROP FUNCTION TrainingAtTime;
GO
If this isn't what you're after, unfortunately I don't understand your requirements due the the language barrier (and absence of a question).

How to find how many times repeat number in comma separated value in particular column in SQL Server

I have a SQL Server table as shown below, in that one column contains comma-separated integer values. What I want is to get particular number with count as below expected result
Edited:
I know how to split the comma separated value. but my problem is output that I want. see the expected output in that count column shows in all table howmnay times particular number repeat.
For example :
Id values
---------------
1 2,3
2 1,2,3
3 1,3
4 2,3
Expected result :
number repeat count
--------------
1 2
2 3
3 4
Can anyone help me with this? How to write the query to get this desired output?
Thanks in advance
It looks like the question is how to aggregate the results of a SPLIT function, not how to split the values.
SQL Server 2016 provides the built-in STRING_SPLIT function to split a delimited string and return the values as a table. Individual values are returned in the value field. The following query groups the value field and returns the count:
declare #table table (id int, somevalues nvarchar(200))
insert into #table
values
(1,N'2,3'),
(2,N'1,2,3'),
(3,N'1,3'),
(4,N'2,3')
select value,count(* )
from #table
cross apply string_split(somevalues,',')
group by value
The same query can be used in previous versions as long as a split function is available. Almost all of the available techniques are described in Aaron Bertrand's articles like this one and this follow up. The fastest methods use CLR and XML.
The queries are the same, the only things that change are the names of the columns returned by the split function, eg:
select item,count(* )
from #table
cross apply dbo.SplitStrings_XML(somevalues,',')
group by item
In both cases the result is :
value (No column name)
1 2
2 3
3 4
First create split function like this
CREATE FUNCTION SplitString (
#Input NVARCHAR(MAX)
, #Character CHAR(1)
)
RETURNS #Output TABLE (Item NVARCHAR(1000))
AS
BEGIN
DECLARE #StartIndex INT
, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output (Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
GO
then you can adapt the query as follows
create table #temp(
id int,
test varchar(20)
)
insert into #temp (id,test) values (1,'1,2,3')
SELECT t.id, count(sf.Item)
FROM #temp AS t
CROSS APPLY dbo.SplitString(t.test,',') AS sf
group by t.id;
drop table #temp

How can I avoid calling a stored procedure from a UDF in SQL Server

Before I start, I know you can't call a stored procedure from a UDF and I know that there are various "reasons" for this (none that make much sense to me though, tbh it just sounds like laziness on Microsoft's part).
I am more interested in how I can design a system to get round this flaw in SQL Server.
This is a quick overview of the system I currently have:
I have a dynamic report generator where users specify data items, operators (=, <, !=, etc.) and filter values. These are used to build up "rules" with one or more filters, e.g. I might have a rule that has two filters "Category < 12" and "Location != 'York'";
there are thousands and thousands of these "rules", some of them have many, many filters;
the output from each of these rules is a statuory report that always has exactly the same "shape", i.e. the same columns/ data types. Basically these reports produce lists of tonnages and materials;
I have a scalar-valued function that generates Dynamic SQL for a specified rule, returning this as a VARCHAR(MAX);
I have a stored procedure that is called to run a specific rule, it calls the UDF to generate the Dynamic SQL, runs this and returns the results (this used to just return the results but now I store the output in process-keyed tables to make the data easier to share and so I return a handle to this data instead);
I have a stored procedure that is called to run all the rules for a particular company, so it makes a list of the rules to run, runs them sequentially and then merges the results together as output.
So this all works perfectly.
Now I want one final thing, a report that runs the company summary and then applies costs to the tonnages/ materials to result in a cost report. This seemed such a simple requirement when I started on this last week :'(
My report has to be a table-valued function for it to work with the report broker system I have already written. If I write it as a stored procedure then it will not be run through my report broker which means that it will not be controlled, i.e. I won't know who ran the report and when.
But I can't call a stored procedure from within a table-valued function and the two obvious ways to handle this are as follows:
Get the SQL to create the output, run it and suck up the results.
--Method #1
WHILE #RuleIndex <= #MaxRuleIndex
BEGIN
DECLARE #DSFId UNIQUEIDENTIFIER;
SELECT #DSFId = [GUID] FROM NewGUID; --this has to be deterministic, it isn't but the compiler thinks it is and that's good enough :D
DECLARE #RuleId UNIQUEIDENTIFIER;
SELECT #RuleId = DSFRuleId FROM #DSFRules WHERE DSFRuleIndex = #RuleIndex;
DECLARE #SQL VARCHAR(MAX);
--Get the SQL
SELECT #SQL = DSF.DSFEngine(#ServiceId, #MemberId, #LocationId, #DSFYear, NULL, NULL, NULL, NULL, #DSFId, #RuleId);
--Run it
EXECUTE(#SQL);
--Copy the data out of the results table into our local copy
INSERT INTO
#DSFResults
SELECT
TableId, TableCode, TableName, RowId, RowName, LocationCode, LocationName, ProductCode, ProductName, PackagingGroupCode, PackagingGroupName, LevelName, WeightSource, Quantity, Paper, Glass, Aluminium, Steel, Plastic, Wood, Other, 0 AS General
FROM
DSF.DSFPackagingResults
WHERE
DSFId = #DSFId
AND RuleId = #RuleId;
SELECT #RuleIndex = #RuleIndex + 1;
END;
Call the report directly
--Method #2
WHILE #RuleIndex <= #MaxRuleIndex
BEGIN
DECLARE #DSFId UNIQUEIDENTIFIER;
SELECT #DSFId = [GUID] FROM NewGUID; --this has to be deterministic, it isn't but the compiler thinks it is :D
DECLARE #RuleId UNIQUEIDENTIFIER;
SELECT #RuleId = DSFRuleId FROM #DSFRules WHERE DSFRuleIndex = #RuleIndex;
DECLARE #SQL VARCHAR(MAX);
--Run the report
EXECUTE ExecuteDSFRule #ServiceId, #MemberId, #LocationId, #DSFYear, NULL, NULL, NULL, #RuleId, #DSFId, 2;
--Copy the data out of the results table into our local copy
INSERT INTO
#DSFResults
SELECT
TableId, TableCode, TableName, RowId, RowName, LocationCode, LocationName, ProductCode, ProductName, PackagingGroupCode, PackagingGroupName, LevelName, WeightSource, Quantity, Paper, Glass, Aluminium, Steel, Plastic, Wood, Other, 0 AS General
FROM
DSF.DSFPackagingResults
WHERE
DSFId = #DSFId
AND RuleId = #RuleId;
SELECT #RuleIndex = #RuleIndex + 1;
END;
I can think of the following workarounds (none of which are particularly satisfactory):
rewrite some of this in CLR (but this is just a whole lot of hassle to break the rules);
use a stored procedure to produce my report (but this means I lose control of the execution unless I develop a new system for this SINGLE report, different to the dozens of existing reports that all work fine);
split execution from reporting, so I have one process to execute the report and another that just picks up the output (but no way to tell when the report has completed without more work);
wait until Microsoft see sense and allow execution of stored procedures from UDFs.
Any other ideas out there?
Edit 3-May-2013, here is a (very) simple example of how this hangs together:
--Data to be reported
CREATE TABLE DataTable (
MemberId INT,
ProductId INT,
ProductSize VARCHAR(50),
Imported INT,
[Weight] NUMERIC(19,2));
INSERT INTO DataTable VALUES (1, 1, 'Large', 0, 5.4);
INSERT INTO DataTable VALUES (1, 2, 'Large', 1, 6.2);
INSERT INTO DataTable VALUES (1, 3, 'Medium', 0, 2.3);
INSERT INTO DataTable VALUES (1, 4, 'Small', 1, 1.9);
INSERT INTO DataTable VALUES (1, 5, 'Small', 0, 0.7);
INSERT INTO DataTable VALUES (1, 6, 'Small', 1, 1.2);
--Report Headers
CREATE TABLE ReportsTable (
ReportHandle INT,
ReportName VARCHAR(50));
INSERT INTO ReportsTable VALUES (1, 'Large Products');
INSERT INTO ReportsTable VALUES (2, 'Imported Small Products');
--Report Detail
CREATE TABLE ReportsDetail (
ReportHandle INT,
ReportDetailHandle INT,
DatabaseColumn VARCHAR(50),
DataType VARCHAR(50),
Operator VARCHAR(3),
FilterValue VARCHAR(50));
INSERT INTO ReportsDetail VALUES (1, 1, 'ProductSize', 'VARCHAR', '=', 'Large');
INSERT INTO ReportsDetail VALUES (2, 1, 'Imported', 'INT', '=', '1');
INSERT INTO ReportsDetail VALUES (2, 1, 'ProductSize', 'VARCHAR', '=', 'Small');
GO
CREATE FUNCTION GenerateReportSQL (
#ReportHandle INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #SQL VARCHAR(MAX);
SELECT #SQL = 'SELECT SUM([Weight]) FROM DataTable WHERE 1=1 ';
DECLARE #Filters TABLE (
FilterIndex INT,
DatabaseColumn VARCHAR(50),
DataType VARCHAR(50),
Operator VARCHAR(3),
FilterValue VARCHAR(50));
INSERT INTO #Filters SELECT ROW_NUMBER() OVER (ORDER BY DatabaseColumn), DatabaseColumn, DataType, Operator, FilterValue FROM ReportsDetail WHERE ReportHandle = #ReportHandle;
DECLARE #FilterIndex INT = NULL;
SELECT TOP 1 #FilterIndex = FilterIndex FROM #Filters;
WHILE #FilterIndex IS NOT NULL
BEGIN
SELECT TOP 1 #SQL = #SQL + ' AND ' + DatabaseColumn + ' ' + Operator + ' ' + CASE WHEN DataType = 'VARCHAR' THEN '''' ELSE '' END + FilterValue + CASE WHEN DataType = 'VARCHAR' THEN '''' ELSE '' END FROM #Filters WHERE FilterIndex = #FilterIndex;
DELETE FROM #Filters WHERE FilterIndex = #FilterIndex;
SELECT #FilterIndex = NULL;
SELECT TOP 1 #FilterIndex = FilterIndex FROM #Filters;
END;
RETURN #SQL;
END;
GO
CREATE PROCEDURE ExecuteReport (
#ReportHandle INT)
AS
BEGIN
--Get the SQL
DECLARE #SQL VARCHAR(MAX);
SELECT #SQL = dbo.GenerateReportSQL(#ReportHandle);
EXECUTE (#SQL);
END;
GO
--Test
EXECUTE ExecuteReport 1;
EXECUTE ExecuteReport 2;
SELECT dbo.GenerateReportSQL(1);
SELECT dbo.GenerateReportSQL(2);
GO
--What I really want
CREATE FUNCTION RunReport (
#ReportHandle INT)
RETURNS #Results TABLE ([Weight] NUMERIC(19,2))
AS
BEGIN
INSERT INTO #Results EXECUTE ExecuteReport #ReportHandle;
RETURN;
END;
--Invalid use of a side-effecting operator 'INSERT EXEC' within a function
If I was in your situation, I wouldn't try to hack anything. I would setup the objects like this:
CREATE TABLE [dbo].[ReportCollection] (
[ReportCollectionID] int,
[ReportID] int
)
CREATE TABLE [dbo].[ReportResult] (
[ReportID] int,
[LocationCode] int,
[LocationName] nvarchar(max)
)
CREATE PROCEDURE [dbo].[usp_ExecuteReport] (
#ReportID int
)
AS
INSERT [dbo].[ReportResult]
SELECT #ReportID, 1, N'StackOverflow'
END
CREATE FUNCTION [dbo].[udf_RetrieveReportCollectionResults] (
#ReportCollectionID int
)
RETURNS #Results TABLE ([ReportID], [LocationCode], [LocationName])
AS
BEGIN
SELECT *
FROM [dbo].[ReportResult] rr
JOIN [dbo].[ReportCollection] rc
ON rr.ReportID = rc.ReportID
WHERE rc.ReportCollectionID = #ReportCollectionID
END
And use them like this:
INSERT [dbo].[ReportCollection] VALUES (1, 1)
INSERT [dbo].[ReportCollection] VALUES (1, 2)
EXEC [dbo].[usp_ExecuteReport] #ReportID = 1
EXEC [dbo].[usp_ExecuteReport] #ReportID = 2
SELECT * FROM [dbo].[udf_RetrieveReportCollectionResults](1)
Each time you run your reports, start a new collection. Your application should kick off all of the reports and consolidate the results afterward.
--
If you really wanted to call a stored procedure from a udf (please don't), do a search on xp_cmdshell.
If you really want this working as a function then the least hacky way would be CLR integration.
You don't have to redo everything - just write a CLR wrapper function that calls the stored procedure & returns the stored procs result set as it's own.1
This way all your current SQL development is untouched.

Why is the natural ID generation in this SQL Stored Proc creating duplicates?

I am incrementing the alphanumeric value by 1 for the productid using stored procedure. My procedure incrementing the values up to 10 records, once its reaching to 10th say for PRD0010...no more its incrementing... however, the problem is it is repeating
the same values PRD0010.. for each SP call.
What could be the cause of this?
create table tblProduct
(
id varchar(15)
)
insert into tblProduct(id)values('PRD00')
create procedure spInsertInProduct
AS
Begin
DECLARE #PId VARCHAR(15)
DECLARE #NId INT
DECLARE #COUNTER INT
SET #PId = 'PRD00'
SET #COUNTER = 0
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
--here increse the vlaue to numeric id by 1
SET #NId = #NId + 1
--GENERATE ACTUAL APHANUMERIC ID HERE
SET #PId = #PId + cast(#NId AS VARCHAR)
INSERT INTO tblProduct(id)values (#PId)
END
Change
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
To
SELECT TOP 1
#NId = cast(substring(id, 4, len(id)) as int)
FROM tblProduct order by LEN(id) DESC, ID DESC
You have to remember that
PRD009
is always greater than
PRD0010
or
PRD001
All in all, I think your approach is incorrect.
Your values will be
PRD00
PRD001
...
PRD009
PRD0010
PRD0011
...
PRD0099
PRD00100
This will make sorting a complete nightmare.
In addition to astander's analysis, you also have a concurrency issue.
The simple fix would be to add this at the beginning of your proc:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
And add a COMMIT at the end. Otherwise, two callers of this stored proc will get the same MAX/TOP 1 value from your table, and insert the same value.
Also, you can and should prevent these duplicates from existing by adding a key to your table, for this column. If you already have a PRIMARY KEY on this table, you can add an additional key using a UNIQUE constraint. This will prevent duplicates occurring in the future, no matter what programming errors occur. E.g.
ALTER TABLE tblProduct ADD CONSTRAINT UQ_Product_ID UNIQUE (ID)

Resources