Salary PAYE brackets using a stored procedure - sql-server

I am trying to insert into an Employee table and I want the if statements to insert into the Salary, NetSalary, and Deduction columns if the Employee will earn a certain amount based on the hourly rate.
If statements are meant to insert data into the relevant column when the amount falls into a particular bracket
ALTER PROCEDURE [dbo].[spSaveEmployee]
(#EmployeePersonName Varchar(20),
#JobTitle int,
#Branch int,
#Department int,
#Manager int,
#HourlyRate MONEY,
#PaymentDate datetime = '2021-10-05 00:00:00.000',
#ErrorMessage Varchar(50) OUTPUT)
AS
BEGIN
DECLARE #DefaultDate DATETIME = GETDATE()
--DECLARE #PAYE INT
--SET #PAYE = 26 / 100
DECLARE #Salary INT
--SET #Salary = #HourlyRate * 180
--DECLARE #PYE INT
--SET #PYE = 1.15
DECLARE #NetSalary MONEY
DECLARE #Deduction DECIMAL (18,2)
DECLARE #NetSalary1 MONEY
DECLARE #NetSalary2 MONEY
DECLARE #NetSalary3 MONEY
--SET #NetSalary = #Salary - #PYE
DECLARE #Deduction1 DECIMAL (18,2)
DECLARE #Deduction2 DECIMAL (18,2)
DECLARE #Deduction3 DECIMAL (18,2)
SET #Deduction1 = (#Salary * 18) / 100
SET #NetSalary1 = #Salary - #Deduction1
SET #Deduction2 = (#Salary * 26) / 100
SET #NetSalary2 = #Salary - #Deduction2
SET #Deduction3 = (#Salary * 31) / 100
SET #NetSalary3 = #Salary - #Deduction3
IF NOT EXISTS (SELECT * FROM EmployeeBook
WHERE EmployeePersonName = #EmployeePersonName)
BEGIN
INSERT INTO EmployeeBook (EmployeePersonName, JobTitle, Branch, Department,
Manager, HourlyRate, PAYE_Tax, Salary,
NetSalary, PaymentDate, Deductions)
VALUES (#EmployeePersonName, #JobTitle, #Branch, #Department,
#Manager, #HourlyRate, 1.15, #Salary,
#NetSalary, #DefaultDate, #Deduction)
SET #ErrorMessage = ''
IF #Salary <= 321000
BEGIN
SELECT #Deduction = #Deduction
SELECT #NetSalary = #NetSalary
END
ELSE IF #Salary >= 321001 AND #Salary <= 445100
BEGIN
SELECT #Deduction = #Deduction2
SELECT #NetSalary = #NetSalary2
END
ELSE IF #Salary >= 445100 AND #Salary <= 584200
BEGIN
SELECT #Deduction = #Deduction3
SELECT #NetSalary = #NetSalary3
END
END
ELSE
BEGIN
SET #EmployeePersonName = ''
SET #JobTitle = 0
SET #Branch = 0
SET #Department = 0
SET #Manager = 0
SET #HourlyRate = 0.00
SET #Salary = 0.00
SET #NetSalary = 0.00
SET #PaymentDate = #DefaultDate
SET #Deduction = 0
SET #ErrorMessage = 'Sorry could not save ' + #EmployeePersonName + ' Employee name already exists'
END
END
GO

The following does what I believe you are trying to accomplish. Issues and comments:
You set your variables after you use them - so they have no effect.
T-SQL is optimised for set-based operations therefore you should attempt to carry out your operations in a set-based manner if possible rather than procedural
Best practice is to use consistent casing for T-SQL
As Larnu points out, error handling is best handled using throw.
Best practice is to always schema prefix your database objects
SPs should always have a return statement
SPs should normally start SET NOCOUNT, XACT_ABORT ON;
Since you can validate the existence of the person straight away, I would and save any further processing time.
What if the salary falls outside the defined brackets? You might not expect it, but you should handle it hence the ELSE in the CASE expression.
I would avoid prefixing your SP's with sp as its too close to the system prefix sp_. In fact why prefix them at all?
When creating brackets like this, one end condition needs to be less than or equal and the other condition greater than (or vice-versa) to avoid a value falling into 2 brackets. Even though the case will short-circuit to the first matching expression, its best from an understanding perspective to have correct and accurate logic.
ALTER PROCEDURE [dbo].[SaveEmployee]
(
#EmployeePersonName VARCHAR(20),
#JobTitle INT,
#Branch INT,
#Department INT,
#Manager INT,
#HourlyRate MONEY,
#PaymentDate DATETIME = '2021-10-05 00:00:00.000',
#ErrorMessage VARCHAR(50) OUTPUT
)
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
IF EXISTS (SELECT 1 FROM dbo.EmployeeBook WHERE EmployeePersonName = #EmployeePersonName) BEGIN
SET #ErrorMessage = 'Sorry could not save ' + #EmployeePersonName + ' Employee name already exists' END
RETURN;
-- Comment out 2 lines above to uncomment the 2 code line below
-- As suggested by Larnu, this would be more a typical error handling approach
--DECLARE #Error NVARCHAR(2048) = 'Sorry could not save ' + #EmployeePersonName + ' Employee name already exists';
--THROW 51000, #Error, 1;
END;
DECLARE #DefaultDate DATETIME = GETDATE(), #Salary INT = #HourlyRate * 180;
INSERT INTO dbo.EmployeeBook (EmployeePersonName, JobTitle, Branch, Department
, Manager, HourlyRate, PAYE_Tax, Salary
, NetSalary, PaymentDate, Deductions)
SELECT EmployeePersonName, JobTitle, Branch, Department
, Manager, HourlyRate, 1.15, Salary
, CASE SalaryBracket WHEN 1 THEN Salary - Deduction1
WHEN 2 THEN Salary - Deduction2
WHEN 3 THEN Salary - Deduction3
-- ELSE ??
END
, #DefaultDate
, CASE SalaryBracket WHEN 1 THEN Deduction1
WHEN 2 THEN Deduction2
WHEN 3 THEN Deduction3
-- ELSE ??
END
FROM (
VALUES (#EmployeePersonName, #JobTitle, #Branch, #Department, #Manager, #HourlyRate, 1.15, #Salary
, (#Salary * 18) / 100, (#Salary * 26) / 100, (#Salary * 31) / 100
-- Compute Salary Bracket in one place for reuse
, CASE WHEN #Salary <= 321000 THEN 1
WHEN #Salary > 321001 AND #Salary <= 445100 THEN 2
WHEN #Salary > 445100 AND #Salary <= 584200 THEN 3 END
)
) AS X (EmployeePersonName, JobTitle, Branch, Department, Manager, HourlyRate, PAYE_Tax, Salary, Deduction1, Deduction2, Deduction3, SalaryBracket);
RETURN 0;
END
GO

Related

SQL SERVER 2016 -CAN execute SP with parameters inside a Function?

I have an SP which brings a table with some fields and subtracts the results to another table, I tried to do this as a function but as you know you can not use DML sentences in a function so I created this SP:
CREATE OR ALTER PROCEDURE SP_Integraciones_AsignarLotesLineas
#Cantidad numeric, #producto nvarchar(max), #bodega nvarchar(max)
AS
BEGIN
BEGIN TRY
BEGIN TRAN
DECLARE #temp as table(idLoteT nvarchar(max), CantidadT numeric, FechaT date)
DECLARE #loteAc nvarchar(Max), #CantidadAc numeric, #restante numeric , #Cont numeric, #fechaAc date, #resultado nvarchar(max)
SET #Cont = #Cantidad
DECLARE CursoIns CURSOR SCROLL
FOR SELECT IdLote, CONVERT(NUMERIC,cantidad), Fecha
FROM tblintegraciones_lotes
WHERE idproducto = #producto
AND Bodega = #bodega
AND cantidad > '0.0'
ORDER BY fecha ASC
OPEN CursoIns
FETCH CursoIns INTO #loteAc, #CantidadAc, #fechaAc
WHILE( #Cont > 0 AND ##FETCH_STATUS = 0 )
BEGIN
IF( #CantidadAc >= #Cont )
BEGIN
SET #restante = #CantidadAc- #Cont
INSERT INTO #temp(
idLoteT, CantidadT, FechaT
)VALUES(
#loteAc, #Cont, #fechaAc
)
UPDATE tblintegraciones_lotes SET Cantidad = #restante WHERE idProducto = #producto AND IdLote = #loteAc
SET #Cont = #cont - #CantidadAc
END
ELSE
BEGIN
SET #Cont = #Cont - #CantidadAc
SET #restante = #Cont - #CantidadAc
INSERT INTO #temp(
idLoteT, CantidadT, FechaT
)VALUES(
#loteAc, #CantidadAc, #fechaAc
)
UPDATE tblintegraciones_lotes SET Cantidad = 0 WHERE idProducto = #producto AND IdLote = #loteAc
END
FETCH CursoIns INTO #loteAc, #CantidadAc, #fechaAc
END
CLOSE CursoIns;
DEALLOCATE CursoIns;
SELECT * FROM #temp
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK
DECLARE #DescripcionError AS nvarchar(max)
SET #DescripcionError = ERROR_MESSAGE()
RAISERROR (#DescripcionError, 18, 1, 'Inventario - lotes - Integraciones', 5)
END CATCH
END
GO
but the problem is that I must run this in a select since I need that query parcing to JSON, so there should be 2 solutions:
Execute this SP in a select sentence
Create some function that calls the SP.
Can you help me how can I execute this SP in a function or find the best soluction?
Here the Query that I need execute this:
(SELECT CASE
WHEN MFAC.codigo IN ('ME-14009',
'ME-14010',
'ME-14011') THEN 'IMP-BOLSAP'
ELSE MFAC.codigo
END AS ItemCode,
REPLACE(MFAC.cantidad, ',', '.') AS Quantity,
CASE WHEN MFAC.codigo LIKE '%PV%' THEN MFAC.valor ELSE null END as Price,
REPLACE(MFAC.descuento, ',', '.') AS DiscountPercent,
CASE
WHEN MFAC.codbodega = 'PV-PER' THEN 'PV-DQU'
ELSE MFAC.codbodega
END AS WarehouseCode,
#OcrCode3 AS CostingCode3,
#OcrCode3 AS COGSCostingCode3
FOR JSON PATH
) DocumentLines

How to iterate over a string of varying length, replacing different abbreviations with their full text. All abbreviations separated by a semicolon

My problem is this; I have a field in a table that contains values like this:
NP
NP;MC;PE
MC;AB;AT;MI;TC;WM
OS
OG
I want to convert these abbreviations to their full name. i.e. NP becomes Nuclear Power, OG becomes Oil and Gas, MI becomes Military etc.
My desired output would be:
Nuclear Power
Nuclear Power;Military;Pesticides
and so on.
I'm creating this as a function. I got it working for just the one abbreviation and then the same for two. However my issue is that I may have 5 abbreviations or 7. I know my current approach is dreadful but cannot figure out how to loop it in the right way.
Please note: I've shortened the list of abbreviations for StackOverflow but there's 25 in total.
Please further note: I did the function bottom up (I don't know why) and got the two value and single value working. I've removed anything I did for values over 3 as nothing I did worked.
ALTER FUNCTION [dbo].[get_str_full]
(
-- Add the parameters for the function here
#str_input VARCHAR(250)
)
RETURNS VARCHAR(250)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result VARCHAR(250)
DECLARE #TEMPSTRING VARCHAR(250)
DECLARE #TEMPSTRING_RIGHT AS VARCHAR(250)
-- DECLARE #PI_COUNT BIGINT
DECLARE #COUNTER INT
DECLARE #TOTAL_VALS BIGINT
DECLARE #STRING_ST VARCHAR(250)
DECLARE #POS_STR BIGINT
DECLARE #REMAINING_STR VARCHAR(250)
-- Used for easy loop skips
DECLARE #LEFTSKIP AS BIGINT
SET #LEFTSKIP = 1
SET #Result = #str_input
SET #STRING_ST = #Result
SET #COUNTER = (LEN(#Result) - LEN(REPLACE(#Result,';',''))) + 1
SET #TOTAL_VALS = (LEN(#Result) - LEN(REPLACE(#Result,';',''))) + 1
-- If the string has a semicolon then there's more than one PI value
IF CHARINDEX(';', #Result) > 0
BEGIN
WHILE #COUNTER > 0
BEGIN
IF #TOTAL_VALS >= 3 -- If counter is more than 2 then there's three or more
BEGIN
DECLARE #TEMP_VAL BIGINT
SET #TEMP_VAL = 5
END
ELSE IF #TOTAL_VALS = 2-- Theres 2
BEGIN
-- Do left two chars first
IF #LEFTSKIP = 1
BEGIN
SET #TEMPSTRING = LEFT(#Result, 2)
SELECT #TEMPSTRING = CASE #TEMPSTRING
WHEN 'MC' THEN 'Military Contracting'
WHEN 'NP' THEN 'Nuclear'
WHEN 'OG' THEN 'Oil & Gas'
WHEN 'OS' THEN 'Oil Sands'
WHEN 'PM' THEN 'Palm Oil'
WHEN 'PE' THEN 'Pesticides'
ELSE #TEMPSTRING
END
SET #LEFTSKIP = 2
END
ELSE IF #LEFTSKIP = 2
BEGIN
SET #TEMPSTRING_RIGHT = RIGHT(#Result, 2)
SELECT #TEMPSTRING_RIGHT = CASE #TEMPSTRING_RIGHT
WHEN 'MC' THEN 'Military Contracting'
WHEN 'NP' THEN 'Nuclear'
WHEN 'OG' THEN 'Oil & Gas'
WHEN 'OS' THEN 'Oil Sands'
WHEN 'PM' THEN 'Palm Oil'
WHEN 'PE' THEN 'Pesticides'
ELSE #TEMPSTRING_RIGHT
END
END
END
SET #COUNTER = #COUNTER - 1
END
SET #Result = CONCAT(#TEMPSTRING,';', #TEMPSTRING_RIGHT)
END
ELSE
BEGIN
SET #Result = REPLACE(#Result, 'MC', 'Military Contracting')
SET #Result = REPLACE(#RESULT, 'NP', 'Nuclear Power')
SET #Result = REPLACE(#Result, 'OG', 'Oil & Gas')
SET #Result = REPLACE(#Result, 'OS', 'Oil Sands')
SET #Result = REPLACE(#Result, 'PM', 'Palm Oil')
SET #Result = REPLACE(#Result, 'PE', 'Pesticides')
END
-- Return the result of the function
RETURN #Result
END
First for some easily consumable sample data:
DECLARE #tranlation TABLE(tCode VARCHAR(10), tString VARCHAR(40));
DECLARE #t TABLE(String VARCHAR(1000));
INSERT #t VALUES('PE;N'),('NP'),('NP;MC;PE;XX')
INSERT #tranlation VALUES ('N','Nukes'),('NP','Nuclear Power'),('MC','Military'),
('PE','Pesticides');
Note my updated sample data which includes "XX", which has no match , and an "N" for "Nukes" which would wreck any solution which leverages REPLACE. If you are on SQL 2016+ you can use STRING_SPLIT and STRING_AGG.
SELECT
OldString = t.String,
NewString = STRING_AGG(ISNULL(tx.tString,items.[value]),';')
FROM #t AS t
OUTER APPLY STRING_SPLIT(t.String,';') AS items
LEFT JOIN #tranlation AS tx
ON items.[value] = tx.tCode
GROUP BY t.String ;
Returns:
OldString NewString
----------------- -------------------------------------------
NP Nuclear Power
NP;MC;PE;XX Nuclear Power;Military;Pesticides;XX
PE;N Pesticides;Nukes
You should really fix your table design so that you do not store multiple pieces of info in one column.
If you would like it as a function, I would strongly recommend an inline Table-Valued function rather than a scalar function.
If you have SQL Server version 2017+ you can use STRING_SPLIT and STRING_AGG for this.
CREATE OR ALTER FUNCTION GetFullStr
( #str varchar(250) )
RETURNS TABLE
AS RETURN
(
SELECT STRING_AGG(ISNULL(v.FullStr, s.value), ';') result
FROM STRING_SPLIT(#str, ';') s
LEFT JOIN (VALUES
('MC', 'Military Contracting'),
('NP', 'Nuclear'),
('OG', 'Oil & Gas'),
('OS', 'Oil Sands'),
('PM', 'Palm Oil'),
('PE', 'Pesticides')
) v(Abbr, FullStr) ON v.Abbr = s.value
);
GO
You can, and should, replace the VALUES with a real table.
On 2016 you would need FOR XML PATH instead of STRING_AGG:
CREATE OR ALTER FUNCTION GetFullStr
( #str varchar(250) )
RETURNS TABLE
AS RETURN
(
SELECT STUFF(
(SELECT ';' + ISNULL(v.FullStr, s.value)
FROM STRING_SPLIT(#str, ';') s
LEFT JOIN (VALUES
('MC', 'Military Contracting'),
('NP', 'Nuclear'),
('OG', 'Oil & Gas'),
('OS', 'Oil Sands'),
('PM', 'Palm Oil'),
('PE', 'Pesticides')
) v(Abbr, FullStr) ON v.Abbr = s.value
FOR XML PATH(''), TYPE
).value('text()[1]','varchar(2500)'),
, 1, 1, '')
);
GO
You use it like this:
SELECT s.result AS FullStr
FROM table
OUTER APPLY GetFullStr(value) AS s;
-- alternatively
SELECT (SELECT * FROM GetFullStr(value)) AS FullStr
FROM table;
You could assign your abbreviation mappings to a TABLE variable and then use that for your REPLACE. You could build this into a function, then pass your string values in.
The test below returns Military:Nuclear Power:XX.
declare #mapping table (abbrev varchar(50), fullname varchar(100))
insert into #mapping(abbrev, fullname)
values ('NP','Nuclear Power'),
('MC','Military')
declare #testString varchar(100), #newString varchar(100)
set #teststring = 'MC:NP:XX'
set #newString = #testString
SELECT #newString = REPLACE(#newString, abbrev, fullname) FROM #mapping
select #newString

Using a SP to retrieve information or generate it if data exists in table

I have a requirement in which I need to run a select statement for data in a table if that data exists I need to return the values in a few of the columns. If the data doesn't exist, I need to insert a new row and return the inserted data.
I'll be using an API to execute the stored procedure and return the data, and then write that to a machine via OPC.
What I'm struggling with currently, is that a new entry is created, but does not increment the two columns I need to increment by a digit. In the code below, a new entry will create, but still returns and inputs the same value as the previous lot number. Is there a better way to achieve what I'm wanting to do?
CREATE PROCEDURE [dbo].[Lotconfirmation]
#plcid nvarchar(6) OUTPUT,
#supplierlot nvarchar(25),
#internallotnum nvarchar(25)OUTPUT,
#plclotnum nvarchar(25) OUTPUT,
#suppliercode nvarchar(1),
#supplierpartnum nvarchar(25),
#suppliermodel nvarchar(25),
#qtyconsumed int,
#id int OUTPUT,
#errormsg nvarchar(max) OUTPUT,
#errornum int OUTPUT,
#errorproc nvarchar(max) OUTPUT,
#errorstate int OUTPUT,
#errorline int OUTPUT
AS
BEGIN
BEGIN TRY
/* Check if lot already exists */
SELECT
#internallotnum = InternalLotNum, #plcid = PLCID,
#plclotnum = plclotnum, #id = id
FROM
dbo.ShrinkLotData
WHERE
SupplierMfgLot = #supplierlot
IF #internallotnum IS NULL
BEGIN
DECLARE #table TABLE
(
plcid nvarchar(6),
internallotnum nvarchar(25),
plclotnum nvarchar(25),
id int
)
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table
END
END TRY
/* E-mail if errors occurs */
BEGIN CATCH
SET #errormsg = 'SP Failed with msg:' + ERROR_MESSAGE()
SET #errornum = ERROR_NUMBER()
SET #errorproc = ERROR_PROCEDURE()
SET #errorstate = ERROR_STATE()
SET #errorline = ERROR_LINE()
/* Place holder to insert fail data into a table
INSERT INTO KSError (datestamp, errormsg, errorproc, errorstate, errorline)
VALUES (#datestamp, #errormsg, #errornum, #errorproc, #errorstate, #errorline)
*/
EXECUTE msdb.dbo.sp_send_dbmail
#recipients = 'email#domain.com',
#profile_name = 'Profile Alert',
#subject = 'KepServer Stored Procedure:',
#body = #errormsg
END CATCH
END
GO
EDIT:
It seems to be working when I cast values as an integer, so I'll need to review those data types and probably just set them up as integers.
(SELECT MAX(CAST(InternalLotNum AS INT)) + 1 FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(CAST(InternalLotNum AS Int), 2) + 1) FROM dbo.ShrinkLotData),
While the question is still up, do you guys see a better / more efficient way to do what I'm hoping?
Thanks!
Your OUTPUT clause is returning the values directly to the client, instead of inserting them into your table variable. Should be something like:
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
INTO #table(plcid,InternalLotNum,PLCLotNum,ID)
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table

How to assign a variable to the execution of a stored procedure?

I want to assign the value returned from the sp_formCreateEventID stored procedure into a new variable (#eventId). I initially thought this was the way to go. This solution is also in line with the EXEC command generated by SSMS.
However, for some reason the EXEC line returns an INT from the stored procedure as expected, but when it cant' assigned it's value to the #eventId variable.
DECLARE #eventId INT
EXEC #eventId = sp_formCreateEventID #patientId, #programId, #clinicianId, #formId, #phaseTypeId, #draft, #dataCollectionDate, NULL
SELECT #eventId
sp_formCreateEventID (don't hate me, I didn't write this...):
ALTER PROCEDURE [dbo].[sp_formCreateEventID]
#PatientID int,
#ProgramID int,
#ClinicianID int,
#FormID int,
#PhaseTypeID int,
#Draft varchar(5),
#CompletedDate varchar(40),
#UserID int = null
AS
BEGIN
IF #CompletedDate = ''
SET #CompletedDate = NULL
--for some forms such as Clinical Input - Initial, there should only have one form filled for a youth. If that is the case and the event has existed, just return that one.
DECLARE #EID int
SET #EID = dbo.fn_GetExistingOnlyOneEventID(#PatientID, #ProgramID, #FormID)
PRINT #eid
IF #EID <> -99
BEGIN
SELECT
#EID AS 'EventID'
RETURN
END
DECLARE #TxCycleID int
DECLARE #TxCyclePhaseTypeID int
DECLARE #TxCyclePhaseID int
DECLARE #seqNum int
DECLARE #NewEventID INT
--if there is no cycle for this patient for this program, then create one.
IF NOT EXISTS (SELECT * FROM TxCycle WHERE PatientID = #PatientID AND ProgID = #ProgramID)
BEGIN
INSERT INTO TxCycle
(OpenDate, PatientID, ProgID)
VALUES
(GETDate(), #PatientID, #ProgramID)
END
SELECT
#TxCycleID = Max(TxCycleID)
FROM TxCycle
WHERE
PatientID = #PatientID AND
ProgID = #ProgramID
--In this cycle, for the current phase type, get the max seq number
IF EXISTS (SELECT * FROM TxCyclePhase WHERE TxCycle = #TxCycleID)
BEGIN
SELECT
#seqNum = MAX(SeqNum)
FROM
TxCyclePhase
WHERE
TxCycle = #TxCycleID
SET #seqNum = #seqNum + 1
END
ELSE
BEGIN
SET #seqNum = 1
END
PRINT 'New Seq Num: ' + CONVERT(Varchar(5),#seqNum)
--greate a new seq number under the same phase
INSERT INTO TxCyclePhase
(Type, seqNum, TxCycle)
VALUES
(#PhaseTypeID, #seqNum, #TxCycleID)
--get the new ID, this will be used for the Event
SELECT
#TxCyclePhaseID = Max(TxCyclePhaseID)
FROM
TxCyclePhase
DECLARE #isFinal int
IF #Draft = 'Yes'
BEGIN
SET #isFinal = 0
END
ELSE
BEGIN
SET #isFinal = 1
END
IF EXISTS(SELECT * FROM LoginPassword WHERE ClinID = #ClinicianID AND AccessID IN (1,3))
BEGIN
IF NOT EXISTS (SELECT * FROM ClinPat WHERE ClinID = #ClinicianID AND PatientID = #PatientID)
BEGIN
INSERT INTO
ClinPat
(ClinID, PatientID)
VALUES
(#ClinicianID, #PatientID)
END
END
INSERT INTO FormPat
(PatientID, ClinID, FormID, TxCyclePhase, Date, Final, DataCollectionDate)
VALUES
(#PatientID, #ClinicianID, #FormID, #TxCyclePhaseID, GETDATE(), #isFinal, #CompletedDate)
SELECT #NewEventID = Scope_Identity()
SELECT #NewEventID AS 'EventID'
What am I doing wrong?
You need a RETURN at the bottom of your procedure.
RETURN #NewEventID
Here is a complete but simple example:
CREATE PROCEDURE [dbo].[uspExampleOne]
#Parameter1 INT
AS
BEGIN
SET NOCOUNT ON
RETURN 333
SET NOCOUNT OFF
END
GO
and
Declare #MyValue INT
EXEC #MyValue = [dbo].[uspExampleOne] 111
SELECT '#MyValueHere' = #MyValue
Result:
#MyValueHere
333
But a better design IMHO is to use an OUTPUT variable:
Why?
What happens when you need a second OUTPUT? What happens when the needed value is not an INT?
ALTER PROCEDURE [dbo].[uspExampleOne]
#Parameter1 INT ,
#OutParameter2 INT OUT
AS
BEGIN
SET NOCOUNT ON
Select #OutParameter2 = 444
RETURN 333
SET NOCOUNT OFF
END
GO
and
Declare #MyValue INT
Declare #OutParameterTwo INT
EXEC #MyValue = [dbo].[uspExampleOne] 111 , #OutParameterTwo OUT
SELECT '#MyValueHere' = #MyValue
Select '#OutParameterTwoHere' = #OutParameterTwo
Output
#MyValueHere
333
#OutParameterTwoHere
444
Below shows what I mean about "future proofing" with OUTPUT parameters
ALTER PROCEDURE [dbo].[uspExampleOne]
#Parameter1 INT ,
#OutParameter2 INT OUT,
#OutParameter3 VARCHAR(128) OUT
AS
BEGIN
SET NOCOUNT ON
Select #OutParameter2 = 444
Select #OutParameter3 = 'Better Design With Output Parameters. Not stuck with 1 return-value or data-type'
RETURN 0 /* everything went ok */
SET NOCOUNT OFF
END
GO
and the call to it
Declare #MyValue INT
Declare #OutParameterTwo INT
Declare #OutParameterThree VARCHAR(128)
EXEC #MyValue = [dbo].[uspExampleOne] 111 , #OutParameterTwo OUT , #OutParameterThree OUT
SELECT '#MyValueHere' = #MyValue
Select '#OutParameterTwoHere' = #OutParameterTwo , '#OutParameterThreeHere' = #OutParameterThree
and output
#OutParameterTwoHere #OutParameterThreeHere
444 Better Design With Output Parameters. Not stuck with 1 return-value or data-type

Struggling to convert my sql query to be set based instead of using while loops

My code is:
Declare #Users table(Names nvarchar(50) not null, Flag int);
Declare #ValidUsers table(Names nvarchar(50) not null);
Declare #Office int;
Declare #NumberOfRecords int;
Declare #Count int;
Declare #IntCount int;
Declare #Binary AS nvarchar(16);
Declare #bit as nvarchar(1);
Declare #PermissionSub as nvarchar(1);
Declare #Permission as nvarchar(16);
Declare #ShouldContinue as bit;
set #ShouldContinue = 1;
set #Permission = '0001111111111111'; /* going to pass this value */
set #Count = '1';
set #IntCount = '1';
set #Office = '3'; /* going to pass this value */
Insert into #Users
Select
dbUser.usrFullName, udFeeEarnerLicence.purchaseFlag
From
[OMSBB].[dbo].[udFeeEarnerLicence]
Inner Join
[OMSBB].[dbo].[dbUser] ON udFeeEarnerLicence.feeUsrId = dbUser.usrID
Where
dbUser.brId = #Office;
select #NumberOfRecords = COUNT(Flag) from #Users;
DECLARE #Flag AS int;
select #Flag = Flag from #Users;
while(#Count <= #NumberOfRecords)
begin
WITH CTE AS
(
SELECT
Flag, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM
#Users
)
SELECT TOP(1) #Flag = Flag -- this TOP(1) is just a fail-safe
FROM CTE
WHERE RwNr = #Count;
WITH A AS
(
SELECT 0 AS ORD, #Flag AS NUMBER, CAST('' AS VARCHAR(20)) AS BITS
UNION ALL
SELECT ORD+1, NUMBER/2, CAST(BITS+CAST(NUMBER%2 AS VARCHAR(20)) AS VARCHAR(20))
FROM A
WHERE NUMBER > 0
)
SELECT #Binary = RIGHT('000000000000000'+ CASE WHEN BITS='' THEN '0' ELSE REVERSE(BITS) END,16)
FROM A
WHERE NUMBER = 0;
WHILE (#IntCount <= 16)
BEGIN
select #bit = SUBSTRING(#Binary, #IntCount, #IntCount + 1);
select #PermissionSub = SUBSTRING(#Permission, #IntCount, #IntCount + 1);
if(#PermissionSub = '1' and #bit != '1') /* if Permission selection is required and user does not have permission*/
begin
SET #ShouldContinue = 0;
break;
end
end
Set #IntCount = 0;
if(#ShouldContinue = 0)
begin
continue;
end
; WITH CTE AS
(
SELECT Names, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM #Users
)
INSERT INTO #ValidUsers
SELECT Names
FROM CTE
WHERE RwNr = #Count;
end
select * from #ValidUsers
I will be adapting this code to use it inside of an SSRS report so that's why there are comments on some parameters saying that I will be passing the parameters. This code at its basics finds all users who are from a specified office and have the specified permissions. The permission a user has are set in 5 flags in this example I'm using the purchaseFlag. This value is an int and it calculated by creating an order of permissions and set their bit values to create a string of 0's and 1's and then converting that binary number into a decimal for example '8191' which the binary value of would be '0001111111111111'. I use two while loops in this one to go through the users and the other to go through each of the 16 characters in the permissions. My issue is that this I'm almost certain that this query works but it takes so long to run that I haven't seen the result of it yet and people have recommended that I use sets instead.

Resources