SQL - Replacing connection strings for multiple stored procedures - sql-server

I'm looking to replace connection strings across multiple stored procedures.The development database I'm working with is restored from production and stored procedures within contain connection strings to a production linked server. My aim is replace the linked server connection strings to point at a development linked server for testing purposes. I'm looking to automated this as a step in the SQL Agent restore job to run immediately after the restore.
The issue I'm having is setting the stored proc definition as a variable while keeping the formatting. When selecting the definition text, the stored proc is all on one line so after replacing the CREATE for ALTER along with the connection stings I cannot execute the SQL due to the formatting. I've tried playing around with STRING_SPLIT as but to no avail. Is there a way to do this or is it not possible?
Here is an example of part of one of the procedures
SELECT
a.AgreementNumber,
a.AgreementProposalID,
c.CustomerNumber,
pc.Id CustomerID,
a.AgreementCreateDate
INTO
#a
FROM
SENTINEL.DotDot_S3DB01_Replica.dbo.AgreementTable a
INNER JOIN
SENTINEL.DotDot_S3CUSTDB_Replica.dbo.CustomerTable c ON a.AgreementCustomerNumber = c.CustomerNumber
INNER JOIN
SENTINEL.DotDot_Proposal_Replica.dbo.Customer pc ON pc.Code = c.CustomerNumber
WHERE
a.AgreementCreateDate > #LastEnteredDate;
After my changes it needs to look like this to reference the test database names
SELECT
a.AgreementNumber,
a.AgreementProposalID,
c.CustomerNumber,
pc.Id CustomerID,
a.AgreementCreateDate
INTO
#a
FROM
SENTINEL.DotDotUAT_S3DB01.dbo.AgreementTable a
INNER JOIN
SENTINEL.DotDotUAT_S3CUSTDB.dbo.CustomerTable c ON a.AgreementCustomerNumber = c.CustomerNumber
INNER JOIN
SENTINEL.DotDotUAT_Proposal .dbo.Customer pc ON pc.Code = c.CustomerNumber
WHERE
a.AgreementCreateDate > #LastEnteredDate;
I've tried using this code to select the stored procedure code as a variable so I can execute the alter procedure command with the changes however the object definition selected from sys.procedures is displayed all on one line.
DECLARE #SQL VARCHAR(MAX)
SET #SQL = ( SELECT OBJECT_DEFINITION(object_id)
FROM sys.procedures
WHERE name = 'Usp_Proc')
SET #SQL = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#SQL,'CREATE','ALTER'),'OriginalString','ReplacementString'),'OriginalString','ReplacementString'),'OriginalString','ReplacementString'),'OriginalString','ReplacementString')
EXEC (#SQL)

Related

Remote Call Compiles SPROC differently to local, causes error

I have a stored proc that returns details of SQL Agent Jobs on the local server. There is a master script that calls this proc, using OPENQUERY, against every SQL server in the ecosystem. In pseudocode, the master script looks something like this:
FOR EACH #LinkedServer in the list
SET #SQL = 'INSERT #Results SELECT * FROM OPENQUERY(' + #LinkedServer + ',''EXEC ScriptToGetAgentJobInfo'')'
EXEC sp_executesql #SQL
NEXT #LinkedServer
Some of the agent jobs are created from SSRS report subscriptions, so they have horrible looking names. In order to replace them with the name of the report that is the target of the subscription, I appeal to the ReportServer database on the #LinkedServer, as part of the ScriptToGetAgentJobInfo.
However, not every server contains a ReportServer database, so sometimes this appeal would fail. To get round that failure, I have the following lines of script:
DECLARE #Reports TABLE
( AgentJob SYSNAME
,Reportname NVARCHAR(128));
IF EXISTS(SELECT 1 FROM [master].[dbo].sysdatabases WHERE [name] = 'ReportServer')
BEGIN;
INSERT #Reports(AgentJob,Reportname)
SELECT Job.job_id, Report.[Name]
FROM
ReportServer.dbo.ReportSchedule AS ReportSched
INNER JOIN dbo.sysjobs AS Job ON CONVERT(SYSNAME,ReportSched.ScheduleID) = Job.[name]
INNER JOIN ReportServer.dbo.Subscriptions AS Subscription ON ReportSched.SubscriptionID = Subscription.SubscriptionID
INNER JOIN ReportServer.dbo.[Catalog] AS Report ON Subscription.report_oid = Report.itemid;
END;
The idea is that if the reportserver database doesn't exist, I can avoid any calls to it, which would error, but if it does, I can get data from it. I then join the #Reports table to my SQL Agent job query with a LEFT JOIN to show the name of the relevant report if there is one.
All this works fine when I run the script locally, but when it is called through the master procedure, I get an error saying Invalid object name 'ReportServer.dbo.ReportSchedule'.
I can get round this problem by making the reportserver select statement "dynamic" (although it is totally static) and calling it with another sp_executesql call, but I really hate doing this!
So my question is this: Why does the error only occur when calling the script remotely and how can I avoid it without recourse to dynamic sql?
The master script is written and run in SQL Server 14.0, while the linked server that is causing the problem is only on SQL Server 10.50.

Pass Linked Server name as parameter to Stored Procedure (not using Dynamic TSQL)

I need to pass linked server name as variable to stored procedure right now after testing and research they all suggest to using dynamic sql and open query which I am using now. however I am not comfortable using it(sql injection) plus I need to call other user defined function to the query. I am looking for a more secure and direct call. Here is my SP
ALTER PROCEDURE [dbo].[GetBackUpStatus]
-- Add the parameters for the stored procedure here
#linkedServerName AS VARCHAR(100),
#exemptDB as VARCHAR(100)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #Sql varchar(8000)
SET NOCOUNT ON;
set #Sql = 'select * from openquery (' + #linkedServerName + ' , ''SELECT SERVERPROPERTY(''''SERVERNAME'''') AS "SERVERNAME",
T1.NAME AS DatabaseName,
MAX(T2.backup_finish_date) AS last_db_backup_date,
CAST(COALESCE(DATEDIFF(second, MAX(T2.backup_finish_date) , CURRENT_TIMESTAMP)/ 3600.0, 9999.0) as NUMERIC(6,2)) AS [Hours Since Backup]
FROM master.sys.databases T1
LEFT OUTER JOIN msdb.dbo.backupset T2 ON T2.database_name = T1.NAME
WHERE T1.NAME NOT IN (''''tempdb'''')
GROUP BY T1.NAME
ORDER BY T1.NAME'')'
Exec (#Sql)
END
the purpose of this query is to get the server status and its database, I don't like this because of that confusing single quotes, this query will eventually grow as I develop and add function calls.
I tried this and something like this is what I wanted, since it is direct query and cleaner without those quotes. That's how I typically use linked server.
Select * from [' + #linkedservername + '].[schema].table
thanks
Solution for a large scale data model with hundreds of tables / objects.
Dynamic modification and cloning of a stored procedure for every linked-server.
It is kinda hiding any dynamic SQL under the hood.
How to
Create a stored procedure which interacts with an existing linked-server.
During a database deployment process:
Obtain the source code of the stored procedure.
Replace the name of the linked-server in the code.
If you want to create a new stored procedure (cloned), replace the name of the initial stored procedure in the code.
Create a cloned stored procedure or modify the current.
Repeat all steps for each required linked-server.
There are another variations for it.
Now, any external logic may decide which procedure to use.
You can check the existence of a linked-server or its related stored procedure.
For modifications and cloning, it is possible to use SQL Server or external tools, such as C#, CMD, etc.
For creation under SQL Server.

Is a Stored Procedure suitable for my task

I am completely new to SQL Server and a bit lost. When I try the following, it executes the first three lines and ignores the rest, just get
'Command(s) completed successfully.'
USE [RenewalsDb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[One]
AS
BEGIN
SET NOCOUNT ON;
DROP TABLE [dbo].NewTransTable;
SELECT * INTO [dbo].[NewTransTable] FROM [dbo].aqryTransTable;
DELETE FROM [dbo].[NewTransTable] WHERE (((NewTransTable.CURRENT_LICENSE_SKU_DESC) Like '% partner program %'));
DELETE FROM [dbo].[NewTransTable] WHERE (((NewTransTable.RENEWAL_MAINTAINANCE_SKU_DESC) Like '% partner program %'));
UPDATE NewTransTable SET NewTransTable.[Quote Number] = Null;
UPDATE dbo.TransactionTable SET Concat = dbo.qryNamedAcReseller.LATEST_DISTRIBUTOR_NAME + dbo.qryNamedAcReseller.[Sub Territory FY14 ]
FROM dbo.TransactionTable INNER JOIN
dbo.qryNamedAcReseller ON dbo.TransactionTable.LATEST_INSTANCE_NUMBER = dbo.qryNamedAcReseller.LATEST_INSTANCE_NUMBER;
UPDATE dbo.TransactionTable SET Concat = dbo.qryNamedAcReseller.[Sub Territory FY14 ]
FROM dbo.TransactionTable INNER JOIN
dbo.qryNamedAcReseller ON dbo.TransactionTable.LATEST_INSTANCE_NUMBER = dbo.qryNamedAcReseller.LATEST_INSTANCE_NUMBER
WHERE Concat IS NULL;
UPDATE dbo.NewTransTable SET [Quote Number] = dbo.Autogen.[Quote Number] FROM dbo.Autogen RIGHT OUTER JOIN
dbo.NewTransTable ON dbo.Autogen.[IB Reference Num] = dbo.NewTransTable.LATEST_INSTANCE_NUMBER AND
dbo.Autogen.[Quote Known As] = dbo.NewTransTable.[Quote Known As]
DROP TABLE [dbo].NewTransTable2;
SELECT * INTO [dbo].[NewTransTable2] FROM [dbo].aqryTransTable2;
ALTER TABLE [dbo].NewTransTable2 ADD Named_Account nvarchar(255);
END
GO
Welcome to stackoverflow.
Stored Procedure is like a template which can reused multiple times and it can be made dynamic with the help or parameters. Refer mssqltips.com - SQL Server Stored Procedure Tutorial on guide to Stored Procedures. If you want to execute some commands only once, Stored Procedure is not the right thing.
So when you execute the above script, what SQL Server is doing is creating the template structure i.e. Stored procedure named [One] and not actually running the scripts within the stored procedure.
To execute this stored procedure named [One] you have to call it using EXEC One or just simply One and Execute (F5)

SQL Server proc running 5x slower than plain query

I have the following query:
DECLARE #DaysNotUsed int = 14
DECLARE #DaysNotPhoned int = 7
--Total Unique Students
DECLARE #totalStudents TABLE (SchoolID uniqueidentifier, TotalUniqueStudents int)
INSERT INTO #totalStudents
SELECT
SSGG.School,
COUNT(DISTINCT S.StudentID)
FROM Student S
INNER JOIN StudentStudents_GroupGroups SSGG ON (SSGG.Students = S.StudentID AND SSGG.School = S.School)
INNER JOIN [Group] G ON (G.GroupID = SSGG.Groups AND G.School = SSGG.School)
INNER JOIN SessionHistory SH ON (SH.Student = S.StudentID AND SH.School = S.School AND SH.StartDateTime > GETDATE() - #DaysNotUsed)
WHERE G.IsBuiltIn = 0
AND S.HasStartedProduct = 1
GROUP BY SSGG.School
--Last Used On
DECLARE #lastUsed TABLE (SchoolID uniqueidentifier, LastUsedOn datetime)
INSERT INTO #lastUsed
SELECT
vi.SchoolID,
MAX(sh.StartDateTime)
FROM View_Installation as vi
INNER JOIN SessionHistory as sh on sh.School = vi.SchoolID
GROUP BY vi.SchoolID
SELECT
VI.SchoolID,
INS.DateAdded,
INS.Removed,
INS.DateRemoved,
INS.DateToInclude,
VI.SchoolName AS [School Name],
VI.UsersLicensed AS [Licenses],
ISNULL(TS.TotalUniqueStudents, 0) as [Total Unique Students],
ISNULL(TS.TotalUniqueStudents, 0) * 100 / VI.UsersLicensed as [% of Students Using],
S.State,
LU.LastUsedOn,
DATEDIFF(DAY, LU.LastUsedOn, GETDATE()) AS [Days Not Used],
SI.AreaSalesManager AS [Sales Rep],
SI.CaseNumber AS [Case #],
SI.RenewalDate AS [Renewal Date],
SI.AssignedTo AS [Assigned To],
SI.Notes AS [Notes]
FROM View_Installation VI
INNER JOIN School S ON S.SchoolID = VI.SchoolID
LEFT OUTER JOIN #totalStudents TS on TS.SchoolID = VI.SchoolID
INNER JOIN #lastUsed LU on LU.SchoolID = VI.SchoolID
LEFT OUTER JOIN InactiveReports..SchoolInfo SI ON S.SchoolID = SI.SchoolID
LEFT OUTER JOIN InactiveReports..InactiveSchools INS ON S.SchoolID = INS.SchoolID
WHERE VI.UsersLicensed > 0
AND VI.LastPhoneHome > GETDATE() - #DaysNotPhoned
AND
(
(
SELECT COUNT(DISTINCT S.StudentID)
FROM Student S
INNER JOIN StudentStudents_GroupGroups SSGG ON (SSGG.Students = S.StudentID AND SSGG.School = S.School)
INNER JOIN [Group] G ON (G.GroupID = SSGG.Groups AND G.School = SSGG.School)
WHERE G.IsBuiltIn = 0
AND S.School = VI.SchoolID
) * 100 / VI.UsersLicensed < 50
OR
VI.SchoolID NOT IN
(
SELECT DISTINCT SH1.School
FROM SessionHistory SH1
WHERE SH1.StartDateTime > GETDATE() - #DaysNotUsed
)
)
ORDER BY [Days Not Used] DESC
Running just plain sql like this in SSMS take about 10 seconds to run. When I created a stored procedure with exactly the same code, the query takes 50 seconds instead. The only difference in the actual code of the proc is a SET NOCOUNT ON that the IDE put in by default, but adding that line to the query doesn't have any impact. Any idea what would cause such a dramatic slow down like this?
EDIT I neglected the declare statements at the beginning. These are not in the proc, but are parameters to it. Could this be the difference?
I agree about the potential parameter sniffing issue, but I would also check these settings.
For the procedure:
SELECT uses_ansi_nulls, uses_quoted_identifier
FROM sys.sql_modules
WHERE [object_id] = OBJECT_ID('dbo.procedure_name');
For the SSMS query window where the query is running fast:
SELECT [ansi_nulls], [quoted_identifier]
FROM sys.dm_exec_sessions
WHERE session_id = ##SPID;
If either of these don't match, you might consider dropping the stored procedure and re-creating it with those two settings matching. For example, if the procedure has uses_quoted_identifier = 0 and the session has quoted_identifier = 1, you could try:
DROP PROCEDURE dbo.procedure_name;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE PROCEDURE dbo.procedure_name
AS
BEGIN
SET NOCOUNT ON;
...
END
GO
Ideally all of your modules will be created with the exact same QUOTED_IDENTIFIER and ANSI_NULLS settings. It's possible the procedure was created when the settings were off (the default is on for both), or it's possible that where you are executing the query, one or both options are off (you can change this behavior in SSMS under Tools/Options/Query Execution/SQL Server/ANSI).
I'm not going to make any disclaimers about the behavior of the stored procedure with the different settings (for example you may have wanted ANSI_NULLS off so you could compare NULL = NULL), that you'll have to test, but at least you'll be comparing queries that are being run with the same options, and it will help narrow down potential parameter sniffing issues. If you're intentionally using SET ANSI_NULLS OFF, however, I caution you to find other approaches as that behavior will eventually be unsupported.
Other ways around parameter sniffing:
make sure you don't inadvertently compile the procedure with atypical parameters
use the recompile option either on the procedure or on the statement that seems to be the victim (I'm not sure if all of these are valid, because I can only tell that you are using SQL Server 2005 or greater, and some of these were introduced in 2008)
declare local variables similar to your input parameters, and pass the input parameter values to them, using the local variables later in the prodedure and ignoring the input parameters
The last option is my least favorite, but it's the quickest / easiest fix in the midst of troubleshooting and when users are complaining.
Also, in addition to everything else mentioned, if you are on SQL Server 2008 and up, have a look at OPTIMIZE FOR UNKNOWN http://blogs.msdn.com/b/sqlprogrammability/archive/2008/11/26/optimize-for-unknown-a-little-known-sql-server-2008-feature.aspx
I would recommend recompiling the execution plan for the stored procedure.
usage: sp_recompile '[target]'
example: sp_recompile 'dbo.GetObject'
When you execute a query from SSMS the query plan is automatically redone every time its executed. However with stored procs, sql server caches execution plans for stored procedures, and its this execution plan that gets used everytime the stored proc is called.
Link for sp_recompile.
You can also change the proc to use with WITH RECOMPILE clause within the stored proc.
Example:
CREATE PROCEDURE dbo.GetObject
(
#parm1 VARCHAR(20)
)
WITH RECOMPILE
AS
BEGIN
-- Queries/work here.
END
However this will force the execution plan to be recompiled every time the stored proc is called. This is good for dev/testing where the proc and/or data changes quite frequently. Make sure you remove it when you deploy it to production, as this can have a performance hit.
sp_recompile only recompiles the execution plan once. If you need to do it again at a later date, you will need to make the call again.
Good luck!
OK, thank you all for your help. Turns out it was a terribly stupid rookie mistake. The first time I created the proc, it created it under my user's schema instead of the dbo schema. When I called the proc I was simply doing 'exec proc_name', which I'm realizing now was using the version of the proc under my user's schema. Running 'exec dbo.proc_name' ran as expected.

How to determine the SQL Server object name from object id and database id?

I need the behaviour of SQL Server 2005 where function OBJECT_NAME takes two arguments, obj id and db id, while SQL Server 2000 takes only obj id so the execution must be in the context of the database to which inspected object belongs to.
Solution must be possible to implement in a function, so it can be used in a select query.
In SQL 2005 and up it is of course trivial to do this. The problem is SQL 2000. I used 2000 a lot back when, but no longer have access to any installations of it; the rest of this is largely from memory, and may be inaccurate.
The key thing is how to retrieve data from a database other than the "current" database, when you cannot know what that other database (or databases) will be at the time the code is written. (Yes, the db_id parameter is very convenient!) For this problem and for similar problems, the general work-around is to create dynamic code, something like:
SET #Command = 'select name from ' + #dbname + '.dbo.sysobjects where object_id = ' + #ObjectId
EXECUTE (#Command)
The problem is, I'm pretty sure you can't run dynamic code within functions (or perhaps just within SQL 2000 functions).
You might have to resort to creating a temp table, populating it via dynamic query, and then using it within the "main" query you are trying to write. Psuedo code would be like:
CREATE #TempTable
IF SQL2000 or earlier
INSERT #TempTable EXECUTE (select data from TargetDb.dbo.sysobjects)
-- Note that the entire insert may need to be in the dynamic statement
ELSE
INSERT #TempTable SELECT [from query based on object_id]
SELECT [the data you need]
from YourTable
join #TempTable
In SQL 2008 and up, use:
OBJECT_NAME ( object_id [, database_id ] )
for example:
SELECT TOP 10
object_schema_name(objectid, dbid) as [SchemaName],
object_name(objectid, dbid) as [ObjectName],
e.*
from sys.dm_exec_cached_plans P
CROSS APPLY sys.dm_exec_query_plan(P.plan_handle) E

Resources