Syntax error in ALTER PROCEDURE in a DB update script - sql

I have a script to update my database for a new release of my web app . In this update i need to alter a strored procedure. I have an ALTER PROCEDURE script that works fine when run on its own, however when I drop it into my update script and run it I get the errors "Incorrect syntax near the keyword 'PROCEDURE'." and "Must declare the scalar variable "#age"." What am I doing wrong here? The script is as follows:
BEGIN TRY
BEGIN TRANSACTION
-- ==================================================================
-- v0.1 to v0.2
-- ==================================================================
IF EXISTS
(
SELECT * FROM SystemParameters WHERE Name = 'Version' AND Value = '0.1'
)
BEGIN
-- ==============================================================
-- Changed Stored Procedures
-- ==============================================================
ALTER PROCEDURE ClearCache
#age int = 120
AS
BEGIN
DECLARE #timestamp DATETIME
SELECT #timestamp = DATEADD(MINUTE, -#age, GETDATE())
-- Clear old searches
END
-- ==============================================================
-- Update the Version Number
-- ==============================================================
UPDATE SystemParameters SET Value = '0.2' WHERE Name = 'Version'
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
-- Report the Error
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
Any help would be appreciated :)

I doubt you can alter the procedure inside the IF clause
Try using it as a dynamic SQL
EXEC
(
'ALTER PROCEDURE ClearCache
#age int = 120
AS
BEGIN
DECLARE #timestamp DATETIME
SELECT #timestamp = DATEADD(MINUTE, -#age, GETDATE())
-- Clear old searches
END
'
)

Related

How to use results from an SQL Query as a list to delete (WSUS) updates

