Copy trigger from one database to another - sql

Is it possible, in a script executed in MS SQL Server 2005, to copy a trigger from one database to another?
I've been asked to write a test script for a trigger my project is using. Our test structure is to create an empty database containing only the object under test, then execute a script on that database that creates all the other objects needed for the test, fills them, runs whatever tests are needed, compares the results against expected results, and then drops everything except the object under test.
I can't just create a database that is empty except for the trigger, because the trigger depends on several tables. My test script currently runs the CREATE TRIGGER after all the required tables are created, but this won't do because the test script isn't allowed to contain the object under test.
What's been suggested is that, instead of running a CREATE TRIGGER, I somehow copy the trigger at that point in the script from the live database to the test database. I've had a quick Google and haven't found a way to do this. Thus my question - is this even possible, and if so, how can I do it?

You could read the text of the trigger with sp_helptext (triggername)
Or you can select the text into a variable and execute that:
declare #sql varchar(8000)
select #sql = object_definition(object_id)
from sys.triggers
where name = 'testtrigger'
EXEC #sql

I have a stored procedure that copies a bunch of tables to a test database. To make it less prone to mistakes that could potentially change the wrong database, I want to avoid using USE and instead explicitly specify per statement which database the trigger is copied from and to.
With the help of this answer, I came up with this solution:
DECLARE #sql NVARCHAR(MAX);
EXEC SourceDB.sys.sp_executesql
N'SELECT #output = (SELECT OBJECT_DEFINITION(OBJECT_ID(''TriggerName'')))',
N'#output VARCHAR(MAX) OUTPUT',
#output = #sql OUTPUT;
EXEC DestDB.sys.sp_executesql #sql;

Related

How to use OPENROWSET to execute a stored procedure with parameters and insert result into a temp table

