How can I conditionally use a linked server depending on the environment a stored proc is currently running in? - sql-server-2005

Here's the issue I'm having. I am trying to create a stored proc that will be deployed to DEV, QA, and PROD environments. Because of the strict requirements on the deployment process, I have to make sure my proc in the same across all three environments and has to work (of course!). The problem is that this proc references a table in a different database. In DEV and QA this is ok, because the database is on the same server, however in PROD the database in question is located on a separate server. The following is a code snippet from my proc that tries to deal with the different environment issues:
IF ##SERVERNAME<>'Production'
BEGIN
select distinct m.acct_id
from l_map m (nolock)
join #llist ll on ll.acct_id = m.acct_id
where ll.acct_id not in (select l_number from [OTHERDATABASE].[dbo].[OTHERTABLE] where lmi_status_code not in (select item from #ruleItems))
END
ELSE
BEGIN
select distinct m.acct_id
from l_map m (nolock)
join #llist ll on ll.acct_id = m.acct_id
where ll.acct_id not in (select l_number from [OTHERSERVER].[OTHERDATABASE].[dbo].[OTHERTABLE] where lmi_status_code not in (select item from #ruleItems))
END
My proc is called from within a different proc. When I test the above logic directly, I get the results I expect. However, when I try to test it in context in DEV or QA (from the top level proc), I get an error saying that [OTHERSERVER] could not be found. I cannot (and don't need to) create a linked server in DEV and QA, but I need to be able to use the linked server in the PROD environment. Does anyone know how to accomplish this?

Use synonyms, see here.
Also see these two SO examples: one, two.
Synonym definition on each server may be (is) different, but the code (stored procedure) does not change.

My suggestion is to create a view on the table in the linked server. On your test server you can create a view onto a local table with test data.
In this way information about the linked server is isolated to the view. Then you can write your stored proc or other queries referencing the view, rather than referencing the linked server directly.
Note that this will not enable you to test the security and permissions you need, only that the query works with the schema.

I have the same situation.
Using an alias, I can not use OpenQuery that I need to execute functions with parameters on the destination server, where a simple SELECT INTO or EXECUTE was not possible.
Using EXEC will return (using my configuration) in error Msg 7411:
Server 'linked_server_name' is not configured for RPC.
Here is an example of my approach using a string query. Note that on testing I don't use linked server but you can use one if you need:
-- Prepare Source Query Fragment
IF ##SERVERNAME = 'production_server'
SET #SelectQuery = ' OPENQUERY (['
+ #SourceServer + '],''EXEC [production_source_db].[schema_name].['
+ #FuncrionName+'] '''''
+ #param_1 + ''''', '''''
+ #param_2 + ''''''')';
ELSE
SET #SelectQuery = ' EXEC [testing_schema].['
+ #FuncrionName+'] '''
+ #param_1 + ''', '''
+ #param_2 + ''')';
-- Prepare Destination Query Fragment
IF ##SERVERNAME = 'production_server'
SET #Destination = '[production_destination_server].[production_destination_db].[schema_name]';
ELSE
SET #Destination = '[testing_schema]';
-- Execute the data transfer
EXEC ('
INSERT INTO ' + #Destination + '.[Destination_Table] (
[Col1]
, [Col2])
SELECT
[Col1]
, [Col2]
FROM ' + #SelectQuery )

Related

Store a database name in variable & then using it dynamically

I have a Table in my Database Which have name of all the Database of my Server
Table Look like
create Table #db_name_list(Did INT IDENTITY(1,1), DNAME NVARCHAR(100))
INSERT INTO #db_name_list
SELECT 'db_One ' UNION ALL
SELECT 'db_Two' UNION ALL
SELECT 'db_Three' UNION ALL
SELECT 'db_four' UNION ALL
SELECT 'db_five'
select * from #db_name_list
I have so many SP in my Database..Which uses multiple table and Join Them..
At Present I am using the SQL code like
Select Column from db_One..Table1
Left outer join db_two..Table2
on ....some Condition ....
REQUIREMENT
But I do not want to HARDCODE the DATABASE Name ..
I want store DataBase name in Variable and use that .
Reason :: I want to restore same Database with Different name and want to Run those SP..At Present we Cant Do ,Because I have used db_One..Table1
or db_two..Table2
I want some thing like ...
/SAMPLE SP/
CREATE PROCEDURE LOAD_DATA
AS
BEGIN
DECLARE #dbname nvarchar(500)
set #dbname=( SELECT DNAME FROM #db_name_list WHERE Did=1)
set #dbname2=( SELECT DNAME FROM #db_name_list WHERE Did=2)
PRINT #DBNAME
SELECT * FROM #dbname..table1
/* or */
SELECT * FROM #dbname2.dbo.table1
END
i.e using Variable Instead of Database name ..
But it thow error
"Incorrect syntax near '.'."
P.S This was posted by some else on msdn but the answer there was not clear & I had the same kind of doubt. So please help
You can't use a variable like this in a static sql query. You have to use the variable in dynamic sql instead, in order to build the query you want to execute, like:
DECLARE #sql nvarchar(500) = 'SELECT * FROM ' + #dbname + '.dbo.mytable'
EXEC(#sql);
There seem to be a couple of options for you depending on your circumstances.
1. Simple - Generalise your procedures
Simply take out the database references in your stored procedure, as there is no need to have an explicit reference to the database if it is running against the database it is stored in. Your select queries will look like:
SELECT * from schema.table WHERE x = y
Rather than
SELECT * from database.schema.table WHERE x = y
Then just create the stored procedure in the new database and away you go. Simply connect to the new database and run the SP. This method would also allow you to promote the procedure to being a system stored procedure, which would mean they were automatically available in every database without having to run CREATE beforehand. For more details, see this article.
2. Moderate - Dynamic SQL
Change your stored procedure to take a database name as a parameter, such as this example:
CREATE PROCEDURE example (#DatabaseName VARCHAR(200))
AS
BEGIN
DECLARE #SQL VARCHAR(MAX) = 'SELECT * FROM ['+#DatabaseName+'].schema.table WHERE x = y'
EXEC (#SQL)
END

SQL Server - find SPs which don't drop temp tables

(1) Is there a good/reliable way to query the system catalogue in order
to find all stored procedures which create some temporary tables in their
source code bodies but which don't drop them at the end of their bodies?
(2) In general, can creating temp tables in a SP and not dropping
them in the same SP cause some problems and if so, what problems?
I am asking this question in the contexts of
SQL Server 2008 R2 and SQL Server 2012 mostly.
Many thanks in advance.
Not 100% sure if this is accurate as I don't have a good set of test data to work with. First you need a function to count occurrences of a string (shamelessly stolen from here):
CREATE FUNCTION dbo.CountOccurancesOfString
(
#searchString nvarchar(max),
#searchTerm nvarchar(max)
)
RETURNS INT
AS
BEGIN
return (LEN(#searchString)-LEN(REPLACE(#searchString,#searchTerm,'')))/LEN(#searchTerm)
END
Next make use of the function like this. It searches the procedure text for the strings and reports when the number of creates doesn't match the number of drops:
WITH CreatesAndDrops AS (
SELECT procedures.name,
dbo.CountOccurancesOfString(UPPER(syscomments.text), 'CREATE TABLE #') AS Creates,
dbo.CountOccurancesOfString(UPPER(syscomments.text), 'DROP TABLE #') AS Drops
FROM sys.procedures
JOIN sys.syscomments
ON procedures.object_id = syscomments.id
)
SELECT * FROM CreatesAndDrops
WHERE Creates <> Drops
1) probably no good / reliable way -- though you can extract the text of sp's using some arcane ways that you can find in other places.
2) In general - no this causes no problems -- temp tables (#tables) are scope limited and will be flagged for removal when their scope disappears.
and table variables likewise
an exception is for global temp tables (##tables) which are cleaned up when no scope holds a reference to them. Avoid those guys -- there are usually (read almost always) better ways to do something than with a global temp table.
Sigh -- if you want to go down the (1) path then be aware that there are lots of pitfalls in looking at code inside sql server -- many of the helper functions and information tables will truncate the actual code down to a NVARCHAR(4000)
If you look at the code of sp_helptext you'll see a really horrible cursor that pulls the actual text..
I wrote this a long time ago to look for strings in code - you could run it on your database -- look for 'CREATE TABLE #' and 'DROP TABLE #' and compare the outputs....
DECLARE #SearchString VARCHAR(255) = 'DELETE FROM'
SELECT
[ObjectName]
, [ObjectText]
FROM
(
SELECT
so.[name] AS [ObjectName]
, REPLACE(comments.[c], '#x0D;', '') AS [ObjectText]
FROM
sys.objects AS so
CROSS APPLY (
SELECT CAST([text] AS NVARCHAR(MAX))
FROM syscomments AS sc
WHERE sc.[id] = so.[object_id]
FOR XML PATH('')
)
AS comments ([c])
WHERE
so.[is_ms_shipped] = 0
AND so.[type] = 'P'
)
AS spText
WHERE
spText.[ObjectText] LIKE '%' + #SearchString + '%'
Or much better - use whatever tool of choice you like on your codebase - you've got all your sp's etc scripted out into source control somewhere, right.....?
I think SQL Search tool from red-gate would come handy in this case. You can download from here. This tool will find the sql text within stored procedures, functions, views etc...
Just install this plugin and you can find sql text easily from SSMS.

Can I use a variable as the value of the option AUDIT_GUID for the CREATE SERVER AUDIT statement?

I am trying to make the Audit_GUID value in the CREATE SERVER AUDIT command dynamic by using the NEWID() function in SQL. Below is my SQL script to do this:
USE [master]
GO
DECLARE #newGUID as uniqueidentifier
SET #newGUID = NEWID()
CREATE SERVER AUDIT Audit_Select_Queries -- Name of the Audit(unique for a Server)
TO FILE
( FILEPATH = N'XXXX' -- Folder to Store Audit Files at
,MAXSIZE = 0 MB -- 0 = UNLIMITED
,MAX_ROLLOVER_FILES = 2147483647 -- Max possible number of Files
,RESERVE_DISK_SPACE = OFF
)
WITH
( QUEUE_DELAY = 1000 -- Delay Audit actions by this time for completion
,ON_FAILURE = CONTINUE -- Database operation is more important than Audit
,AUDIT_GUID = #newGUID -- UUID of the Audit (unique for a server)
)
ALTER SERVER AUDIT Audit_Select_Queries WITH (STATE = OFF)
GO
But I get a syntax error near #newGUID saying "Incorrect syntax near '#newGUID'"
Please let me know what am I doing wrong.
EDIT: I am working on Microsoft SQL Server 2012
No ...
CREATE SERVER AUDIT is a statement – so AUDIT_GUID isn't a 'parameter' in the same way that a SQL Server parameter of a stored procedure is a parameter. If you're familiar with other languages, you could consider CREATE SERVER AUDIT as a 'special form' and, as such, you simply need to remember that it doesn't accept variables for that option.
I can understand why that's confusing as, for example, the BACKUP statement(s) do allow variables for certain 'parameters' ("options"), namely the name of the database; e.g. this is perfectly valid T-SQL:
DECLARE #databaseName nvarchar = "insert_name_of_database_here";
BACKUP DATABASE databaseName
...
For clarifying these types of questions, just consult Microsoft's documentation for the relevant version of SQL Server if you can't remember whether some parameters or options accept variables or not. [You can easily open the relevant documentation from SSMS by highlighting the statement, built-in procedure, etc. and hitting F1 on your keyboard.]
But if You're Willing to Dynamically Generate the T-SQL ...
Here's how you can use dynamic SQL – via EXECUTE or sp_executesql – to do what you're trying to do:
DECLARE #dynamicSql nvarchar(1000);
SELECT #dynamicSql = 'CREATE SERVER AUDIT
...
AUDIT_GUID = ''' + CAST(#newGUID AS nvarchar(255)) + ''''
+ '...' + ...,
EXEC sp_executesql #dynamicSql;

Statement 'SELECT INTO' is not supported in this version of SQL Server - SQL Azure

I am getting
Statement 'SELECT INTO' is not supported in this version of SQL Server
in SQL Server
for the below query inside stored procedure
DECLARE #sql NVARCHAR(MAX)
,#sqlSelect NVARCHAR(MAX) = ''
,#sqlFrom NVARCHAR(MAX) = ''
,#sqlTempTable NVARCHAR(MAX) = '#itemSearch'
,#sqlInto NVARCHAR(MAX) = ''
,#params NVARCHAR(MAX)
SET #sqlSelect ='SELECT
,IT.ITEMNR
,IT.USERNR
,IT.ShopNR
,IT.ITEMID'
SET #sqlFrom =' FROM dbo.ITEM AS IT'
SET #sqlInto = ' INTO ' + #sqlTempTable + ' ';
IF (#cityId > 0)
BEGIN
SET #sqlFrom = #sqlFrom +
' INNER JOIN dbo.CITY AS CI2
ON CI2.CITYID = #cityId'
SET #sqlSelect = #sqlSelect +
'CI2.LATITUDE AS CITYLATITUDE
,CI2.LONGITUDE AS CITYLONGITUDE'
END
SELECT #params =N'#cityId int '
SET #sql = #sqlSelect +#sqlInto +#sqlFrom
EXEC sp_executesql #sql,#params
I have around 50,000 records, so decided to use Temp Table. But surprised to see this error.
How can i achieve the same in SQL Azure?
Edit: Reading this blog http://blogs.msdn.com/b/sqlazure/archive/2010/05/04/10007212.aspx suggesting us to CREATE a Table inside Stored procedure for storing data instead of Temp table. Is it safe under concurrency? Will it hit performance?
Adding some points taken from http://blog.sqlauthority.com/2011/05/28/sql-server-a-quick-notes-on-sql-azure/
Each Table must have clustered index. Tables without a clustered index are not supported.
Each connection can use single database. Multiple database in single transaction is not supported.
‘USE DATABASE’ cannot be used in Azure.
Global Temp Tables (or Temp Objects) are not supported.
As there is no concept of cross database connection, linked server is not the concept in Azure at this moment.
SQL Azure is shared environment and because of the same there is no concept of Windows Login.
Always drop TempDB objects after their need as they create pressure on TempDB.
During buck insert use batchsize option to limit the number of rows to be inserted. This will limit the usage of Transaction log space.
Avoid unnecessary usage of grouping or blocking ORDER by operations as they leads to high end memory usage.
SELECT INTO is one of the many things that you can unfortunately not perform in SQL Azure.
What you'd have to do is first create the temporary table, then perform the insert. Something like:
CREATE TABLE #itemSearch (ITEMNR INT, USERNR INT, IT.ShopNR INT, IT.ITEMID INT)
INSERT INTO #itemSearch
SELECT IT.ITEMNR, IT.USERNR, IT.ShopNR ,IT.ITEMID
FROM dbo.ITEM AS IT
The new Azure DB Update preview has this problem resolved:
The V12 preview enables you to create a table that has no clustered
index. This feature is especially helpful for its support of the T-SQL
SELECT...INTO statement which creates a table from a query result.
http://azure.microsoft.com/en-us/documentation/articles/sql-database-preview-whats-new/
Create the table using # prefix, e.g. create table #itemsearch then use insert into. The scope of the temp table is limited to the session so there will no concurrency problems.
Well, As we all know SQL Azure table must have a clustered index, that is why SELECT INTO failure copy data from one table in to another table.
If you want to migrate, you must create a table first with same structure and then execute INSERT INTO statement.
For temporary table which followed by # you don't need to create Index.
how to create index and how to execute insert into for temp table?

Drop all temporary tables for an instance

I was wondering how / if it's possible to have a query which drops all temporary tables?
I've been trying to work something out using the tempdb.sys.tables, but am struggling to format the name column to make it something that can then be dropped - another factor making things a bit trickier is that often the temp table names contain a '_' which means doing a replace becomes a bit more fiddly (for me at least!)
Is there anything I can use that will drop all temp tables (local or global) without having to drop them all individually on a named basis?
Thanks!
The point of temporary tables is that they are.. temporary. As soon as they go out of scope
#temp create in stored proc : stored proc exits
#temp created in session : session disconnects
##temp : session that created it disconnects
The query disappears. If you find that you need to remove temporary tables manually, you need to revisit how you are using them.
For the global ones, this will generate and execute the statement to drop them all.
declare #sql nvarchar(max)
select #sql = isnull(#sql+';', '') + 'drop table ' + quotename(name)
from tempdb..sysobjects
where name like '##%'
exec (#sql)
It is a bad idea to drop other sessions' [global] temp tables though.
For the local (to this session) temp tables, just disconnect and reconnect again.
The version below avoids all of the hassles of dealing with the '_'s. I just wanted to get rid of non-global temp tables, hence the '#[^#]%' in my WHERE clause, drop the [^#] if you want to drop global temp tables as well, or use a '##%' if you only want to drop global temp tables.
The DROP statement seems happy to take the full name with the '_', etc., so we don't need to manipulate and edit these. The OBJECT_ID(...) NOT NULL allows me to avoid tables that were not created by my session, presumably since these tables should not be 'visible' to me, they come back with NULL from this call. The QUOTENAME is needed to make sure the name is correctly quoted / escaped. If you have no temp tables, #d_sql will be the empty string still, so we check for that before printing / executing.
DECLARE #d_sql NVARCHAR(MAX)
SET #d_sql = ''
SELECT #d_sql = #d_sql + 'DROP TABLE ' + QUOTENAME(name) + ';
'
FROM tempdb..sysobjects
WHERE name like '#[^#]%'
AND OBJECT_ID('tempdb..'+QUOTENAME(name)) IS NOT NULL
IF #d_sql <> ''
BEGIN
PRINT #d_sql
-- EXEC( #d_sql )
END
In a stored procedure they are dropped automatically when the execution of the proc completes.
I normally come across the desire for this when I copy code out of a stored procedure to debug part of it and the stored proc does not contain the drop table commands.
Closing and reopening the connection works as stated in the accepted answer. Rather than doing this manually after each execution you can enable SQLCMD mode on the Query menu in SSMS
And then use the :connect command (adjust to your server/instance name)
:connect (local)\SQL2014
create table #foo(x int)
create table #bar(x int)
select *
from #foo
Can be run multiple times without problems. The messages tab shows
Connecting to (local)\SQL2014...
(0 row(s) affected)
Disconnecting connection from (local)\SQL2014...