My problem is that I want to use the results from a SELECT query as the input values for a Stored Procedure. The issue is that the SP will only accept Scalar values, and I do not know SQL and so have been struggling to find a workaround or solution.
I want to modify the following Proc to accept multiple values to be used within the query:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spDeleteUpdateByUpdateID]
#updateID UNIQUEIDENTIFIER
AS
SET NOCOUNT ON
DECLARE #localUpdateID INT
SET #localUpdateID = NULL
SELECT #localUpdateID = LocalUpdateID FROM dbo.tbUpdate WHERE UpdateID = #updateID
IF #localUpdateID IS NULL
BEGIN
RAISERROR('The update could not be found.', 16, 40)
RETURN(1)
END
IF EXISTS (SELECT r.RevisionID FROM dbo.tbRevision r
WHERE r.LocalUpdateID = #localUpdateID
AND (EXISTS (SELECT * FROM dbo.tbBundleDependency WHERE BundledRevisionID = r.RevisionID)
OR EXISTS (SELECT * FROM dbo.tbPrerequisiteDependency WHERE PrerequisiteRevisionID = r.RevisionID)))
BEGIN
RAISERROR('The update cannot be deleted as it is still referenced by other update(s).', 16, 45)
RETURN(1)
END
DECLARE #retcode INT
EXEC #retcode = dbo.spDeleteUpdate #localUpdateID
IF ##ERROR <> 0 OR #retcode <> 0
BEGIN
RAISERROR('spDeleteUpdateByUpdateID got error from spDeleteUpdate', 16, -1)
RETURN(1)
END
RETURN (0)
TLDR: if anyone knows a quick way for me to use the results from SELECT UpdateID FROM tbUpdate WHERE UpdateTypeID = 'D2CB599A-FA9F-4AE9-B346-94AD54EE0629' to run exec spDeleteUpdateByUpdateID #updateID= i'd be extremely grateful.
There are some examples online of people using cursors to clean up WSUS. It will be slow but you are presumably only running it once. As mentioned there are other strategies for WSUS cleanup that should probably be investigated first.
DECLARE #var1 INT
DECLARE #msg nvarchar(100)
-- Get obsolete updates into temporary table
-- insert your own ID's here if you wish
CREATE TABLE #results (Col1 INT)
INSERT INTO #results(Col1) EXEC spGetObsoleteUpdatesToCleanup
DECLARE WC Cursor
FOR SELECT Col1 FROM #results
OPEN WC
FETCH NEXT FROM WC INTO #var1
WHILE (##FETCH_STATUS > -1)
BEGIN
SET #msg = 'Deleting' + CONVERT(varchar(10), #var1)
RAISERROR(#msg,0,1) WITH NOWAIT EXEC spDeleteUpdateByUpdateId #var1
FETCH NEXT FROM WC INTO #var1
END
CLOSE WC
DEALLOCATE WC
DROP TABLE #results

SQL: Set variable to a cell value inside a stored procedure

I am trying to store a single cell value from one table in a variable (inside a stored procedure), so I can use it to edit a value in another table, but I keep getting a MSG 201:
Procedure or function 'spBookReturn' expects parameter '#bookID', which was not supplied.
Every time I try to run the sp where it all should happen:
CREATE PROC spBookReturn
#loanID UNIQUEIDENTIFIER,
#bookID UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
BEGIN TRANSACTION tBookReturn
UPDATE BorrowedMaterial SET returned = 1, returnedDate = GETDATE();
SET #bookID = (SELECT TOP 1 bookID FROM BorrowedMaterial WHERE loanID = #loanID ORBER BY returnedDate);
UPDATE Books SET nHome = nHome + 1 WHERE ID = #bookID;
COMMIT TRANSACTION tBookReturn;
END;
EXEC spBookReturn '546A444A-3D8D-412E-876D-2053D575B54F'
Does anyone know why the way I have defined the #bookID variable doesn't work and how I can make it work?
Thanks.
EDIT:
I got two tables: BorrowedMaterial that includes the attributes loanID, bookID, returned, returnedDate and a few others that's not relevant.
The other table is Books and it includes bookID, nHome but not the loanID.
So by giving only the loanID as an input, I would like to update the nHome. I am trying to grab bookID since it is the only thing the two attributes got in common and this is where the issues happen.
Side note: I removed the variable #custID it spawned by mistake.
All parameters for a procedure are Input parameters. If you declare a parameter as an OUTPUT parameter, it is still an input one, and if it doesn't have a default value must be supplied.
If you want the OUTPUT parameters to be option, which I personally find can be quite often, then give them a default value. I also add some additional logic to your procedure, as you should be using an TRY...CATCH and an ORDER BY in your query with a TOP.
CREATE PROC dbo.spBookReturn #loanID uniqueidentifier,
#bookID uniqueidentifier = NULL OUTPUT,
#custID uniqueidentifier = NULL OUTPUT
AS
BEGIN
BEGIN TRY --If you are using tranasctions, make sure you have a ROLLBACK and THROW for errors
BEGIN TRANSACTION tBookReturn
UPDATE BorrowedMaterial
SET returned = 1,
returnedDate = GETDATE()
WHERE loanID = #loanID;
/*
UPDATE BorrowedMaterial
SET returnedDate = GETDATE()
WHERE loanID = #loanID;
*/
SET #bookID = (SELECT TOP 1 bookID
FROM BorrowedMaterial
WHERE loanID = #loanID
ORDER BY ???); --A TOP should have an ORDER BY
UPDATE Books
SET nHome = nHome + 1
WHERE ID = #bookID;
COMMIT TRANSACTION tBookReturn;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION tBookReturn;
THROW;
END CATCH;
END;
Then you can execute the procedure as you have, without #bookID and #custID being passed. Of course, if you don't, their values will be "lost" in the calling statement. If you need them, then pass their values too in the EXEC:
DECLARE #bookID uniqueidentifier, #CustID uniqueidentifier;
EXEC dbo.spBookReturn #loanID, #bookID OUTPUT, #CustID OUTPUT;
--SELECT #bookID, #CustID;
declare #bookID UNIQUEIDENTIFIER;
declare #custID UNIQUEIDENTIFIER;
declare #InResult;
exec #InResult= spBookReturn '546A444A-3D8D-412E-876D-2053D575B54F',#bookID,#custID;
select #bookID,#custID;
Seems, you need something like this
My take on what you're looking for. Because there are multiple DML statements (2 Updates) in a single transaction the XACT_ABORT ON option ensures a complete rollback. Also, the THROW in the CATCH block is placed before the ROLLBACK to preserve the SQL generated error metadata. Prior to executing the proc the OUTPUT variables are declared and placed in the parameter list (this omission is what was causing the initial error).
drop proc if exists dbo.spBookReturn;
go
CREATE PROC dbo.spBookReturn
#loanID uniqueidentifier,
#bookID uniqueidentifier OUTPUT,
#custID uniqueidentifier OUTPUT
AS
set nocount on;
set xact_abort on;
BEGIN TRANSACTION tBookReturn
BEGIN TRY
declare #ins_bm table(book_id uniqueidentifier,
cust_id uniqueidentifier);
UPDATE BorrowedMaterial
SET returned = 1,
returnedDate = GETDATE()
output inserted.book_id, inserted.cust_id into #ins_bm
WHERE loanID = #loanID;
if ##rowcount=0
begin
select #bookID=0,
#custID=0;
throw 50000, 'No update performed', 1;
end
else
begin
UPDATE b
SET nHome = nHome + 1
from Books b
WHERE exists (select 1
from #ins_bm bm
where b.ID = bm.book_id);
select top(1) #bookID=book_id,
#custID=cust_id
from #ins_bm;
end
COMMIT TRANSACTION tBookReturn;
END TRY
BEGIN CATCH
THROW
ROLLBACK TRANSACTION tBookReturn;
END CATCH;
go
declare #bookID UNIQUEIDENTIFIER;
declare #custID UNIQUEIDENTIFIER;
declare #InResult int;
exec #InResult=spBookReturn '546A444A-3D8D-412E-876D-2053D575B54F', #bookID output, #custID output;
select #bookID, #custID;
Thanks to #Larnu I figured out the only thing missing was a = on line 3 (and thanks to #charlieface I also got my code cleaned up a tiny bit):
CREATE PROC spBookReturn
#loanID UNIQUEIDENTIFIER,
#bookID UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
BEGIN TRANSACTION tBookReturn
UPDATE BorrowedMaterial SET returned = 1, returnedDate = GETDATE();
SET #bookID = (SELECT TOP 1 bookID FROM BorrowedMaterial WHERE loanID = #loanID
ORDER BY returnedDate);
UPDATE Books SET nHome = nHome + 1 WHERE ID = #bookID;
COMMIT TRANSACTION tBookReturn;
END;
EXEC spBookReturn '546A444A-3D8D-412E-876D-2053D575B54F'

