Specifically, can I do this?
CREATE PROC AutoDestructiveStoredProcedure
AS
DROP PROC AutoDestructiveStoredProcedure
PRINT 'Still alive.'
GO
Is it a bad practice?
What is the expected behavior? Does it change based on implementation?
What would be the difference between executing this in SQL Server, MySQL and Oracle?
Yes, this will work -- at least in SQL Server 2008 R2. It continues executing until the end of the procedure and after that the procedure is gone.
Is it bad practice? I think so. In my mind, the main reason is that it mixes DDL with DML, imposing unexpected side effects on what is normally a well-understood operation (calling a stored procedure).
Unfortunately, I can't answer your question with respect to how it works on MySQL or Oracle.
Yes you can do this not sure why you would want to
CREATE PROC AutoDestructiveStoredProcedure
AS
PRINT 'Being killed'
DROP PROC AutoDestructiveStoredProcedure
PRINT 'Still alive.'
exec createAutoDestruct
print 'Alive Again'
GO
create proc createAutoDestruct as
Exec sp_executesql N'CREATE PROC AutoDestructiveStoredProcedure
AS
PRINT ''Being killed''
DROP PROC AutoDestructiveStoredProcedure
PRINT ''Still alive.''
exec createAutoDestruct
print ''Alive Again'' '
GO
AutoDestructiveStoredProcedure
AutoDestructiveStoredProcedure
I've gathered the following experimental evidence in Oracle. I created the following procedure:
create procedure selfdestruct is
begin
execute immediate 'drop procedure selfdestruct';
end selfdestruct;
Then, ran it using
exec selfdestruct;
The result appears to be a never-ending procedure. A brief look at the database activity reveals that the session is waiting on library cache pin, which appears to be the method that Oracle uses to prevent an procedural object from changing while it is in-use.
In brief, for Oracle, the answer is "No, you can't drop a procedure while it is running."
The reason is simple: I work on a database of which I am not the administrator and I do not want other people to see and edit my SP, so I create it at runtime from the program before running it and at the end I have to delete it in order not to leave it in the database.
you could use the self destruct to not forget objects on customers base
this will work in Microsoft SQL Server 2008 R2 (SP2) and on Oracle 11G(probaly 10g too)
on Microsoft SQL Server 2008 R2 (SP2)
CREATE PROC AutoDestructiveStoredProcedure
AS
PRINT 'Being killed'
DROP PROC AutoDestructiveStoredProcedure
PRINT 'Still alive.'
print 'Alive Again'
GO
select OBJECT_ID('AutoDestructiveStoredProcedure')
--1243151474
exec AutoDestructiveStoredProcedure
--Being killed
--Still alive.
--Alive Again
select OBJECT_ID('AutoDestructiveStoredProcedure')
--null
On oracle
SET serveroutput ON size unlimited;
CREATE OR REPLACE PROCEDURE selfdestruct
AS
v_plSqlBlock varchar2(500);
v_job_int binary_integer;
BEGIN
DBMS_OUTPUT.PUT_LINE('Being killed');
v_plSqlBlock :='
BEGIN
execute immediate ''drop procedure selfdestruct'';
END;
';
dbms_job.submit(
job => v_job_int,
what => v_plSqlBlock
);
commit;
DBMS_OUTPUT.PUT_LINE('Still alive.');
DBMS_OUTPUT.PUT_LINE('Alive Again');
END selfdestruct;
/
SELECT count(1)
FROM dba_procedures
WHERE object_name = upper('selfdestruct');
Related
I’m cleaning out a large database and am going to be removing a lot of stored procedures. I can be pretty certain but not 100% that the SP isn’t being used. How can I be alerted that something is calling a missing stored procedure, and throwing the error:
Could not find stored procedure ‘X’
You could create synonyms for each deleted procedure and substitute a different procedure (with matching parameters). In the example below the procedure doesn't take any parameters.
The code:
create "replacement" proc called 'dbo.test_replacement_proc'
exec 'dbo.test_replacement_proc'
create "test" proc called 'dbo.test_proc'
exec 'dbo.test_proc'
drop 'dbo.test_proc'
create synonym 'dbo.test_proc' for 'dbo.test_replacement_proc'
exec 'dbo.test_proc'
When #4 executes it's the original procedure. When #7 executes it's the replacement procedure.
drop proc if exists dbo.test_replacement_proc;
go
create proc dbo.test_replacement_proc
as
set nocount on;
select 'Could not find stored procedure' replacement_message
go
-- run the proc
exec dbo.test_replacement_proc;
-- Create proc
drop synonym if exists dbo.test_proc;
drop proc if exists dbo.test_proc;
go
create proc dbo.test_proc
as
set nocount on;
select 'This is the text' procedure_message
go
-- run the proc
exec dbo.test_proc;
-- drop procedure
drop proc if exists dbo.test_proc;
-- Create a synonym for the deleted procedure
create synonym dbo.test_proc
for dbo.test_replacement_proc;
go
-- run the proc (which is now a synonym for the replacement proc)
exec dbo.test_proc;
Output #2
replacement_message
Could not find stored procedure
Output #4
procedure_message
This is the text
output #7
replacement_message
Could not find stored procedure
Use SQL Server Agent to create an alert for error #2812.
You need to create an operator also, and then you can setup a notification for the alert.
See MSSQLTips on how to do it all.
how to have dynamically create & alter in the sql script?
Instead of having
if exits - drop
we are looking to have
if exits - alter.
How to handle such scenario.
To clarify my comments above, SQL Server 2016 SP1 released a CREATE OR ALTER statement that will either create an object that doesn't already exists or modify the object if it does. This is only allowed on certain objects such as stored procedures, triggers, functions, and views. Tables, indexes, and other objects that are allocated storage cannot be used in by the CREATE OR ALTER statement. Also note that since they're persisted on disk, indexes views are not permitted to be used by this. A basic example of the syntax is below.
CREATE OR ALTER PROCEDURE SP_TestStoredProcedure
AS
BEGIN
SELECT
Column1,
Column2,
Column3
FROM YourTable
END
Here is a trick I've used.
-- for testing, not needed for real -- DROP PROCEDURE dbo.uspDoSomething
GO
IF NOT EXISTS ( SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'PROCEDURE' and ROUTINE_NAME = 'uspDoSomething' )
BEGIN
EXEC ( 'CREATE PROCEDURE dbo.uspDoSomething(#i INT) AS BEGIN RAISERROR (''Stubbed version'' , 16, -1) END' )
END
GO
--test only
EXEC dbo.uspDoSomething 0
GO
ALTER PROCEDURE dbo.uspDoSomething(#PatientKey INT)
AS
BEGIN
SELECT ##VERSION
END
GO
--test only
EXEC dbo.uspDoSomething 0
GO
Remember, an ALTER does not change all the PERMISSIONS you have on the script.
A DROP/ADD needs permissions reapplied.
Note, you did not originally mention your sql-server version. This trick works with 2014 and before. Obviously, newer versions with CREATE OR ALTER would be preferred over EXEC with dynamic sql.
This question already has answers here:
How do I conditionally create a stored procedure in SQL Server?
(11 answers)
Closed 7 years ago.
I'm writing a db update script which basically retrieve the current version number from the database then create a number of stored procedure if the version is valid. If the current version does not match the expected version then it should skip executing the code.
However I run into a problem when I write the script because CREATE PROCEDURE has to be the first statement in a batch, so it's not possible for me to insert if .. else statement before the create procedure statement.
I've also tried using GOTO but to no avail because GOTO doesn't span across multiple batches. Same thing applies to RETURN and RAISEERROR - the rest of the code will still execute.
Sample script:
IF #Version = '1.0' --doesn't work
BEGIN
CREATE PROCEDURE dbo.uspCreateAccount
AS BEGIN
--The rest of the code goes here
END
END
Can anyone provide some insight on this?
You can accomplish this using the exec functionality.
IF #Version = '1.0'
BEGIN
--Drop it if it already exists
IF OBJECT_ID('uspCreateAccount', 'P') IS NOT NULL
EXEC ('DROP PROCEDURE uspCreateAccount')
--Recreate it.
EXEC('
CREATE PROCEDURE uspCreateAccount
AS BEGIN
--The rest of the code goes here
END
')
END
You can use exec to run a SQL command in a new scope. The following snippet will run even when dbo.YourProc already exists, because the SQL command inside exec() will never be parsed in that case.
if not exists (select * from sys.procedures where name = 'YourProc')
exec ('create procedure dbo.YourProc as select 1 as a')
go
alter procedure dbo.YourProc
as
select ...
This construct creates an empty stub procedure if the procedure does not exist. If the procedure exists, it runs alter. So it preserves rights that have been granted on the procedure.
I'm trying to execute a stored procedure directly after its creation however it is not getting called. It looks like the stored procedure is not yet created during the execution call.
Here is how the script looks like:
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
EXEC sp_Transfer_RegionData
The script runs fine however the needed table is not populated. After replacing the execution part with:
IF OBJECT_ID('sp_Transfer_RegionData') IS NOT NULL
begin
exec [dbo].[sp_Transfer_RegionData]
print 'tada'
end
I could see that the stored procedure does not exist when it has to be executed. Couldn't find a solution for this in the internet...
So how to make the SQL script run sync so that the stored procedure would already exist during the execution part?
You need a GO after you created the SP otherwise you have created a recursive SP that calls itself "indefinitely" which is 32 times in SQL Server.
Maximum stored procedure, function, trigger, or view nesting level
exceeded (limit 32).
Try this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
GO
EXEC sp_Transfer_RegionData
Is it possible to find out who called a stored procedure?
For example, say I get an error in proc3. From within that proc I want to know if it was called by proc1 or proc2.
I would use an extra input parameter, to specify the source, if this is important for your logic.
This will also make it easier to port your database to another platform, since you don't depend on some obscure platform dependent function.
There is no nice automatic way to do this (alas). So it really depends on how much you are prepared to (re)write your procs in order to be able to do this.
If you have a logging mechanism, you might be able to read the log and work out who called you.
For example, if you implement logging by inserting to a table, for example:
CREATE TABLE Log
(timestamp dattime,
spid int,
procname varchar(255),
message varchar(255) )
... text of proc ...
INSERT INTO Log
SELECT get_date(), ##spid, #currentproc, 'doing something'
-- you have to define #currentproc in each proc
-- get name of caller
SELECT #caller = procname
FROM Log
WHERE spid = ##spid
AND timestamp = (SELECT max(timestamp)
FROM Log
WHERE timestamp < get_date()
AND procname != #currentproc )
This wouldn't work for recursive calls, but perhaps someone can fix that?
Do you need to know in proc3 at runtime which caused the error, or do you just need to know while debugging?
You can use SQL Server profiler if you only need to do it during debugging/monitoring.
Otherwise in 2005 I don't believe you have the ability to stack trace.
To work around it you could add and extra parameter to proc3, #CallingProc or something like that.
OR you could add try catch blocks to proc1 and proc2.
BEGIN TRY
EXEC Proc3
END TRY
BEGIN CATCH
SELECT 'Error Caught'
SELECT
ERROR_PROCEDURE()
END CATCH
Good reference here : http://searchsqlserver.techtarget.com/tip/1,289483,sid87_gci1189087,00.html
and of course always SQL Server Books Online
SQL Server 2008 does have the ability to debug through procedures however.
You could have proc1 and proc2 pass their names into proc3 as a parameter.
For example:
CREATE PROCEDURE proc3
#Caller nvarchar(128) -- Name of calling proc.
AS
BEGIN
-- Produce error message that includes caller's name.
RAISERROR ('Caller was %s.', 16,10, #Caller);
END
GO
CREATE PROCEDURE proc1
AS
BEGIN
-- Get the name of this proc.
DECLARE #ProcName nvarchar(128);
SET #ProcName = OBJECT_NAME(##PROCID);
-- Pass it to proc3.
EXEC proc3 #ProcName
END
GO
CREATE PROCEDURE proc2
AS
BEGIN
-- Get the name of this proc.
DECLARE #ProcName nvarchar(128);
SET #ProcName = OBJECT_NAME(##PROCID);
-- Pass it to proc3.
EXEC proc3 #ProcName
END
GO