Insert records across multiple databases in stored procedure - sql

I am attempting to create a stored procedure to insert records from a table in one DB to another DB, using fully qualified names for each table, however, since the tables can be in different DB's, the DB name needs to be able to change based on where a field is located. I attempted to set a variable and use the variable as the DB name, however, SQL does not allow this method.
Please supply me with any suggestions.
Below is a the query that I have to this point:
DECLARE #FromPractice varchar(5) = 'MCE'
DECLARE #ToPractice varchar(5) = 'CRRLL'
DECLARE #FromEnvironment varchar(5)
DECLARE #ToEnvironment varchar(5)
SET #FromEnvironment = ( select environment from practice where practice = #FromPractice)
SET #ToEnvironment = ( select environment from practice where practice = #ToPractice)
PRINT #FromEnvironment
Print #ToEnvironment
INSERT INTO [#ToEnvironment].dbo.Patinfo
(
---Fields
)
SELECT
---Values
FROM [#FromEnvironment].dbo.PatInfo
where practice = #FromPractice
and pat_num = 25970

Here is a rough example of how you could do this in dynamic sql. Please note that this is vulnerable to sql injection. You need to add some logic in your process prior to this to make sure that you are safe. The easiest way given the nature of this is to check sys.databases for your environment variables. You are likely going to need those variable to hold more than 5 characters unless your database names are awfully short.
declare #SQL nvarchar(max)
set #SQL = 'INSERT INTO [' + #ToEnvironment + '].dbo.Patinfo
(
--Fields
)
SELECT
--Values
FROM [' + #FromEnvironment + '].dbo.PatInfo
where practice = ''' + #FromPractice + '''
and pat_num = 25970'
--look before you execute
select #SQL
--When satisfied uncomment this
--exec sp_executesql #SQL

Related

Creating SQL Function to Create Multiple Tables

I am currently moving a SAS process to SQL. Within the SAS process, I leverage macros to create a multitude of tables.
I am trying to leverage the CREATE FUNCTION function within SQL to mimic this process, however I am stuck. I have three arguments, the server name, the name of the new table and the table where it should select from. I'm not sure what I should specify as what I am returning as I'm not looking to return anything, just create tables.
CREATE FUNCTION dbo.functionname (#server VARCHAR(250), #name VARCHAR(250), #table VARCHAR(250))
RETURN (???)
AS BEGIN
SELECT *
INTO #server.dbo.#nm
FROM #table
RETURN
END
This is what I have come up with so far. My SELECT statement wouldn't actually be *, I just put that for simplicity sake for this question.
UPDATE: In this instance, using a stored procedure is not an option as permissions have been limited.
You can create a dynamic SQL script as follows
declare #newtable sysname = 'T003',
#sourcetable sysname = 'sys.tables'
declare #sql nvarchar(max)
set #sql = N'select * into ' + #newtable + ' from ' + #sourcetable + ';'
set #sql = #sql + N'select * from ' + #newtable
exec sp_executesql #sql
Then you can use it in a stored procedure
To return data from new table, the table type must be known before. In this case it is not possible, so developer cannot create the function return type
Or create a function just to create the table and insert data into it. But return fail or success, etc
Then select from the new table using a dynamic SQL again

How to set 1 database name containing a specific table to variable?

So, a little backstory on this one:
I have a lengthy query that I use frequently for my jobs that utilizes the sp_MSForEachDB stored proc. The query creates a temporary table, and then goes through and searches sys.tables, and if a database has a table called T_Study, it will run the query and pull these results into the temporary table.
What I am now trying to do is join in another table from another database, and this database is of a different type. These database are all distinguished by the existence T_BatchStores. All databases that have a T_BatchStores table will also have a table dbo.T_TSyntax, and this is the table on which I will need to join.
Essentially, I am trying to build a universal query that I can run on any server containing this software, and I have these few universal tables, but the names of the databases themselves will vary.
So what I want to do is, in my query that populates my temporary table, add the following:
JOIN '+#MEDDB+'.dbo.T_TSyntax and then my ON clause, etc. Keep in mind that this join occurs within my begin and end clauses and that sp_MSforEachDb will be run on this.
I want #MEDDB to be just a randomly selected name of ANY database on their SQL instance that contains a T_BatchStores table - it does not matter what the name is - I just don't want to modify the query every time I run it.
How can I use my DECLARE #MEDDB and SET/SELECT #MEDDB to work with these requirements?
Also, in some reading, I read that I should use SYSNAME as the data type, but I also know NVARCHAR would work - but I'd just like a tip on which would be ideal.
I have tried the following:
DECLARE #MEDDB SYSNAME
SET #MEDDB = (SELECT TOP 1 name FROM sys.databases WHERE EXISTS (SELECT name FROM sys.tables WHERE name = '''T_BATCHSTORES'''))
SELECT #MEDDB
But this returns 1 row with a NULL value. I'm very much a beginner to SQL, so any assistance would be greatly appreciated!
Note: I am only using Microsoft SQL Server 2008 and 2012 at the present time.
Thanks!
Okay, after heavily modifying the query so that the names aren't actually exactly the same as our actual table structure, here is a slimmed-down version of the query I'm using so that you'll see what I'm going for:
/* Add or change variables as needed and comment back in WHERE statements in Insert section as needed.
You do not need to delete any variables in this section, even if you do not need them.
Simply comment in or out relevant data in the WHERE clause in Section 4. */
/* Section 1: Declaring variables. */
DECLARE #STUID NVARCHAR (65)
DECLARE #IMUID NVARCHAR (200)
DECLARE #ACCN NVARCHAR (100)
DECLARE #MEDDB NVARCHAR (255)
/* Section 2: Assigning values to variables such as an Image file's UID. */
SET #STUID = 'enterSTUID'
SET #IMUID = 'enterIMUIDhere'
SET #ACCN = 'enterACCNhere'
SET #MEDDB = (SELECT TOP 1 name FROM sys.databases WHERE [name] LIKE '%med%'
AND [name] NOT LIKE '%audit%')
/* Section 3: Creating our temporary table to dump our results. */
IF OBJECT_ID('tempdb.dbo.#tempBatchResultsD6') IS NULL
BEGIN
CREATE TABLE #tempBatchResultsD6
(
Database_Name VARCHAR (200),
THING1 VARCHAR(100) NOT NULL,
THING2 VARCHAR(200) NOT NULL,
THING3 DATETIME NOT NULL,
TSyntaxUID VARCHAR (66) NOT NULL,
TSyntaxDesc VARCHAR (128) NOT NULL
)
END
TRUNCATE TABLE #tempBatchResultsD6
/* Section 4: Query that will be used to populate the contents of the temporary table created above.
Utilizing the stored procedure "sp_MSForEachDb," this will search every database on the SQL instance.
Here, we are limiting our results to only searching specific databases by only returning results from databases that have a "T_Studies" table. */
DECLARE #Command NVARCHAR(MAX)
SET #Command = N'USE [?];
IF EXISTS (SELECT * FROM sys.tables WHERE [name] = ''T_Studies'')
BEGIN
INSERT #tempBatchResultsD6
SELECT
DB_Name () as Database_Name,
THING1,
THING2,
THING3,
TS.TSyntaxUID,
TS.TSyntaxDesc
FROM T_Studies ST WITH (nolock)
JOIN T_Patients PT WITH (nolock) ON ST.ST_PT_FOLDERID = PT.PT_FOLDERID
JOIN T_Series SE WITH (nolock) ON ST.ST_FOLDERID = SE.SE_ST_FOLDERID
JOIN T_Images IM WITH (nolock) ON SE.SE_FOLDERID = IM.IM_SE_FOLDERID
JOIN '+#MEDDB+'.dbo.T_TSyntaxes TS WITH (nolock) on IM.IM_TSyntaxID = TS.TSyntaxUID
WHERE ST.STUID = '''+#STUID+'''
--WHERE IM.IM_UID = '''+#IMUID+'''
--WHERE ST.ST_ACCNNO = '''+#ACCN+'''
END'
EXEC sp_MSForEachDb #Command
/* Section 5: Querying our temporary table to get our results. */
SELECT
Database_Name,
THING1,
THING2,
THING3,
TSyntaxUID,
TSyntaxDesc
FROM #tempBatchResultsD6
ORDER BY Database_Name
So as you can see, this is a massive temp table that will pull from all databases that have a T_Studies table in them. It's huge in its actual form, but this is trimmed down significantly.
The problem comes in section 2, where I am using #MEDDB to choose a random database name if the name contains the word "Med" but not the word "audit." This is problematic because it assumes consistent naming across all sites - and while these names are suggested, they are never a guarantee.
To guarantee consistency, I am trying to populate the #MEDDB variable with the name of ANY database on their system that contains a T_BatchStores table instead of the WHERE [name] like portion.
Any advice with this new info would be greatly appreciated!
I am not sure whether I understand your question completely.
But if you wanna loop through all databases, you can use something like the following code. "SELECT XXX" would then be your query, by using "use ' + #DB_Name + ';" you switch to the desired database to execute the your query.
DECLARE #DB_Name varchar(100)
DECLARE #Command nvarchar(max)
DECLARE database_cursor CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #DB_Name
WHILE ##FETCH_STATUS = 0
BEGIN
set #Command =
'
use ' + #DB_Name + ';
SELECT XXX
'
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #DB_Name
END
CLOSE database_cursor
DEALLOCATE database_cursor
DECLARE #MEDDB NVARCHAR(500)
SET #MEDDB = 'SELECT TOP 1 name FROM sys.databases WHERE EXISTS (SELECT name FROM sys.tables WHERE name = ''T_BATCHSTORES'')'
EXEC (#MEDDB)
This is Dynamic SQL. Of course, I'm not getting any results, either, but I'm not in your database. Still, this allows you to be 'dynamic', and use a variable to modify things:
DECLARE #MEDDB NVARCHAR(500)
DECLARE #MEDDB2 NVARCHAR(100) = 'T_BATCHSTORES'
SET #MEDDB = 'SELECT TOP 1 name FROM sys.databases WHERE EXISTS (SELECT name FROM sys.tables WHERE name = ''' + #MEDDB2 + ''')'
EXEC (#MEDDB)
My recommendation is that you run simpler queries that show what you want, and then you build them into the dynamic method.
For instance, I'm in AdventureWorks right now, so I can do:
SELECT * FROM sys.databases
This gives me a list of all the databases that are available in here.
So maybe I expand that to
SELECT * FROM sys.databases WHERE name = 'AdventureWorks'
This gives me just one line of results. So if that's what I want, I build it into the dynamic portion:
DECLARE #MEDDB NVARCHAR(500)
DECLARE #MEDDB2 NVARCHAR(100) = 'AdventureWorks'
SET #MEDDB = 'SELECT * FROM sys.databases WHERE name =''' + #MEDDB2 + ''''
EXEC (#MEDDB)
It just all depends what you're looking for. I always find the data I want first, and then figure out how to make my query look for it.

SQL Server stored procedure passing parameters into variables

I have a big query which works and I want to write a stored procedure for it.
I'm getting this error:
the OLE DB provider SQLNCLI11" for linked server "theServer" does not contain the table ""#dbName"."dbo"."tableName"
What I am trying to do:
create PROCEDURE [sys.sp_myProcedure]
(
#dbName varchar(30) output,
#rid varchar (10) output,
#mdate output
)
AS
BEGIN
declare #prt varchar(12)
declare #pid int
declare #cid int
--declare #rid int
declare #aid int
SET NOCOUNT ON;
set #cid= (select CID from theServer.[#dbName].dbo.tableName where RID= #rid)
set #pid= (select PID from theServer.[#dbName].dbo.tableName where RID= #rid)
set #aid= (select aid from theServer.[#dbName].dbo.tableName where RID= #rid)
--then my query begins
theServer.[#dbName].dbo.tablename is a linked server.
What I want to do is:
execute [sys.sp_myProcedure] 'someDbname', '123', '2012-03-03'
and the parameters passed here would set/update the variables #dbName, #rid, #mdate at runtime. ( #mdate I have it further away in the query, it's too big to adapt it with myTable and to change all the sensitive data).
How can I do this ?? (using SQL Server 2012)
edit (based on the comments and answers):
so, it's #thatString = '--insert the query here ' . Then, in my case how can i set those variables according to the parameters inside the query? Should i do it with replace? like this: set #thatString= replace(#thatString, dbName, #dbname) ?
**
edit 2
**
set #sql = '
use [someDbName];
use [123];
use [2012-03-03];
select ... '
set #sql = replace (#sql, 'someDbName', #dbName)
set #sql = replace (#sql, '123', #rid)
set #sql = replace (#sql, '2012-03-03', #mdate)
execute #sql
end
Did i get it right? is the execute #sql in the right place?
I'm asking cause it doesnt work. i'm getting the name ' --part of my query here' is not a valid identifier
Names of databases or other objects cannot be specified dynamically from variables. The workaround is to compose a dynamic SQL query in a string, into which you concatenate the required names, and then execute (#thatString).
(You might think you can employ use, but it is scoped such that you would have to include the rest of your query within the same executed string.)
--
Edit with more info as requested. You can compose the string however you like. If you need any more guidance, there are plenty of pages that discuss dynamic T-SQL. But hey, two ideas:
set #myDynamicQuery =
'
use [' + #myDynamicDatabase + '];
select BLAH from WHOM where DATA = ''what'';
';
or if you will be using the name a lot, you could reduce the hassle caused by breaking in and out of single quotes as follows - though I personally never use this as I don't like how it looks:
set #myDynamicQuery =
'
use [A_RARE_PLACEHOLDER];
select BLAH from WHOM where DATA = ''what'';
-- lots more uses of A_RARE_PLACEHOLDER
';
set #myDynamicQuery = replace(
#myDynamicQuery,
'A_RARE_PLACEHOLDER',
#myDynamicDatabase
);
Then execute (#myDynamicQuery);

T-SQL: Variable Scope

I am trying to store the results of an SQL query into a variable.The query simply detects the datatype of a column, hence the returned result is a single varchar.
SET #SQL =
'declare ##x varchar(max) SET ##x = (select DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = ' +char(39)+#TabName+char(39) +
' AND column_name = ' +char(39)+#colName+char(39) + ')'
EXECUTE (#SQL)
Anything within the 'SET declaration' cannot access any variables outside of it and vice versa, so I am stuck on how to store the results of this query in a varchar variable to be accessed by other parts of the stored procedure.
You dont need a dynamic query to achieve what you want, below query will give the same result as yours.
declare #x varchar(max)
declare #tableName varchar(100), #ColumnName varchar(50)
set #tableName = 'Employee'
set #ColumnName = 'ID'
select #x = DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
where
Table_Name = #tableName
and column_name = #ColumnName
select #x
All user-defined variables in T-SQL have private local-scope only. They cannot be seen by any other execution context, not even nested ones (unlike #temp tables, which can be seen by nested scopes). Using "##" to try to trick it into making a global-variable doesn't work.
If you want to execute dynamic SQL and return information there are several ways to do it:
Use sp_ExecuteSQL and make one of the parameters an OUTPUT parameter (recommended for single values).
Make a #Temp table before calling the dynamic SQL and then have the Dynamic SQL write to the same #Temp table (recommended for multiple values/rows).
Use the INSERT..EXEC statement to execute your dynamic SQL which returns its information as the output of a SELECT statement. If the INSERT table has the same format as the dynamic SQL's SELECT output, then the data output will be inserted into your table.
If you want to return only an integer value, you can do this through the RETURN statement in dynamic SQL, and receive it via #val = EXEC('...').
Use the Session context-info buffer (not recommended).
However, as others have pointed out, you shouldn't actually need dynamic SQL for what you are showing us here. You can do just this with:
SET #x = ( SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = #TabName
AND column_name = #colName )
You may want to consider using the sp_executesql stored procedure for dynamic sql.
The following link provides a good usage example of sp_executesql procedure with output parameters:
http://support.microsoft.com/kb/262499

SQL Variables as Column names in Where Clause [duplicate]

This question already has answers here:
Can I pass column name as input parameter in SQL stored Procedure
(9 answers)
Closed 4 years ago.
I need some help with my SQL logic, and I've been working (and researching) this for 2 days now with zero success.
My goal is to try an pass a variable from an ASP page to a stored procedure, which is utilizing the variable as criteria for a column name in the where clause.
So for example (a simplified version of my query):
#strDept nvarchar(10), #strUser nvarchar(30)
-- The asp page will pass f18 to #strDept & Ted Lee to strUser
-- f18 is the column name in my database that I need in the where.
select x, y, z from table1 where #strDept in (#strUser)
-- and this is the select statement, notice the where clause.
The stored procedure does execute, but it returns no values and I know its treating the #strDept as a literal nvarchar and not a column name.
So I guess my question is, how do I get SQL Server 2005 to treat my #sqlDept variable as a column name?
The reason you can't find guidance on how to do this is that it's a really bad idea.
Sooner or later, someone is going to pass a "column name" of 1 ;drop database badidea. Which will be a blessing for all concerned.
Read up on SQL Injection, and rethink your design.
If this is an internal company application why is everyone re-iterating and beating SQL Injection to death... Its very simple to just use Dynamic SQL.
If you are comfortable that these are only internal users using this then its very simple. Here is the concept. You essentially write a SQL Statement that writes a string that is really a SQL statement and then execute it.
CREATE Procedure myDynamicProcedure
#strDept nvarchar(10),
#strUser nvarchar(30)
as
BEGIN
1. Declare a variable to store the SQL Statement.
DECLARE #SQL varchar(max)
2. SET your #SQL Variable to be the SELECT Statement. Basically you are building it so it returns what you are wanting to write. Like this:
SET #SQL = 'select x, y, z from table1 where' + #strDept +
' in ' + #strUser
3. Execute the #SQL Statement and it will be exactly like you ran:
SELECT x,y,z from table1 where f18 = 'Ted Lee'
EXEC (#SQL)
END
Why do you want to make column name dynamic? What do you plan to achieve? You can use dynamic query like answer above but injection attacks may start.
If you explain what you want to do with that maybe we can recommend another solution.
You can use some dynamic sql e.g.
DECLARE #sqlDept VARCHAR(100)='CURRENT_TIMESTAMP';
EXEC('SELECT '+#sqlDept)
In your case this will be
DECLARE #strDept nvarchar(10)='dept1'
,#strUser nvarchar(30)='user1';
DECLARE #DynamicSql nvarchar(1000);
SET #DynamicSql='select x, y, z from table where '+#strDept+' in ('''+#strUser+''')';
Then
SELECT #DynamicSql;
Will give you:
select x, y, z from table where dept1 in ('user1')
To execute this statement you do this as
EXEC(#DynamicSql);
Another alternative is to use a small bit of substitution in the proc. This still uses dynamic SQL, but you are never executing user supplied values.
DECLARE #userSuppliedValue VARCHAR(50) = 'JOHNNY DROP TABLES'
DECLARE #substValue VARCHAR(50)
IF #userSuppliedValue = 'Table1'
SET #substValue = 'Table1'
IF #userSuppliedValue = 'Table2'
SET #substValue = 'Table2'
/*Repeat for N permutations*/
/* Throw an error if you think its necessary to do so when no match is found*/
IF #substValue IS NULL
RAISERROR(1,1,'errah')
EXEC ('SELECT * FROM ' + #substValue)
I think the best way is to build a dynamic SQL and add a lookup to see if the column exist and prevent SQL injection in the column name.
declare #strDept nvarchar(10), #strUser nvarchar(30),
#sql nvarchar(300), #found smallint
set #strDept = 'f18'
set #strUser = 'Ted Lee'
set #found = (SELECT count(*)
FROM syscolumns
WHERE id=OBJECT_ID('table1') AND name=''+#strDept+'')
set #sql = 'select x, y, z from table1 where ' + #strDept + ' in ('''+#strUser+''')'
if #found = 1 exec (#sql)
SQL injection testing : See SQL FIDDLE : http://www.sqlfiddle.com/#!6/df3f6/18/0
DECLARE #value varchar(10)
SET #value = 'intStep'
DECLARE #sqlText nvarchar(1000);
SET #sqlText = N'SELECT ' + #value + ' FROM dbo.tblBatchDetail'
Exec (#sqlText)