I have series of queries based on a report type. For simplicity here is an example of what i'm trying to do:
If #Reporttype = '1'
Select lcustomerid, lname, fname
from customers
Where dtcreated > #startdate
Else if #Reporttype = '2'
Select barcode, lname, fname
from employees
where dtcreated > #startdate
Else if #reporttype = '3'
Select thetime, lname, name, barcode, lcustomerid
from Customers
where dtcreated > #startdate
You'll notice that I run 3 separate queries, based on the report type being passed. You'll also notice I am returning different columns and the number of columns.
I'd like to make this a stored function, and return the columns I need based on the report type I pass. However, I know that since the number of columns, and the column names are different - that's not going to work as a stored function as I'd like it to.
The major problem here will be reporting this information - I don't want to have separate functions, because i'll have to maintain different reports for each report type.
Is there a way I can make this work?
You can use multi-statement function but you need to specify all columns which will be returned by 3 select statements. It seems it's impossible return multiple result sets.
User-defined functions can not return multiple result sets. Use a
stored procedure if you need to return multiple result sets. https://msdn.microsoft.com/en-us/library/ms191320.aspx
This is one inconvenience but in report you can use only columns you need, others will be nulls.
CREATE FUNCTION MyFun
(
#Reporttype int,
#startdate datetime
)
RETURNS
#Result TABLE
(
lcustomerid int,
lname nvarchar(50),
fname nvarchar(50),
barcode int,
thetime datetime,
name nvarchar(50)
)
AS
BEGIN
If #Reporttype = '1'
insert into #Result (lcustomerid, lname, fname)
select lcustomerid, lname, fname
from customers
Where dtcreated > #startdate
Else if #Reporttype = '2'
insert into #Result (barcode, lname, fname)
Select barcode, lname, fname
from employees
where dtcreated > #startdate
Else if #reporttype = '3'
insert into #Result (thetime, lname, name, barcode, lcustomerid)
Select thetime, lname, name, barcode, lcustomerid
from customers
where dtcreated > #startdate
RETURN
END
So, you can call function in this way
SELECT * FROM dbo.MyFun (1, getdate())
If you cannot use stored procedure and you need to use a function, you can UNPIVOT the data and than in the client side you can PIVOT it.
I need to do something like this when different number of columns are returned to SQL Server Reporting Services report. For example, the following code is always returning three columns - RowID, Column, Value:
DECLARE #Table01 TABLE
(
[ID] INT
,[Value01] INT
,[Value02] NVARCHAR(256)
,[Value03] SMALLINT
);
DECLARE #Table02 TABLE
(
[ID] INT
,[Value01] INT
);
INSERT INTO #Table01 ([ID], [Value01], [Value02], [Value03])
VALUES (1, 111, '1V2', 7)
,(2, 222, '2V2', 8)
,(3, 333, '3V2', 9);
INSERT INTO #Table02 ([ID], [Value01])
VALUES (1, 111)
,(2, 222)
,(3, 333);
-- your function starts here
DECLARE #Mode SYSNAME = 'Table01' -- try with 'Table02', too
DECLARE #ResultSet TABLE
(
[RowID] INT
,[Column] SYSNAME
,[Value] NVARCHAR(128)
);
IF #Mode = 'Table01'
BEGIN;
INSERT INTO #ResultSet ([RowID], [Column], [Value])
SELECT [ID]
,[Column]
,[Value]
FROM
(
SELECT [ID]
,CAST([Value01] AS NVARCHAR(256))
,CAST([Value02] AS NVARCHAR(256))
,CAST([Value03] AS NVARCHAR(256))
FROM #Table01
) DS ([ID], [Value01], [Value02], [Value03])
UNPIVOT
(
[Value] FOR [Column] IN ([Value01], [Value02], [Value03])
) UNPVT
END;
ELSE
BEGIN;
INSERT INTO #ResultSet ([RowID], [Column], [Value])
SELECT [ID]
,[Column]
,[Value]
FROM
(
SELECT [ID]
,CAST([Value01] AS NVARCHAR(256))
FROM #Table02
) DS ([ID], [Value01])
UNPIVOT
(
[Value] FOR [Column] IN ([Value01])
) UNPVT
END;
SELECT *
FROM #ResultSet;
Then in the reporting I need to perform pivot operation again. This is workaround with many limitations:
the unpivot data must be cast to its largest type (usually, string)
unnecessary operations are performed (pivot -> unpivot) instead of just rendering the data;
it does not working well with large amount of data (it is slow)
and others..
For This you may create a scalar value function that return an xml type column and then you can populate that xml tag values to your report screen
CREATE FUNCTION ReportFunc
(
#intReporttype int,
#dtStartdate datetime
)
RETURNS XML
BEGIN
Declare #xmlResult xml
If #intReporttype = '1'
SET #xmlResult = (
select lcustomerid, lname, fname
from customers
Where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
Else if #intReporttype = '2'
SET #xmlResult = (
Select barcode, lname, fname
from employees
where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
Else if #intReporttype = '3'
SET #xmlResult = (
Select thetime, lname, name, barcode, lcustomerid
from customers
where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
RETURN #xmlResult
END
In SQL it is difficult to create something similar so generic or abstract, especially when it has to do with SELECT of colums. If your purpose is to write as less code as you can in order your sql script to be maintained easily and to be able to add new report types in the future with just minor changes I would suggest to use a stored procedure with dynamic sql. You cannot use a function while you wisk your SELECT to be dynamic, its the wrong method. I would write something like that
CREATE PROCEDURE MyProcedure
(
#ReportType int,
#startdate datetime
)
AS
BEGIN
DECLARE #colNames varchar(MAX),#tblName varchar(MAX),#sSQL varchar(MAX);
SELECT #colNames = CASE
WHEN #ReportType = 1 THEN
'lcustomerid, lname, fname' --You can also add alias
WHEN #ReportType = 2 THEN
'barcode, lname, fname'
WHEN #ReportType = 3 THEN
'thetime, lname, name, barcode, lcustomerid'
ELSE
RAISEERROR('Error msg');
END,
#tblName = CASE
WHEN #ReportType = 1 OR #ReportType = 3 THEN
'customers' --You can also add alias
WHEN #ReportType = 2 THEN
'employees'
ELSE
RAISEERROR('Error msg');
END
SET #sSQL =
'Select '+#colNames+'
from '+#tblName +'
where dtcreated > '''+CONVERT(varchar(10), #startdate, 121)+''''
EXEC(#sSQL)
END
And you will call it as
EXEC MyProcedure 1,'20170131'
for example
With this code every time you want a new report type you will need to add just another line in case with the requested column names. I have used this way in working with Crystal reports and I think it is the best possible solution
If you can use Stored Procedures then for maintainability I would look at using a master stored procedure which calls other stored procedures to return different result sets:
CREATE PROCEDURE MyProc_1(#startdate DateTime)
AS
BEGIN
SELECT lcustomerid, lname, fname
FROM customers WHERE dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc_2(#startdate DateTime)
AS
BEGIN
SELECT barcode, lname, fname
FROM employees where dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc_3(#startdate DateTime)
AS
BEGIN
SELECT thetime, lname, name, barcode, lcustomerid
FROM Customers WHERE dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc(#Reporttype char(1), #startdate DateTime)
AS
BEGIN
IF #Reporttype = '1' EXEC MyProc_1 #startdate
ELSE IF #Reporttype = '2' EXEC MyProc_2 #startdate
ELSE IF #reporttype = '3' EXEC MyProc_3 #startdate
END
GO
And to use:
DECLARE #dt datetime = getdate()
EXEC MyProc 1, #dt
CREATE Proc Emp_det
(
#Reporttype INT,
#startdate DATETIME
)
AS
BEGIN
If #Reporttype = '1' BEGIN
Select lcustomerid, lname, fname
FROM customers
WHERE dtcreated > #startdate
END
ELSE IF #Reporttype = '2' BEGIN
Select barcode, lname, fname
FROM employees
WHERE dtcreated > #startdate
END
ELSE IF #reporttype = '3' BEGIN
Select thetime, lname, name, barcode, lcustomerid
FROM Customers
WHERE dtcreated > #startdate
END
END
GO
Exec Emp_det 1,GETDATE()
Related
I am trying to store sensor data recorded to the appropriate target column, depending on what the latest settings are in the settings table (represented as #Unit in the code). The code I have so far gives me a syntax error at line 19 near 'Time', which I am not sure as to why. Any ideas?
CREATE PROCEDURE StoreTemp
#Temperature float,
#Seconds int,
#SessionId int,
#DatasetId int
AS
DECLARE
#Unit varchar(20),
#TempCol varchar(20)
SELECT #Unit = RecordingUnit from ReadLastUnit
SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END)
INSERT INTO DATASET (#TempCol, Time)
VALUES (#Temperature, GETDATE())
GO
You can't use a variable to specify a column name in an insert statement. The column name must be static.
You could use dynamic SQL, however in this case you can just conditionally insert a value into the correct column using a case expression as follows:
CREATE PROCEDURE StoreTemp
(
#Temperature float
, #Seconds int
, #SessionId int
, #DatasetId int
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Unit varchar(20), #TempCol varchar(20);
SELECT #Unit = RecordingUnit from ReadLastUnit;
--SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END);
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN #Unit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN #Unit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE();
END
GO
For better performance and a neater query, you could even simplify the insert to do it all e.g.
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN RecordingUnit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN RecordingUnit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE()
from ReadLastUnit;
Thats assuming you can always guarantee a single row in ReadLastUnit.
I have a stored procedure where I pass parameter with type of filter, and second parameter with value of filter. It can be game type, user type etc.
I want to filter data based on different type. If it is game type it should filter by game_name column(whatever it is passed as parameter), if user type by user type name.
I am wondering from perspective of design, is it better to put multiple case statements in one stored procedure or create each stored procedure for different filter type, which in the end I would end with 5-6 different stored procedures with same core sql(select statement).
Example of procedure:
ALTER PROCEDURE [dbo].[Reports_UserStatsDaily] #network VARCHAR(9) = NULL,
#playerAddress VARCHAR(42) = NULL,
#year INT = NULL,
#month INT = NULL,
#from VARCHAR(15) = null,
#to VARCHAR(15) = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET nocount ON;
-- Insert statements for procedure here
SELECT [playeraddress],
[network],
[rounds],
[sessions],
[handle],
[hold],
Datefromparts([year], [month], [day]) AS [Date]
FROM [dbo].[userstatsdaily]
WHERE ( #network IS NULL
OR ( network = Upper(#network) ) )
AND ( #playerAddress IS NULL
OR ( playeraddress = Upper(#playerAddress) ) )
AND ( #year IS NULL
OR [year] = #year )
AND ( #month IS NULL
OR [month] = #month )
AND ( #from IS NULL
OR ( Datefromparts([year], [month], [day]) BETWEEN
Cast(#from AS DATETIME2) AND Cast(#to AS DATETIME2)
)
)
AND (len(Handle) > 9 or len(Hold) > 9)
ORDER BY [year] ASC,
[month] ASC,
[day] ASC
END
Problem here is, more filters I put, I put more optional parameters, and append in the end in WHERE clause. How to achieve separation of concerns in stored procedures?
DECLARE #Coid INT
DECLARE #DTRID INT
DECLARE #EMPID INT
DECLARE #DATE datetime
SELECT TOP 1 #EMPID = tblEmployees.Id, #Coid = tblEmployees.CompanyId, #DATE = tblDailyTimeRecord.TimeIn, #DTRID = tblDailyTimeRecord.Id FROM tblEmployees INNER JOIN tblDailyTimeRecord ON tblEmployees.Id = tblDailyTimeRecord.EmployeeId WHERE tblDailyTimeRecord.Date = CONVERT(date, GETDATE()) AND tblDailyTimeRecord.TimeOut IS NULL ORDER BY tblDailyTimeRecord.ID DESC
IF #Coid IS NULL
BEGIN
DECLARE #IdentityValue TABLE ( ContactID int,EmpId int,DATE datetime)
INSERT INTO [tblDailyTimeRecord]([EmployeeId],[Date],[TimeOut],[IsModified],[CompanyId])
OUTPUT INSERTED.Id,INSERTED.EmployeeId,INSERTED.TimeOut INTO #IdentityValue
Select Id,CONVERT(date, GETDATE()),GETDATE(),0,CompanyId From tblEmployees Where AccessCode = 'GI0056'
INSERT INTO tblTimeLog([EmployeeId],[Time],[Type],[TimeLogSourceId],[CreationDate])
Select EmpId,DATE,2,11,GETDATE() From #IdentityValue
END
ELSE
BEGIN
DECLARE #PAIR int
DECLARE #TIMEOUT datetime
SET #TIMEOUT = GETDATE()
IF #DATE IS NULL
SET #PAIR = 0
ELSE
SET #PAIR = 1
UPDATE [tblDailyTimeRecord] SET TimeOut = #TIMEOUT, PairNo = #PAIR WHERE ID = #DTRID
INSERT INTO tblTimeLog([EmployeeId],[Time],[Type],[TimeLogSourceId],[CreationDate])
VALUES (#EMPID,#TIMEOUT,2,11,#TIMEOUT)
END
Just general formatting rules:
1. All declaration better are in the beginning.
2. Be consistent: all reserved words in capital
3. Do not use reserved words for column names.
4. Comment your code.
5. Use unified intends.
6. Finish statements with semicolon.
7. If you can, use CASE instead of IF SET.
8. Use short aliases for tables and views.
9. Name variables in SELECT statement, then it will be clear what goes where.
Have I forgot something?
Logic/Design.
1. Instead of multiple variables you could use temp table variable or just temp table. It would make code shorter and more readable.
2. First SELECT returns one row, but in the INSERT statement you might insert something completely irrelevant.
3. Having the same data in two tables might be a sign of bad design.
4. Having Date and Datetime for the same event in the same row is very suspicious.
It might be as the code below. Hope that helps.
DECLARE #Coid INT;
DECLARE #DTRID INT;
DECLARE #EMPID INT;
DECLARE #DATE DATETIME;
DECLARE #PAIR INT;
DECLARE #TIMEOUT DATETIME;
DECLARE #IdentityValue TABLE (
ContactID int,
EmpId int,
ContactDate DATETIME
);
-- Select ONLY one row
SELECT TOP 1
#EMPID = e.Id,
#Coid = e.CompanyId,
#DATE = tr.TimeIn,
#DTRID = tr.Id,
#TIMEOUT = GETDATE(),
#PAIR = CASE WHEN tr.TimeIn IS NULL THEN 0 ELSE 1 END
FROM tblEmployees as e
INNER JOIN tblDailyTimeRecord as tr
ON e.Id = tr.EmployeeId
WHERE tr.Date = CONVERT(DATE, GETDATE())
AND tr.TimeOut IS NULL
ORDER BY tr.ID DESC;
IF #Coid IS NULL
BEGIN
-- Insert more than one row or might insert nothing
INSERT INTO [tblDailyTimeRecord] (
[EmployeeId],
[Date],
[TimeOut],
[IsModified],
[CompanyId]
)
OUTPUT INSERTED.Id,
INSERTED.EmployeeId,
INSERTED.TimeOut
INTO #IdentityValue
SELECT Id as [EmployeeId],
CONVERT(DATE, GETDATE()) as [Date],
#TIMEOUT as [TimeOut],
0 as [IsModified],
CompanyId
FROM tblEmployees
WHERE AccessCode = 'GI0056';
INSERT INTO tblTimeLog(
[EmployeeId],
[Time],
[Type],
[TimeLogSourceId],
[CreationDate])
SELECT EmpId as [EmployeeId],
ContactDate as [Time],
2 as [Type],
11 as [TimeLogSourceId],
#TIMEOUT as [CreationDate]
FROM #IdentityValue;
END
ELSE
BEGIN
UPDATE [tblDailyTimeRecord]
SET TimeOut = #TIMEOUT,
PairNo = #PAIR
WHERE ID = #DTRID
INSERT INTO tblTimeLog(
[EmployeeId],
[Time],
[Type],
[TimeLogSourceId],
[CreationDate]
) VALUES (
#EMPID,
#TIMEOUT,
2,
11,
#TIMEOUT
);
END
I am having a trouble in implementing below requirement.
Current RadGrid: Below is the RadGrid in which I am using GroupByExpressions
to display/show data grouped with "Business Unit" column.
In RadGrid column 2nd(InvoiceLineNo) and 3rd(InvoiceNo), I am auto generating the numbers using Stored Procedure.
i.e., for "InvoiceLineNo" column, Autogenerated No's are: 01,02,03,04,05,06,07,08.......n
for "InvoiceNo" column, Autogenerated No's are: 15100001, 15100002, 15100003........n
where, 15 is a "year" and 100001 are "running numbers"
Requirement is: I want to show the "InvoiceLineNo" column data as Group wise.
Example:
for 1st "Business Unit" group (i.e., SUNWAY LEISURE SDN BHD (CARNIVAL)),
InvoiceLineNo shall be: 01,02,03,04,05,06,07,08
for 2nd "Business Unit" group (i.e., SUNWAY MALL PARKING SDN BHD),
InvoiceLineNo shall be: 01,02,03,04,05,06,07,08
Similarly, I want to show the "InvoiceNo" column data as Group wise.
Example:
for 1st "Business Unit" group (i.e., SUNWAY LEISURE SDN BHD (CARNIVAL)),
InvoiceNo shall be: 15100001,15100001,15100001,15100001,15100001,15100001,15100001,15100001
for 2nd "Business Unit" group (i.e., SUNWAY MALL PARKING SDN BHD),
InvoiceNo shall be: 15100002,15100002,15100002,15100002,15100002,15100002,15100002,15100002
"InvoiceNo" column data will always be unique for different "Business Unit".
I want output to be like below snapshot:
I can autogenerate the numbers serial wise but I am not getting how to autogenerate the 2 column values based on Group and show them like
that.
Please help me to achieve it. Please do reply.
Thanks in advance.
Edit:
Below is the Stored Procedure I am using to generate autogenerated numbers in RadGrid's 2 column's:
ALTER PROCEDURE [dbo].[SDM_Assign_RunningNo]
-- Add the parameters for the stored procedure here
#TableName as nvarchar(50),
#NewID as nvarchar(50) OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #rn_year as nvarchar(50)
--Get Year From table
SELECT #rn_year =RNYear FROM dbo.SDM_Tran_RunningNo
WHERE RNYear= YEAR(GetDate())
--get last 2 digits of year
Declare #2digit_rn_year as nvarchar(50)
SELECT #2digit_rn_year = RNYear % 100 FROM dbo.SDM_Tran_RunningNo
WHERE RNYear= YEAR(GetDate())
IF #TableName='SDM_Tran_GenerateInvoice_No'
BEGIN
SELECT #NewID=Next_InvoiceNo FROM dbo.SDM_Tran_RunningNo
WHERE RNYear=#rn_year
UPDATE dbo.SDM_Tran_RunningNo
SET Next_InvoiceNo=Next_InvoiceNo+1
WHERE RNYear=#rn_year
SET #NewID = #2digit_rn_year +'1'+RIGHT('000000' + CAST(#NewID as varchar(10)), 5)
END
ELSE IF #TableName='SDM_Tran_GenerateInvoice_LineNo'
BEGIN
SELECT #NewID=Next_InvoiceLineNo FROM dbo.SDM_Tran_RunningNo
WHERE RNYear=#rn_year
UPDATE dbo.SDM_Tran_RunningNo
SET Next_InvoiceLineNo=Next_InvoiceLineNo+1
WHERE RNYear=#rn_year
SET #NewID = RIGHT('000000' + CAST(#NewID as varchar(10)), 2)
END
END
And then inserting the 2 column values into Table as below (using Stored Procedure),
to display it in RadGrid:
DECLARE #InvoiceNo as nvarchar(50)
--SP to generate new Invoice No
EXEC dbo.SDM_Assign_RunningNo
#TableName='SDM_Tran_GenerateInvoice_No',
#NewID = #InvoiceNo OUTPUT
DECLARE #InvoiceLineNo as nvarchar(50)
--SP to generate new Invoice Line No
EXEC dbo.SDM_Assign_RunningNo
#TableName='SDM_Tran_GenerateInvoice_LineNo',
#NewID = #InvoiceLineNo OUTPUT
INSERT INTO SDM_Tran_GenerateInvoice
VALUES (#InvoiceID,
#SPfoID,
#InvoiceLineNo, #InvoiceNo, #InvoiceType,
#BillingIDfoID, #BusinessUnit, #DirectCost,
#Status, GETDATE(), #AccountCode)
This is a concept... you might need to modify it to suit your requirement.
DECLARE #Temp TABLE (
InvoiceID nvarchar(50),
SPfoID nvarchar(50),
InvoiceLineNo nvarchar(50),
InvoiceNo nvarchar(50),
InvoiceType nvarchar(50),
BillingIDfoID nvarchar(50),
BusinessUnit nvarchar(2000),
DirectCost nvarchar(2000),
Status nvarchar(10),
Date datetime,
AccountCode nvarchar(1000)
)
DECLARE #Temp1 TABLE (
OrderID INT IDENTITY, -- Added This so It Will follow this new Identity Row
InvoiceID nvarchar(50),
SPfoID nvarchar(50),
InvoiceLineNo nvarchar(50),
InvoiceNo nvarchar(50),
InvoiceType nvarchar(50),
BillingIDfoID nvarchar(50),
BusinessUnit nvarchar(2000),
DirectCost nvarchar(2000),
Status nvarchar(10),
Date datetime,
AccountCode nvarchar(1000)
)
DECLARE #CompanyValue nvarchar(2000) = '' --BusinessUnit datatype
DECLARE #Counter nvarchar(50) = '0' --InvoiceNo datatype
DECLARE #InvoiceLine INT = 1
DECLARE #Year INT = YEAR(GETDATE())
DECLARE #ShortYear VARCHAR(2) = SUBSTRING(CONVERT(VARCHAR(4), #Year), 3, 2)
EXEC dbo._RunningNo
#TableName='Invoice',
#NewID = InvoiceID --OUTPUT
INSERT INTO #Temp (InvoiceID, SPfoID, InvoiceType, BillingIDfoID, BusinessUnit, DirectCost, Status, Date, AccountCode)
SELECT InvoiceID, SPfoID, InvoiceType, BillingIDfoID, BusinessUnit, DirectCost, Status, Date, AccountCode FROM [MainTable] ORDER BY BusinessUnit
INSERT INTO #Temp1
SELECT * FROM #Temp ORDER BY BusinessUnit
SELECT * FROM #Temp
SELECT * FROM #Temp1 -- before update
--Update #Temp1 table
UPDATE #Temp1
SET
#Counter = InvoiceNo = CASE WHEN #CompanyValue = '' OR #CompanyValue = BusinessUnit THEN (CONVERT(VARCHAR(100), CONVERT(INT,#Counter) + 1)) ELSE '1' END,
#InvoiceLine = CASE WHEN #CompanyValue = '' OR #CompanyValue = BusinessUnit THEN #InvoiceLine ELSE #InvoiceLine + 1 END,
#CompanyValue = BusinessUnit,
InvoiceLineNo = #ShortYear + '10000' + CONVERT(VARCHAR(3), #InvoiceLine)
SELECT * FROM #Temp1 --after update
--Update main table
UPDATE g
SET g.InvoiceLineNo = t.InvoiceLineNo,
g.InvoiceNo = t.InvoiceNo
FROM SDM_Tran_GenerateInvoice g
INNER JOIN #Temp1 t
ON g.InvoiceID = t.InvoiceID
Select * from [MainTable]
ORDER BY BusinessUnit;
I have an existing Stored procedure that generate employee ID. The employee ID have a format of EPXXXX, EP then 4 numeric values. I want my stored procedure to be shorten.
given the table (tblEmployee) above. Below is the stored procedure for inserting the new employee with the new employee number. The process is I have to get the last employee id, get the last 4 digits (which is the number), convert it to integer, add 1 to increment, check if the number is less than 10, 100 or 1000 or equal/greater than 1000, add the prefix before inserting the new records to the table.
create procedure NewEmployee
#EmployeeName VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON
DECLARE #lastEmpID as VARCHAR(6)
SET #lastEmpID =
(
SELECT TOP 1 Employee_ID
FROM tblEmployee
ORDER BY Employee_ID DESC
)
DECLARE #empID as VARCHAR(4)
SET #empID =
(
SELECT RIGHT(#lastEmpID, 4)
)
DECLARE #numEmpID as INT
#numEmpID =
(
SELECT CONVERT(INT, #empID) + 1
)
DECLARE #NewEmployeeID as VARCHAR(6)
IF #numEmp < 10
SET #NewEmployee = SELECT 'EP000' + CONVERT(#EmpID)
IF #numEmp < 100
SET #NewEmployee = SELECT 'EP00' + CONVERT(#EmpID)
IF #numEmp < 1000
SET #NewEmployee = SELECT 'EP0' + CONVERT(#EmpID)
IF #numEmp >= 1000
SET #NewEmployee = SELECT 'EP' + CONVERT(#EmpID)
INSERT INTO tblEmployee(Employee_ID, Name)
VALUES (#NewEmployeeID, #EmployeeName)
END
Try this one -
CREATE PROCEDURE dbo.NewEmployee
#EmployeeName VARCHAR(50)
AS BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tblEmployee(Employee_ID, Name)
SELECT
'EP' + RIGHT('0000' + CAST(Employee_ID + 1 AS VARCHAR(4)), 4)
, #EmployeeName
FROM (
SELECT TOP 1 Employee_ID = CAST(RIGHT(Employee_ID, 4) AS INT)
FROM dbo.tblEmployee
ORDER BY Employee_ID DESC
) t
END
I'm not suggesting over what you have currently but, i'd do this way. This is the way I've implemented in my application. Which im gonna give you. Hope you Like this. This is fully Dynamic and Works for all the Transaction you could have.
I've a table Which hold the Document Number as :
CREATE TABLE INV_DOC_FORMAT(
DOC_CODE VARCHAR(10),
DOC_NAME VARCHAR(100),
PREFIX VARCHAR(10),
SUFFIX VARCHAR(10),
[LENGTH] INT,
[CURRENT] INT
)
Which would hold the Data Like :
INSERT INTO INV_DOC_FORMAT(DOC_CODE,DOC_NAME,PREFIX,SUFFIX,[LENGTH],[CURRENT])
VALUES('01','INV_UNIT','U','',5,0)
INSERT INTO INV_DOC_FORMAT(DOC_CODE,DOC_NAME,PREFIX,SUFFIX,[LENGTH],[CURRENT])
VALUES('02','INV_UNIT_GROUP','UG','',5,0)
And, i'd have a fUNCTION OR Procedure but, i've an function here Which would generate the Document Number.
CREATE FUNCTION GET_DOC_FORMAT(#DOC_CODE VARCHAR(100))RETURNS VARCHAR(100)
AS
BEGIN
DECLARE #PRE VARCHAR(10)
DECLARE #SUF VARCHAR(10)
DECLARE #LENTH INT
DECLARE #CURRENT INT
DECLARE #FORMAT VARCHAR(100)
DECLARE #REPEAT VARCHAR(10)
IF NOT EXISTS(SELECT DOC_CODE FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE)
RETURN ''
SELECT #PRE= PREFIX FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #SUF= SUFFIX FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #LENTH= [LENGTH] FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #CURRENT= [CURRENT] FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SET #REPEAT=REPLICATE('0',(#LENTH-LEN(CONVERT(VARCHAR, #CURRENT))))
SET #FORMAT=#PRE + #REPEAT +CONVERT(VARCHAR, #CURRENT+1) + #SUF
RETURN #FORMAT
END
You can use the Function like :
INSERT INTO INV_UNIT(UNIT_CODE,UNIT_NAME,UNIT_ALIAS,APPROVED,APPROVED_USER_ID,APPROVED_DATE)
VALUES(DBO.GET_DOC_FORMAT('01'),#Unit_Name,#Unit_Alias,#APPROVED,#APPROVED_USER_ID,#APPROVED_DATE)
--After Transaction Successfully complete, You can
UPDATE INV_DOC_FORMAT SET [CURRENT]=[CURRENT]+1 WHERE DOC_CODE='01'
Or, you can create an Single Procedure which would handle all the things alone too.
Hope you got the way...
Hence,
Looking at your Way, you are making an Mistake.
You are getting
SET #lastEmpID =
(
SELECT TOP 1 Employee_ID
FROM tblEmployee
ORDER BY Employee_ID DESC
)
Last employee id, and then you are manipulating the rest of the ID. This would create or reuse the ID that was generated earlier however deleted now.
Suppose EMP0010 was there. After some day that EMP has been Deleted. So, When you again create an Employeee next time, You gonna have Same Emp ID you had before for anohter Employe but no more exits however. I dont think thats a good idea.
And, Instead of this :
DECLARE #NewEmployeeID as VARCHAR(6)
IF #numEmp < 10
SET #NewEmployee = SELECT 'EP000' + CONVERT(#EmpID)
IF #numEmp < 100
SET #NewEmployee = SELECT 'EP00' + CONVERT(#EmpID)
IF #numEmp < 1000
SET #NewEmployee = SELECT 'EP0' + CONVERT(#EmpID)
IF #numEmp >= 1000
SET #NewEmployee = SELECT 'EP' + CONVERT(#EmpID)
Which you used to repeat an Zero. You would use Replicate Function() of SQL. Like above on the Example of Mine.
SET #REPEAT=REPLICATE('0',(#LENTH-LEN(CONVERT(VARCHAR, #CURRENT))))
I don't think you need a Stored Procedure , Try using Ranking Functions
select
'EP'+RIGHT('000000'+ CAST(ROW_NUMBER() OVER (ORDER BY Name) AS VARCHAR(6)), 4)
AS [emp_code]
,
Name
FROM emp1 WITH(NOLOCK)
SQL Fiddle
EDIT
select
'EP'+RIGHT('000000'+ CAST((ROW_NUMBER() OVER (ORDER BY Name)+10) AS VARCHAR(6)), 4)
AS [emp_code] --^Add the last Emp no.
,
Name
FROM emp1 WITH(NOLOCK)
SQL Fiddle
of course the accepted answer is working fine, but it is not working if we have numm in previous values. so modified it as below, hope this will help others as well
CREATE PROCEDURE dbo.NewEmployee
#EmployeeName VARCHAR(50)
AS BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tblEmployee(Employee_ID, Name)
SELECT
'EP' + RIGHT('0000' + CAST(Employee_ID + 1 AS VARCHAR(4)), 4)
, #EmployeeName
FROM (
SELECT TOP 1 Employee_ID = CAST(RIGHT(Employee_ID, 4) AS INT)
FROM dbo.tblEmployee
ORDER BY Employee_ID DESC
) t
END