Programmatically Set Table Name in the FROM Clause - sql

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

Related

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.

Linked Server Query / Dynamic 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)

Stored procedure, pass table name as a parameter

I have about half a dozen generic, but fairly complex stored procedures and functions that I would like to use in a more generic fashion.
Ideally I'd like to be able to pass the table name as a parameter to the procedure, as currently it is hard coded.
The research I have done suggests I need to convert all existing SQL within my procedures to use dynamic SQL in order to splice in the dynamic table name from the parameter, however I was wondering if there is a easier way by referencing the table in another way?
For example:
SELECT * FROM #MyTable WHERE...
If so, how do I set the #MyTable variable from the table name?
I am using SQL Server 2005.
Dynamic SQL is the only way to do this, but I'd reconsider the architecture of your application if it requires this. SQL isn't very good at "generalized" code. It works best when it's designed and coded to do individual tasks.
Selecting from TableA is not the same as selecting from TableB, even if the select statements look the same. There may be different indexes, different table sizes, data distribution, etc.
You could generate your individual stored procedures, which is a common approach. Have a code generator that creates the various select stored procedures for the tables that you need. Each table would have its own SP(s), which you could then link into your application.
I've written these kinds of generators in T-SQL, but you could easily do it with most programming languages. It's pretty basic stuff.
Just to add one more thing since Scott E brought up ORMs... you should also be able to use these stored procedures with most sophisticated ORMs.
You'd have to use dynamic sql. But don't do that! You're better off using an ORM.
EXEC(N'SELECT * from ' + #MyTable + N' WHERE ... ')
You can use dynamic Sql, but check that the object exists first unless you can 100% trust the source of that parameter. It's likely that there will be a performance hit as SQL server won't be able to re-use the same execution plan for different parameters.
IF OBJECT_ID(#tablename, N'U') IS NOT NULL
BEGIN
--dynamic sql
END
ALTER procedure [dbo].[test](#table_name varchar(max))
AS
BEGIN
declare #tablename varchar(max)=#table_name;
declare #statement varchar(max);
set #statement = 'Select * from ' + #tablename;
execute (#statement);
END

How do I supply the FROM clause of a SELECT statement from a UDF parameter

In the application I'm working on porting to the web, we currently dynamically access different tables at runtime from run to run, based on a "template" string that is specified. I would like to move the burden of doing that back to the database now that we are moving to SQL server, so I don't have to mess with a dynamic GridView. I thought of writing a Table-valued UDF with a parameter for the table name and one for the query WHERE clause.
I entered the following for my UDF but obviously it doesn't work. Is there any way to take a varchar or string of some kind and get a table reference that can work in the FROM clause?
CREATE FUNCTION TemplateSelector
(
#template varchar(40),
#code varchar(80)
)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM #template WHERE ProductionCode = #code
)
Or some other way of getting a result set similar in concept to this. Basically all records in the table indicated by the varchar #template with the matching ProductionCode of the #code.
I get the error "Must declare the table variable "#template"", so SQL server probably things I'm trying to select from a table variable.
On Edit: Yeah I don't need to do it in a function, I can run Stored Procs, I've just not written any of them before.
CREATE PROCEDURE TemplateSelector
(
#template varchar(40),
#code varchar(80)
)
AS
EXEC('SELECT * FROM ' + #template + ' WHERE ProductionCode = ' + #code)
This works, though it's not a UDF.
The only way to do this is with the exec command.
Also, you have to move it out to a stored proc instead of a function. Apparently functions can't execute dynamic sql.
The only way that this would be possible is with dynamic SQL, however, dynamic SQL is not supported by SqlServer within a function.
I'm sorry to say that I'm quite sure that it is NOT possible to do this within a function.
If you were working with stored procedures it would be possible.
Also, it should be noted that, be replacing the table name in the query, you've destroyed SQL Server's ability to cache the execution plan for the query. This pretty much reduces the advantage of using a UDF or SP to nil. You might as well just call the SQL query directly.
I have a finite number of tables that I want to be able to address, so I could writing something using IF, that tests #template for matches with a number of values and for each match runs
SELECT * FROM TEMPLATENAME WHERE ProductionCode = #code
It sounds like that is a better option
If you have numerous tables with identical structure, it usually means you haven't designed your database in a normal form. You should unify these into one table. You may need to give this table one more attribute column to distinguish the data sets.

Cross-database queries with different DB names in different environments?

How would you handle cross database queries in different environments. For example, db1-development and db2-development, db1-production and db2-production.
If I want to do a cross-database query in development from db2 to db1 I could use the fully qualified name, [db1-development].[schema].[table]. But how do I maintain the queries and stored procedures between the different environments? [db1-development].[schema].[table] will not work in production because the database names are different.
I can see search and replace as a possible solution but I am hoping there is a more elegant way to solve this problem. If there are db specific solutions, I am using SQL Server 2005.
Why are the database names different between dev and prod? It'd, obviously, be easiest if they were the same.
If it's a single table shared, then you could create a view over it - which only requires that you change that view when moving to production.
Otherwise, you'll want to create a SYNONYM for the objects, and make sure to always reference that. You'll still need to change the SYNONYM creation scripts, but that can be done in a build script fairly easily, I think.
For this reason, it's not practical to use different names for development and production databases. Using the same db name on development, production, and optionally, acceptance/Q&A environments, makes your SQL code much easier to maintain.
However, if you really have to, you could get creative with views and dynamic SQL. For example, you put the actual data retrieval query inside a view, and then you select like this:
declare #environment varchar(10)
set #environment = 'db-dev' -- input parameter, comes from app layer
declare #sql varchar(8000)
set #sql = 'select * from ' + #environment + '.dbo.view'
execute(#sql)
But it's far from pretty...