dynamic SQL : use variable declared in a string, outside of the string

I have a stored procedure which should calculate one's age.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter PROCEDURE [dbo].[ageTest]
(
#curDate date
)
AS
BEGIN
SET NOCOUNT ON;
declare #sql nvarchar(max)
declare #params nvarchar (1000)
declare #age date
set #params = N' #curDate date , #age date output'
set #sql = '
declare #dif float
declare #ageRound int
declare #theAge varchar (100)
set #age = ''19890406''
set #theAge =(select (datediff (mm, #age , getdate())))
set #dif = #theAge % 12
set #ageRound = cast (#theAge as float)/12
select #ageRound as Years, #dif as months
'
set #sql = replace (#sql, '19890406', #curDate)
execute sp_executesql #sql, #params, #curDate, #age output
end
execute [dbo].[ageTest] '19511214'
What I want to obtain is two columns:
Years Months
63 10
Right now it looks like this:
The problem is it loops. I should probably remove the select from #sql and put it outside, then I have the declaration problem. Thoughts?
Edit: not a duplicate
IF you're really only a beginner.
First of all, you should always be using TRY...CATCH block in your procedures.
Here's a quick rewrite of what you've done in your code:
-- =============================================
-- Procedure Name : dbo.ageTest
-- Usage Example : EXECUTE [dbo].[ageTest] '19511214';
-- =============================================
ALTER PROCEDURE [dbo].[ageTest]
(
#curDate DATE
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT DATEDIFF(MM, #curDate, CURRENT_TIMESTAMP) / 12 AS Years
, DATEDIFF(MM, #curDate, CURRENT_TIMESTAMP) % 12 AS Months;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
It shows how you should be using parameters in SP.

Finding out the type of value passed is select statement or csv without using SQL predefined keyword

Please provide help in this created procedure which when executes returns result as the value passed to parameter it is csv or it is select.
Here I used select in like statement to find out what value is passed. there is any way in which without using any sql keyword I can identified whether passed parameter value is csv or it is select
Create procedure TypeOfvalue
(#abc varchar(max))
as
begin
IF (#abc like '%select%')
begin
Print 'It is select'
End
Else
Begin
Print 'It is CSV'
End
First:
Declare #abc varchar(max)
set #abc='1'
execute TypeOfvalue #abc
Output: "It is CSV"
Second:
Declare #abc varchar(max)
set #abc='select * from tbl1'
execute TypeOfvalue #abc
Output: "It is a Select"
You can use the PARSEONLY in combination with NOEXEC options for this. See this code example:
CREATE PROCEDURE #TypeOfvalue (#code nvarchar(max)) AS
BEGIN
DECLARE #result int
BEGIN TRY
EXEC(N'SET PARSEONLY ON;SET NOEXEC ON;'+#code+N';SET NOEXEC OFF;SET PARSEONLY OFF;')
PRINT(N'Statement')
SET #result = 1
END TRY
BEGIN CATCH
PRINT(N'CSV')
SET #result = 2
END CATCH
RETURN #result
END
GO
DECLARE #returnCode int
EXECUTE #returnCode = #TypeOfvalue #code = N'abc;'
SELECT #returnCode as returned -- 2 (CSV)
EXECUTE #returnCode = #TypeOfvalue #code = N'SELECT * FROM sys.all_objects'
SELECT #returnCode as returned
DROP PROCEDURE #TypeOfvalue -- 1 (SELECT)
Source
CREATE PROC TestProc
#SQL NVARCHAR(MAX)
AS
BEGIN
IF EXists (
SELECT 1 FROM sys.dm_exec_describe_first_result_set(#SQL, NULL, 0)
WHERE error_message IS NOT NULL
AND error_number IS NOT NULL
AND error_severity IS NOT NULL
AND error_state IS NOT NULL
AND error_type IS NOT NULL
AND error_type_desc IS NOT NULL )
BEGIN
Print 'It is CSV'
END
ELSE
BEGIN
Print 'It is Select'
END
END
GO
Note that this is check the validation it means that a valid SELECT statement not any input that have a SELECT keyword.
And also this will say input is valid for other valid statements like UPDATE, INSERT and etc.

SQL Stored Procedure not handling error from nested stored procedure

I have two stored procedures, one nested inside the other. When the nested stored procedure is called, at the moment, it should error with a foreign key constraint violation and then rollback the earlier call to insert into the ProductLicense table. The nested procedure does not perform any action on the database because of the foreign key violation but the calling stored procedure isn't catching the error and rolling back. If I execute the nested stored procedure by itself it does return error 547 Foreign key violation.
How can I get the two stored procedures to work together?
Outer procedure:
ALTER PROCEDURE [dbo].[AddNewLicense2_i]
-- Add the parameters for the stored procedure here
#customerId nvarchar(10),
#licenseModeId int,
#licenseModeProgramId int,
#createdBy int,
#updateBy int,
#systemId nvarchar(50),
#productId int
AS
BEGIN TRY
BEGIN TRANSACTION
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET XACT_ABORT ON; --used for automatic rollback when an error occurs
DECLARE #tempDays INT
DECLARE #programCornerAmt INT
DECLARE #tempEndDate DATETIME
DECLARE #tempExpDate DATETIME
DECLARE #err INT
SET #err = 0
/*SET #tempDays = (SELECT lmp.TimeoutDays
FROM LicenseModeProgram lmp
WHERE lmp.LicenseModeProgramId = #licenseModeProgramId)*/
SELECT #tempDays = TimeoutDays, #programCornerAmt = MonthlyCornersAmount
FROM LicenseModeProgram
WHERE LicenseModeProgramId = #licenseModeProgramId
--Build Expiration and End Dates.
IF #tempDays = NULL --then this is NOT a time rental or metered system
BEGIN
SET #tempEndDate = NULL
SET #tempExpDate = NULL
END
ELSE
BEGIN
SET #tempEndDate = DATEADD("d", #tempDays, GETDATE())
SET #tempExpDate = DATEADD("d", #tempDays, GETDATE())
END
-- Create new product license record
INSERT INTO ProductLicense (CustomerId, LicenseModeId, LicenseModeProgramId, CreatedBy, UpdatedBy, SystemId, ProductId, ExpirationDate, LicenseEndDate)
VALUES (#customerId, #licenseModeId, #licenseModeProgramId, #createdBy, #updateBy, #systemId, #productId, #tempExpDate, #tempEndDate)
IF #licenseModeId = 4 AND #systemId NULL AND #programCornerAmt NULL
--call stored procedure to add corners to the customer account
EXECUTE #err = AddMeteredTx_i #systemId, 1, 1, #programCornerAmt , 'Initial License Creation'
PRINT #err
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('Failed to Create License', 11, 2)
ROLLBACK TRANSACTION
RETURN 1
END CATCH
--COMMIT TRANSACTION
RETURN 0
GO
Inner procedure:
ALTER PROCEDURE [dbo].[AddMeteredTx_i]
-- Add the parameters for the stored procedure here
#systemId nvarchar(50),
#activityEventId int,
#createdBy int,
#amount int,
#notes text
AS
BEGIN TRY
BEGIN TRANSACTION
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET XACT_ABORT ON; --used for automatic rollback when an error occurs
INSERT INTO CustomerAccountActivity (SystemId, ActivityEventId, CreatedBy, Amount, Notes)
VALUES (#systemId, #activityEventId, #createdBy, #amount, #notes)
UPDATE CustomerAccount
SET MeteredBalance = (SELECT MeteredBalance FROM CustomerAccount WHERE SystemId = #systemId) + #amount
WHERE SystemId = #systemId
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('Error Update to Customer Account Record ', 11, 2)
ROLLBACK TRANSACTION
RETURN 1
--COMMIT TRANSACTION
END CATCH
RETURN 0
GO
Catching errors with a call stack like this using ##Error can be problematic. It's a lot more reliable to use TRY/CATCH
The basic format is:
BEGIN TRY
<BEGIN TRAN>
... do stuff ...
<COMMIT TRAN>
END TRY
BEGIN CATCH
<ROLLBACK TRAN>
... do error stuff like re-raise the error to outer scope ...
END CATCH
Any error encountered in the try will automatically take you to the CATCH block without additional checking.