Linked Server Query / Dynamic SQL - sql

I currently have a linked server that I am querying in a stored procedure. I have the query working just fine currently however this query will need to change for every branch of code I have. I would like to know what the best method is for derriving the database name I am calling in the cross server query.
Ex:
Server A has a link to server B. Server A contains 3 databases. SRV_A.DB1_DEV, SRV_A.DB2_Trunk, SRV_A.DB3_Prod Each are linked to their Server B counterpart... SRV_B.DB1_DEV, SRV_B.DB2_Trunk, SRV_B.DB3_Prod
Each database on Server A has the same stored procedure. The only thing that changes in the sproc is the cross server select. So SRV_A.DB1_Dev has a select in the sproc that reads:
SELECT foo FROM [SRV_B].[DB1_DEV].[foo_table] WHERE bar = 1
while the stored procedure on the trunk branch would be
SELECT foo FROM [SRV_B].[DB2_Trunk].[foo_table] WHERE bar = 1
Since I would like to have a VS project that will deploy the DB to every branch mentioned I would like to be able to fill in the database name dynamically. The solution I have came up with that is working is to use a series of IF checks with the CHARINDEX function, and then create the query with dynamic SQL, like this:
DECLARE #dSql NVARCHAR(4000);
DECLARE #databaseName NVARCHAR(100) = DB_NAME();
DECLARE #tableName NVARCHAR(100);
IF SELECT CHARINDEX('Dev', #databaseName, 0)
SET #tableName = '[SRV_B].[DB1_DEV].[foo_table]
...Same if & set for Trunk
...Same if & set for Prod
SET #dSql = 'DECLARE #retID INT;SELECT foo FROM ' + #tableName
+ ' WHERE bar = 1';SET #retID = SELECT SCOPE_IDENTITY()'
EXEC(#dSQL);
I would have to imagine there is a better solution though, if anyone can help me with one, it would be much appreciated. If by some outside shot this is the best way let me know as well.
Thanks,
James

One way to solve this problem might be to abstract the linked server name by wrapping it in a synonym:
note the extra part in the target table name - cross-server queries require a four-part name - I'm assuming this is a typo in the question and that foo_table is in the dbo schema
CREATE SYNONYM dbo.syn_foo_table
FOR [SRV_B].[DB1_DEV].[dbo].[foo_table]
which could then be referred to in the code as
SELECT foo FROM dbo.syn_foo_table WHERE bar = 1
You would then need to customise your deployment script to create the synonym(s) pointing at the correct server/database for the environment. This could use a similar dynamic SQL process to the one you've outlined above, but would only need to be executed once at deployment time (rather than on every execution).
Another possible solution is to use SQLCMD parameters in the stored procedure script, since (AFAIK) VS projects use SQLCMD to deploy database objects.
This feature allows you to parameterise SQL scripts with variables in the form $(variablename) - in your case:
SELECT foo FROM [SRV_B].[$(dbname)].[foo_table] WHERE bar = 1
The value of the variable can be set using an environment variable or passed into the command as an argument using the -v switch. See the SQLCMD MSDN link above for full details.

I was able to use a combination of enviornment variables as mentioned above for the db-name, and also dynamically generate the SRV name as well by using the following query:
DECLARE #ServerName NVARCHAR(100);
SET #ServerName = (SELECT name FROM sys.servers WHERE server_id = 1)

Related

Programmatically Set Table Name in the FROM Clause

Is there a way to programmatically set the table names used in the FROM clause?
The reason is we have a different table names in our prod vs. dev environment therefore we need to set the table names accordingly to be used in our reports, based on the different environments.
For example:
In prod the database name is 123prd, in dev it would be 123dev
In prod the database name is 456prd, in dev it would be 456dev
The report runs against database 123prd and we need to INNER JOIN to another table in the 456prd database.
So for the Prod environment it would be something like below:
USE 123prd
SELECT *
FROM aTable a
JOIN 456prd.dbo.bTable b
ON a.id = b.id
However since the report needs to work correctly according to the different environment Prod vs. Dev I will need to programatically change the database name in the FROM clause.
So this is what I have:
DECLARE #456DBName VARCHAR(16)
SET #456DBName = REPLACE(DB_NAME(), '123', '456')
USE 123prd
SELECT *
FROM aTable a
JOIN CONCAT(#456DbName, '.dbo.bTable') b
ON a.id = b.id
I got error invalid syntax when using CONCAT or +
Is there a correct way on how to do linked server programmatically? Sorry about bad English by the way, hopefully my question makes sense.
You have limited options, because you cannot just set the project that way.
One option is to do away with three part naming. Just reference tables within the database. This works if production/development only consist of one database.
Another option is to wrap all references with views (these could also incorporate business logic). This is particularly helpful if "production" and "development" can span multiple databases. The views can be in one place -- but they probably need to be created using deployment scripts that use dynamic SQL.
A related option would use synonyms. I have not personally used these for this purpose, but they should work.
And finally, there is dynamic SQL. One method is to store the queries and have a stored procedure do the replacement. Heck, you could even store the queries as views for a specific database -- and have a stored procedure do the substitution for other environments.
You can query across multiple servers using 'server groups' in ssms.https://learn.microsoft.com/en-us/sql/ssms/register-servers/execute-statements-against-multiple-servers-simultaneously?view=sql-server-2017
Option 1. You can do it with code, using different methods depending on the environment variable and calling different store procedures or same name different schema.
Option 2. You can use Dynamic Queries. You could create a table to manage the configuration to what schema/table to use depending on the parameter you send from your server. Then just execute the dynamic query.
Ie.
DECLARE #Prefix VARCHAR(10) = 'dev';
DECLARE #Params NVARCHAR(200) = '#Prefix VARCHAR(10)'
, #Query NVARCHAR(MAX) = 'SELECT [Id] FROM [Schema].['+#Prefix+'TableName]';
EXECUTE [sp_executesql]
#Query ,
#Params ,
#Prefix = #Prefix

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.

Create a dynamic table in dynamic SQL and reference it outside of dynamic SQL

Please see the code below:
select top 1 * into #dbusers from dbusers
declare #tsql as varchar(1000)
set #tsql = 'select * from #dbusers'
exec (#tsql)
This works as I would expect i.e. one row is returned by the dynamic SQL. Is it possible to do this:
declare #tsql as varchar(1000)
set #tsql = 'select top 1 * into #dbusers from dbusers'
exec (#tsql)
select * from #dbusers
Here I get the error:
Invalid object name '#dbusers'
Is there a workaround?
I realise that you can have output parameters with dynamic SQL. However, I also know that when using stored procedures you cannot return tables as output parameters.
Is it possible to do this? Is there a workaround (except creating a physical table)?
Temporary tables are only available within the session that created them. With Dynamic SQL this means it is not available after the Dynamic SQL has run. Your options here are to:
Create a global temporary table, that will persist outside your session until it is explicitly dropped or cleared out of TempDB another way, using a double hash: create table ##GlobalTemp
Because this table persists outside your session, you need to make sure you don't create two of them or have two different processes trying to process data within it. You need to have a way of uniquely identifying the global temp table you want to be dealing with.
You can create a regular table and remember to drop it again afterwards.
Include whatever logic that needs to reference the temp table within the Dynamic SQL script
For your particular instance though, you are best off simply executing a select into which will generate your table structure from the data that is selected.

Dynamic Table from stored procedure

I'm trying to create a temp table from stored procedures, from this link
In the string he defines the sql server version. Our clients have different types of sql servers, from 2005 until 2012.
String: 'SQLNCLI', 'Server=(local)\SQL2008;Trusted_Connection=yes;','EXEC getBusinessLineHistory'
How can I use that command independently from sql server plataform
The OPENROWSET creates a dynamic link to a remote server.
http://technet.microsoft.com/en-us/library/ms190312.aspx
You can create a dynamic TSQL call to a dynamic link with changing parameters. Below is sample code. This can be converted into a store procedure with a #my_Server passed as a parameter.
Please note, this does not support multiple calls at the same time since only one table exists.
You can not use a local temp table since there might be a scoping issue with EXEC calling sp_executesql inside a stored procedure.
These are things you will need to research.
-- Set the server info
DECLARE #my_Server SYSNAME;
SET #my_Server = 'Server=(local)\SQL2008';
-- Clear the staging table
truncate table STAGE.dbo.MYTABLE;
-- Allow for dynamic server location
DECLARE #my_TSQL NVARCHAR(2048);
SET #my_TSQL =
'INSERT INTO STAGE.dbo.MYTABLE SELECT * FROM OPENROWSET(''SQLNCLI'',' + #my_TSQL +
';Trusted_Connection=yes;'', ''EXEC usp_My_Stored_Procedure'')';
-- Run the dynamic remote TSQL
exec sp_executesql #my_TSQL;

Selecting data from a different schema within a stored procedure

Consider this:
CREATE PROCEDURE [dbo].[setIdentifier](#oldIdentifierName as varchar(50), #newIdentifierName as varchar(50))
AS
BEGIN
DECLARE #old_id as int;
DECLARE #new_id as int;
SET #old_id = (SELECT value FROM Configuration WHERE id = #oldIdentifierName);
SET #new_id = (SELECT value FROM Configuration WHERE id = #newIdentifierName);
IF #old_id IS NOT NULL AND #new_id IS NOT NULL
BEGIN
UPDATE Customer
SET type = #new_id
WHERE type = #old_id;
END;
END
[...]
EXECUTE dbo.setIdentifier '1', '2';
What this does is create a stored procedure that accepts two parameters which it then uses to update a Customer table.
The problem is that the entire script above runs within a schema other than "dbo". Let's just assume the schema is "company1". And when the stored procedure is called, I get an error from the SELECT statement, which says that the Configuration table cannot be found. I'm guessing this is because MS SQL by default looks for tables within the same schema as the location of the stored procedure, and not within the calling context.
My question is this:
Is there some option or parameter or switch of some kind that will
tell MS SQL to look for tables in the "caller's default schema" and
not within the schema that procedure itself is stored in?
If not,
what would you recommend? I don't really want to prefix the tables
with the schema name, because it would be kind of unflexible to do
that. So I'm thinking about using dynamic sql (and the schema_name()
function which returns the correct value even within the procedure),
but I am just not experienced enough with MS SQL to construct the
proper syntax.
It would be a tad more efficient to explicitly specify the schema name. And generally speaking, schema's are mainly used to divide a database into logical area's. I would not anticipate on tables schema-hopping often.
Regarding your question, you might want to have a look at the 'execute as' documentation on msdn, since it allows to explicitly control your execution context.
I ended up passing the schema name to my script as a property on the command line for the "sqlcmd" command. Like this:
C:/> sqlcmd -vSCHEMANAME=myschema -imysqlfile
In the SQL script I can then access this variable like this:
SELECT * from $(SCHEMANAME).myTable WHERE.... etc
Not quite as flexible as dynamic sql, but "good enough" as it were.
Thanks all for taking time to respond.