I want to insert the results of a stored procedure into a temp table using OPENROWSET. However, the issue I run into is I'm not able to pass parameters to my stored procedure.
This is my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
#PeopleCodeId nvarchar(10),
#StatementNumber int
AS
SET NOCOUNT ON
DECLARE #PersonId int
SELECT #PersonId = [dbo].[fnGetPersonId](#PeopleCodeId)
SELECT *
INTO #tempSpRetrieveStatement
FROM OPENROWSET('SQLNCLI', 'Server=PCPRODDB01;Trusted_Connection=yes;',
'EXEC Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId');
--2577, 15084
SELECT *
FROM #tempSpRetrieveStatement;
OpenRowSet will not allow you to execute Procedure with input parameters. You have to use INSERT/EXEC.
INTO #tempSpRetrieveStatement(Col1, Col2,...)
EXEC PCPRODDB01.Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId
Create and test a LinkedServer for PCPRODDB01 before running the above command.
The root of your problem is that you don't actually have parameters inside your statement that you're transmitting to the remote server you're connecting to, given the code sample you provided. Even if it was the very same machine you were connecting to, they'd be in different processes, and the other process doesn't have access to your session variables.
LinkedServer was mentioned as an option, and my understanding is that's the preferred option. However in practice that's not always available due to local quirks in tech or organizational constraints. It happens.
But there is a way to do this.
It's hiding in plain sight.
You need to pass literals into the string that will be executed on the other server, right?
So, you start by building the string that will do that.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
#PeopleCodeId nvarchar(10),
#StatementNumber int
AS
SET NOCOUNT ON
DECLARE
#PersonId INT,
#TempSQL VARCHAR(4000) = '';
SELECT #PersonId = [dbo].[fnGetPersonId](#PeopleCodeId);
SET #TempSQL =
'EXEC Campus.dbo.spRetrieveStatement(''''' +
FORMAT(#StatementNumber,'D') +''''', ''''' +
FORMAT(#PersonId,'D') + ''''')';
--2577, 15084
Note the seemingly excessive number of quotes. That's not a mistake -- that's foreshadowing. Because, yes, OPENROWSET hates taking variables as parameters. It, too, only wants literals. So, how do we give OPENROWSET what it needs?
We create a string that is the entire statement, no variables of any kind. And we execute that.
SET #TempSQL =
'SELECT * INTO #tempSpRetrieveStatement ' +
'FROM OPENROWSET(''SQLNCLI'', ''Server=PCPRODDB01;Trusted_Connection=yes;'', ' + #TempSQL +
'EXEC Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId';
EXEC (#TempSQL);
SELECT *
FROM #tempSpRetrieveStatement;
And that's it! Pretty simple except for counting your escaped quotes, right?
Now... This is almost beyond the scope of the question you asked, but it is a 'gotcha' I've experienced in executing stored procedures in another machine via OPENROWSET. You're obviously used to using temp tables. This will fail if the stored procedure you're calling is creating temp tables or doing a few other things that -- in a nutshell -- inspire the terror of ambiguity into your SQL server. It doesn't like ambiguity. If that's the case, you'll see a message like this:
"Msg 11514, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1
The metadata could not be determined because statement '…your remote EXEC statement here…' in procedure '…name of your local stored procedure here…' contains dynamic SQL. Consider using the WITH RESULT SETS clause to explicitly describe the result set."
So, what's up with that?
You don't just get data back with OPENROWSET. The local and remote servers have a short conversation about what exactly the local server is going to expect from the remote server (so it can optimize receiving and processing it as it comes in -- something that's extremely important for large rowsets). Starting with SQL Server 2012, sp_describe_first_result_set is the newer procedure for this, and normally it executes quickly without you noticing it. It's just that it's powers of divination aren't unlimited. Namely, it doesn't know how to get the type and name information regarding temp tables (and probably a few other things it can't do -- PIVOT in a select statement is probably right out).
I specifically wanted to be sure to point this out because of your reply regarding your hesitation about using LinkedServer. In fact, the very same reasons you're hesitant are likely to render that error message's suggestion completely useless -- you can't even predict what columns you're getting and in what order until you've got them.
I think what you're doing will work if, say, you're just branching upstream based on conditional statements and are executing one of several potential SELECT statements. I think it will work if you're just not confident that you can depend on the upstream component being fixed and are trying to ensure that even if it varies, this procedure doesn't have to because it's very generic.
But on the other hand you're facing a situation in which you literally cannot guarantee that SQL Server can predict the columns, you're likely going to have to force some changes in the stored procedure you're calling to insist that it's stable. You might, for instance work out how to ensure all possible fields are always present by using CASE expressions rather than any PIVOT. You might create a session table that's dedicated to housing what you need to SELECT just long enough to do that then DELETE the contents back out of there. You might change the way in which you transmit your data such that it's basically gone through the equivalent of UNPIVOT. And after all that extra work, maybe it'll be just a matter of preference if you use LinkedServer or OPENROWSET to port the data across.
So that's the answer to the literal question you asked, and one of the limits on what you can do with the answer.

Copying a stored procedure from one database to another

I have a central management database which collates some information and runs some dynamic SQL for various other tasks when a new database is restored into the environment. One of those tasks is going to be a bit complex to achieve through dynamic SQL so I had the idea of creating a master copy stored procedure in the central DB and copying that over to the new databases after they are restored.
I've seen a few examples of people trying to do that on here but I can't get anything to play ball.
Here's what i am trying to achieve conceptually, note that I'm trying to cater for potentially multiple stored procedures to be created in this way just for future proofing.
declare #sql nvarchar(max), #DatabaseName nvarchar(200)
set #DatabaseName = 'TargetDatabase'
set #sql =
(
SELECT definition + char(13) + 'GO'
FROM sys.sql_modules s
INNER JOIN sys.procedures p
ON [s].[object_id] = [p].[object_id] WHERE p.name LIKE '%mastercopy%'
)
exec #sql
Thanks
Instead of creating dynamic script you could use one script with all the procedures that you want to create (you can script all the procs you want using 2 click in SSMS), you then run this script manually in the context of the database where you want to create these procedures or by passing the file with this script to sqlcmd with -i and passing the correct database name with -d.
Here Use the sqlcmd Utility you can see the examples.

Stored Procedure gives Cannot find object... error in SSIS, but works fine in SSMS

I am trying to build an SSIS package that dynamically rebuilds the indexes for all the tables in my database. The general idea is that the package will make sure that the table is not being update and then execute a stored procedure that drops the old index, if it exists, and then recreates it. The logic behind the package seems to be sound. The problem that I am having is when I execute the package I keep getting the error:
Cannot find object...because it does not exist or you do not have permission...
The index existing should be irrelevant due to the IF EXISTS part.
The procedure looks like this:
REFERENCE_NAME AS VARCHAR(50),
COLUMN_NAME AS VARCHAR(50),
INDEX_NAME AS VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'IF EXISTS (SELECT name FROM sysindexes WHERE name = '+CHAR(39)+#INDEX_NAME+CHAR(39)+') '+
'DROP INDEX '+#INDEX_NAME+' ON '+#REFERENCE_NAME+' '+
'CREATE INDEX '+#INDEX_NAME+' ON '+#REFERENCE_NAME+'('+#COLUMN_NAME+') ON [INDEX]'
EXEC sp_executesql #sql
END
GO
I am able to execute the procedure through SSMS just fine, no error and it builds the index. When I execute the package in SSIS it errors out the minute it gets to the task that executes the stored procedure. I have made sure that SSIS is passing the variables to the execute SQL task and I have verified that I have db_ddladmin rights. Outside of that I am at a loss and have been beating my head against the wall for a day and a half on this.
Is there something I am missing, some permissions I need to request, or some work around for the issue?
Any information would be much appreciated.
Bartover, its definitely not looking at the wrong database. I have checked that the proc is there and the only connection on the package is to that specific database. Yes, I am executing the package manually with Visual Studios 2010 Shell Data Tools.
Sorrel, I tried your idea of a sanity check on the #sql statement on the drop, on both the drop and create, and on whole #sql statement, no joy.
Gnackenson, I had that same thought, but the connection authentication method is set to Windows Authentication, same as ssms. Do you have any ideas as to why it might use different permissions?
It looks like IF EXISTS is being ignored by SSIS SQL Task. To fix my problem, I altered my SQL tasks from DROP - CREATE to DISABLE - ENABLE.

Customizable database names and TempDB

I have a lump of SQL that looks a little like this
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{FOO}')
BEGIN
EXECUTE ('CREATE DATABASE {FOO}')
ALTER DATABASE {FOO} SET AUTO_CLOSE OFF
END
{FOO} is replaced at runtime with the name of a user configurable database. The logic is that I don't want to create the database if it already exists.
If {FOO} is tempdb then I get a failure when the query runs
Option 'AUTO_CLOSE' cannot be set in database 'tempdb'.
My question is why do I get this failure? SELECT * FROM sys.databases WHERE name = 'tempdb' returns zero results so surely my whole BEGIN/END pair shouldn't run? Indeed, if I put a print statement between begin and end, I don't see any output.
My guess is that SQL Server is doing some kind of linting on the SQL to make sure I don't muck around with tempdb? I have solved the problem by using EXECUTE instead, but I'm a little confused why I have to!
Try ensuring both commands are separate and within dynamic SQL, then the change to tempdb won't be caught by the parser:
EXEC sp_executesql N'CREATE DATABASE {FOO};';
EXEC sp_executesql N'ALTER DATABASE {FOO} SET AUTO_CLOSE OFF;';
This is similar to the reason you can't do this:
IF 1 = 1
BEGIN
CREATE TABLE #t1(id INT);
END
ELSE
BEGIN
CREATE TABLE #t1(x NVARCHAR(255));
END
Even though you and I know that only one of those #t1 code paths will ever be reached, SQL Server presumes that both paths could be reached at runtime, and so complains at parse time.

How do I run SQL queries on different databases dynamically?

I have a sql server stored procedure that I use to backup data from our database before doing an upgrade, and I'd really like it to be able to run the stored procedure on multiple databases by passing in the database name as a parameter. Is there an easy way to do this? The best I can figure is to dynamically build the sql in the stored procedure, but that feels like its the wrong way to do it.
build a procedure to back up the current database, whatever it is. Install this procedure on all databases that you want to backup.
Write another procedure that will launch the backups. This will depend on things that you have not mentioned, like if you have a table containing the names of each database to backup or something like that. Basically all you need to do is loop over the database names and build a string like:
SET #ProcessQueryString=
'EXEC '+DatabaseServer+'.'+DatabaseName+'.dbo.'+'BackupProcedureName param1, param2'
and then just:
EXEC (#ProcessQueryString)
to run it remotely.
There isn't any other way to do this. Dynamic SQL is the only way; if you've got strict controls over DB names and who's running it, then you're okay just truncating everything together, but if there's any doubt use QUOTENAME to escape the parameter safely:
CREATE PROCEDURE doStuff
#dbName NVARCHAR(50)
AS
DECLARE #sql NVARCHAR(1000)
SET #sql = 'SELECT stuff FROM ' + QUOTENAME(#dbName) + '..TableName WHERE stuff = otherstuff'
EXEC sp_ExecuteSQL (#sql)
Obviously, if there's anything more being passed through then you'll want to double-check any other input, and potentially use parameterised dynamic SQL, for example:
CREATE PROCEDURE doStuff
#dbName NVARCHAR(50)
#someValue NVARCHAR(10)
AS
DECLARE #sql NVARCHAR(1000)
SET #sql = 'SELECT stuff FROM ' + QUOTENAME(#dbName) + '..TableName WHERE stuff = #pOtherStuff'
EXEC sp_ExecuteSQL (#sql, '#pOtherStuff NVARCHAR(10)', #someValue)
This then makes sure that parameters for the dynamic SQL are passed through safely and the chances for injection attacks are reduced. It also improves the chances that the execution plan associated with the query will get reused.
personally, i just use a batch file and shell to sqlcmd for things like this. otherwise, building the sql in a stored proc (like you said) would work just fine. not sure why it would be "wrong" to do that.
best regards,
don
MSSQL has an OPENQUERY(dbname,statement) function where if the the server is linked, you specify it as the first parameter and it fires the statement against that server.
you could generate this openquery statement in a dynamic proc. and either it could fire the backup proc on each server, or you could execute the statement directly.
Do you use SSIS? If so you could try creating a couple ssis packages and try scheduling them,or executing them remotely.