Can you use #Temp table in dynamic SQL when in OPENROWSET? - sql-server

I am trying to use #Temp tables in my dynamic SQL in Microsoft SQL Server 2014, and it works fine locally, but if I put it inside an OPENROWSET so I can gather data from another server, it gets error:
OLE DB provider "SQLNCLI11" for linked server "(null)" returned
message "Deferred prepare could not be completed.". Msg 8180, Level
16, State 1, Line 9 Statement(s) could not be prepared. Msg 208, Level
16, State 1, Line 9 Invalid object name '#RefmdCodeList'
Is that just not allowed? Or is there some different way I need to do it?
To simplify the issue, here are two examples.
THIS WORKS:
declare #sql nvarchar(max)
set #sql = N' select * from #RefmdCodeList'
exec(#sql)
THIS DOES NOT WORK:
declare #sql nvarchar(max)
set #sql = N' SELECT * FROM
OPENROWSET(''SQLNCLI'',
''Server=' + #PAFServer + ';Database=' +#PAFDatabase +';UID=p2kservices;PWD=P0werP#th!'',
N'' SELECT * from #RefmdCodeList'')'
exec(#sql)
For people who might ask, below is what I'm really trying to do. If I remove the #Temp tables, my code as written below runs fine. With the #Temp tables included, I get error message above.
declare #sql nvarchar(max)
set #sql = N' SELECT * FROM OPENROWSET(''SQLNCLI'', ''Server=' + #PAFServer + ';Database=' +#PAFDatabase +';UID=p2kservices;PWD=P0werP#th!'',
N'' ;WITH spec_data (entity, accession_no, acc_id, spec_id, spec_description, spec_label, spec_sc_code) AS
(SELECT ''''PAMF'''' AS entity, a.accession_no, a.id, asp.id, asp.description, asp.specimen_label, substring(''''88''''+spec_sc.code, 1,5)
FROM accession_2 a (NOLOCK)
JOIN acc_specimen asp (nolock) ON asp.acc_id = a.id
LEFT OUTER JOIN acc_charges spec_ac (NOLOCK) on spec_ac.acc_id = asp.acc_id
AND spec_ac.rec_id = asp.id AND spec_ac.rec_type = ''''S''''
LEFT OUTER JOIN service_code spec_sc (NOLOCK) on spec_sc.id = spec_ac.service_code_id
JOIN acc_refmd aref (NOLOCK) ON aref.acc_id = a.id
WHERE recv_date >= ''''+CONVERT(VARCHAR(10),#SDate, 101) +'''' and recv_date < ''''+CONVERT(VARCHAR(10),#EDate, 101)+''''
AND aref.refmd_id in (SELECT id from #RefmdCodeList)
)
SELECT DISTINCT entity accession_no, asp.acc_id, spec_id, spec_description, spec_label,
spec_sc_code, substring(''''88''''+lab_sc.code, 1,5)
FROM spec_data asp
LEFT OUTER JOIN acc_charges lab_ac (NOLOCK) on lab_ac.acc_id = asp.acc_id and rec_type = ''''L''''
JOIN service_code lab_sc (NOLOCK) on lab_sc.id = lab_ac.service_code_id
JOIN acc_order ord (NOLOCK) on ord.id = lab_ac.rec_id
AND ord.acc_specimen_id = asp.spec_id
WHERE substring(''''88''''+spec_sc.code, 1,5) in (SELECT service_code from #ServiceCodeList)
OR substring(''''88''''+lab_sc.code, 1,5) in (SELECT service_code from #ServiceCodeList)
'') '

Is that just not allowed?
A #temp table is visible only to the current running session. Even openrowset() to the local server will not see the #temp table because openrowset executes on a different connection(& another session).
Or is there some different way I need to do it?
You could populate a staging table on the remote server and use that staging table pretending/as if it was a #table. Generic staging tables (with an int column and a varchar column) for passing through values work pretty well. In order to differentiate between parallel processes the staging table could have a sessionid column (which holds the ##spid on the local side).
For inspiration....
--local temp table
create table #localtemp
(
srvcode varchar(100)
);
insert into #localtemp(srvcode)
select ##servername + '-' + name
from sys.objects;
--populate a proxy table on the remote server
declare #foo int;
select #foo = objectid
from openrowset('SQLNCLI', 'Server=Universe;Trusted_Connection=yes',
'set fmtonly off;
if object_id(''tempdb.dbo.proxyservicetable'') is null
begin
create table tempdb.dbo.proxyservicetable
(
sessionid smallint,
servicecode varchar(100)
);
create clustered index uclxproxyservicetable on tempdb.dbo.proxyservicetable(sessionid);
end
select object_id(''tempdb.dbo.proxyservicetable'') as objectid;
');
select #foo as stagingtableobjectid --on the remoteservers
--cleanup any staging leftovers from previous executions
delete from
openrowset('SQLNCLI', 'Server=Universe;Trusted_Connection=yes',
'
select *
from tempdb.dbo.proxyservicetable;
')
where sessionid = ##spid;
--populate staging with values from the current execution (#localtemp as source)
insert into
openrowset('SQLNCLI', 'Server=Universe;Trusted_Connection=yes',
'
select *
from tempdb.dbo.proxyservicetable;
')
select ##spid, srvcode
from #localtemp;
--passthrough query, at the remote server
declare #sql nvarchar(max)
select #sql = 'select *
from openrowset(''SQLNCLI'', ''Server=Universe;Trusted_Connection=yes'',
'' select ##servername,* from tempdb.dbo.proxyservicetable where sessionid =' + cast(##spid as varchar(10)) + '''
)';
exec(#sql);
go
drop table #localtemp
go

Related

How to join sys.databases, sys.tables and sys.columns

I have to check for value existence in a subset of tables in a subset of databases of a sql server instance. Beware I need to do this because I have 30 databases with same schema name and similar structure. Querying all databases separately is a waste of time.
The query generates correctly code for existing tables, but the additional check for column existence in table fails.
The column in some tables does not exist so the generated code must not include queries on tables without this column.
To solve this I need to realiably find a way to join sys.databases with sys.tables and then sys.columns. Or an alternative way to query all the required databases in a time saving manner.
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
/*tables common root,
all tables i need to query start with this prefix and a number between 1 and 50
and some resulting tables do not exist
ex: dbo.Z_WBL_ASCHEDA23 exist in wbcto, while dbo.Z_WBL_ASCHEDA23 does not exist in db wbgtg
*/
DECLARE #TableName NVARCHAR(200)
SELECT #TableName = 'dbo.Z_WBL_ASCHEDA'
DECLARE #SQL NVARCHAR(MAX)
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 50
)
SELECT #SQL = STUFF((
SELECT CHAR(13)+'SELECT COUNT(1), ''' + db.name + ''', '''+
#TableName+CONVERT(VARCHAR, n.n)+''' FROM ' +#TableName+CONVERT(VARCHAR, n.n)
+ ' WHERE COALESCE(s_dettagli,'''') = ''CONTROLLATO'' '
+CHAR(13)
FROM sys.databases db
INNER JOIN n ON 1=1
INNER JOIN sys.tables t ON OBJECT_ID(db.name + '.' + #TableName+CONVERT(VARCHAR, n.n)) IS NOT NULL
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID and c.name = 's_dettagli'
/*join on columns not working, generates sql for tables without 's_dettagli' column and query fails*/
WHERE db.name like 'wb%' --check only databases starting with 'wb'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
select #SQL
INSERT INTO #temp (exist, DB, tbname)
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
where exist <> 0
EDIT: adding some sql generated from query
SELECT COUNT(1), 'wb360', 'dbo.Z_WBL_ASCHEDA23' FROM wb360.dbo.Z_WBL_ASCHEDA23 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
SELECT COUNT(1), 'Wbbim', 'dbo.Z_WBL_ASCHEDA32' FROM Wbbim.dbo.Z_WBL_ASCHEDA32 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
the table of first query doesn't contain 's_dettagli' column
EDIT2: SOLUTION
EXEC sp_MSforeachdb '
IF ''?'' not like ''wb%''
RETURN
USE [?]
EXEC sp_MSforeachtable
#replacechar = ''!'',
#command1 = ''SELECT ''''?'''' AS db_name, ''''!'''' AS table_name, COUNT(*) FROM ! '',
#whereand = '' And Object_id In (
Select t.Object_id
From sys.objects t
INNER JOIN sys.columns c on c.Object_id = t.Object_id
Where t.name like ''''Z_WBL_ASCHEDA%''''
AND c.name = ''''s_dettagli'''' )'' '
Sys.columns can be joined to sys.tables using the object_id field (the object_id is the representation of the table itself).
sys.tables is run in the context of the database you are querying, hence you cannot see a table contained in another database. sys.databases can be run on any database on an instance and allow you to view other databases on the same instance. As such you don't need to join the table to the database (also the reason why there is no database_id field within sys.tables).
I hope that helps. Any clarification please let me know.
I would suggest alternative ways:
use registered Servers in SSMS and run the script on each database here
use exec sys.sp_MSforeachdb here
use sqlcmd and powershell to switch databases
I believe this script can help you :
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
DECLARE #SchemaName NVARCHAR(200)
DECLARE #TableName NVARCHAR(200)
DECLARE #ColumnName NVARCHAR(200)
DECLARE #SearchText NVARCHAR(200)
DECLARE #DBNameStartWith NVARCHAR(200)
DECLARE #SQL NVARCHAR(MAX)
SET #DBNameStartWith = 'wb'
SET #SchemaName = 'dbo'
SET #TableName = 'Z_WBL_ASCHEDA'
SET #ColumnName = 's_dettagli'
SET #SearchText = 'CONTROLLATO'
DECLARE #DatabaseName varchar(100)
DECLARE Crsr CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
WHERE name LIKE ''+#DBNameStartWith+'%'
OPEN Crsr
FETCH NEXT FROM Crsr INTO #DatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
IF ISNULL((SELECT COUNT(1) FROM SYS.TABLES T,SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name=#TableName AND C.name=#ColumnName),0)>0
BEGIN
SET #SQL = '
IF EXISTS (SELECT 1 FROM '+#DatabaseName+'.SYS.TABLES T,'+#DatabaseName+'.SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name='''+#TableName+''' AND C.name='''+#ColumnName+''')
BEGIN
SELECT COUNT(1),'''+#DatabaseName+''','''+#TableName+'''
FROM '+#DatabaseName+'.'+#SchemaName+'.'+#TableName+'
WHERE '+#ColumnName+'=''' +#SearchText+'''
END'
PRINT(#SQL)
INSERT INTO #Temp
EXEC sp_executesql #SQL
END
FETCH NEXT FROM Crsr INTO #DatabaseName
END
CLOSE Crsr
DEALLOCATE Crsr
SELECT * FROM #Temp

Insert into tables based on updating list (table)

Ive been struggling with this one for a while and appreciate any help.
I have a table that continuously get updated with a list of table names (same table can occure several times) that has been updated with new data recently in Database 1.
I want to create a query that checks this update list and inserts the data from updated tables in Database 1, into corresponding tables in Database 2. And loops through until the end of the list.
The list can look like this:
ID Table TimeStamp
----------------------- -------- -----------------------
0313778E-CB68-E811-910D Customer 2018-07-10 13:27:28.567
0313778E-CB68-E811-910D Customer 2018-07-10 13:28:58.010
194DD17A-CE68-E811-910D Order 2018-07-10 13:27:28.567
0EBB391D-126B-E811-910D Product 2018-07-10 13:28:58.010
4AAE33A5-CE68-E811-910D Customer 2018-07-10 13:27:28.567
DFA2A68C-056B-E811-910D Order 2018-07-10 13:28:58.010
C2CFECB6-CE68-E811-910D Employee 2018-07-10 13:27:28.583
To make it worse, the tables in Database 2 don't have same amount of columns as Database 1.
Ive been working on both MERGE and Cursor, as well as dynamic SQL. Im new to these so keep getting stuck. I think dynamic SQL + CURSOR is the best way to go. All of this will result in a stored procedure. Maybe there is a better way of doing it? Anyway, this is what I have:
Declare #Source_TableName_Column --this one contains the Database 1 tables as well as the correct columns needed to fill matching table in Database 2.
Declare #InsertInto NVARCHAR(MAX),
#TargetTable NVARCHAR(MAX)='Select name from Database2.sys.all_objects', --list of tables I have in Database 2
#Columns NVARCHAR(MAX) = 'Select name from Database2.sys.all_columns', --list of columns I have in Database 2 (same names as it is in SourceTable in Database 1)
;
DECLARE TableInsert CURSOR FOR
SELECT distinct SourceTableName from Database3.dbo.UpdateTableList
OPEN TableInsert
FETCH NEXT FROM TableInsert
INTO #TableName
--LOOP
WHILE ##FETCH_STATUS=0
BEGIN
SET #InsertInto = 'insert into Database2.dbo.'+#TargetTable+' '+(#Columns)+' values'+(....)+'' --not sure how to do this variable where i get all the results from SourceTable matching columns in TargetTable in database 2
FETCH NEXT FROM TableInsert
INTO #TableName
END
--CLOSE CURSOR
CLOSE TableInsert
DEALLOCATE TableInsert
This is complex. I would approach it in a simpler fashion than what you are doing. I’d do it your way if scenario involved destination tables with the same schema, but numerous columns, which does not seem to be the case with you.
Here’s what i’d do.
Open cur1
Fetch next from cur1 into #tablename ....
While ##fetchstatus = 0
Begin
If ##tablename= ‘tbl1’
Begin
Insert into database2.dbo.tbl2 values
Select ... from database1.dbo.table1 where ..
End
If ##tablename = ‘tblx’
And so on
Fetch next from cur1 into ...
End
I made a solution that works for me. Some names I have changed here to better fit my description above. I think they are correct, will look through it later and correct it if wrong =)
I am, however, a bit unsure about the delete statement further down in the code.
I have a TempTable based on the update list. I want to delete the rows in the update list after Ive inserted into tables. I have one with two 'where field IN (corresponding field in TempTable' clauses and another one with a 'delete from ... where exists (tempTable). Temptable has two columns but update list has three in total. Whichever is fastest/best?
DECLARE #InsertInto NVARCHAR(MAX);
DECLARE #SourceID NVARCHAR(MAX);
DECLARE #TableAttribute NVARCHAR(MAX);
DECLARE #SourceViewName NVARCHAR(MAX);
DECLARE #TargetTable NVARCHAR(MAX);
DECLARE #SourceTableName NVARCHAR(MAX);
/*-------Create temp table to be used in cursor-------*/
Declare #SQL_TempTable_Insert NVARCHAR(MAX);
IF OBJECT_ID('tempdb..#Cursor_TempTable') IS NOT NULL DROP TABLE #Cursor_TempTable
CREATE TABLE #Cursor_TempTable (
SourceEntity NVARCHAR(MAX) )
/*-------variable to be used in insert step below-------*/
SET #SQL_TempTable_Insert = 'SELECT SourceLogicalName FROM DataBaseC.dbo.REF_ENTITIES_SYNC group by SourceLogicalName'
/*-------Insert into temp table-------*/
INSERT INTO #Cursor_TempTable EXECUTE (#SQL_TempTable_Insert)
/*-------Create temp table from NeworUpdate table-------*/
Declare #SQL_TempTable_NewOrUpdated NVARCHAR(MAX);
IF OBJECT_ID('tempdb.. #TempTable_NewOrUpdated') IS NOT NULL DROP TABLE #TempTable_NewOrUpdated
CREATE TABLE #TempTable_NewOrUpdated (
[ID] NVARCHAR(MAX),
[TimeStamp] DATETIME )
/*-------variable to be used in insert step below in NewOrUpdate temp table-------*/
SET #SQL_TempTable_NewOrUpdated = 'SELECT ID, TimeStamp FROM DataBaseC.dbo.[REF_POSTS_NewOrUpdated] group by ID, TimeStamp'
/*-------Insert into NewOrUpdate temp table-------*/
INSERT INTO #TempTable_NewOrUpdated EXECUTE (#SQL_TempTable_NewOrUpdated)
/*-------Cursor segment-------*/
DECLARE EntitiesInsert CURSOR FOR
SELECT SourceEntity FROM #Cursor_TempTable
OPEN EntitiesInsert
FETCH NEXT FROM EntitiesInsert
INTO #TargetTable
--LOOP
WHILE ##FETCH_STATUS=0
BEGIN
BEGIN TRY
BEGIN TRAN
SET #SourceViewName = (select SourceName from DataBaseC.dbo.REF_ENTITIES_SYNC where Targetname = #TargetTable);
SET #SourceTableName = (select SourceTableName from DataBaseC.dbo.REF_ENTITIES_SYNC where Targetname = #TargetTable);
SET #TableAttribute = stuff(( select ', ' +char(10)+ ac.[name] from DataBaseB.sys.all_columns ac
inner join DataBaseB.sys.all_objects ao on ao.object_id=ac.object_id
where ao.name = #TargetTable and ac.name not in ('ValidFrom','ValidTo')
FOR XML PATH('')
), 1, 1, '')
--Finds DatabaseA table's Primary Key
SET #SourceID = (select c.name
from sys.index_columns ic
inner join sys.columns c on ic.object_id = c.object_id and ic.column_id = c.column_id
inner join sys.indexes i on ic.object_id = i.object_id and ic.index_id = i.index_id
inner join sys.tables t on i.object_id = t.object_id
inner join sys.schemas s on t.schema_id = s.schema_id
where i.is_primary_key= 1 and t.name = #SourceTableName);
SET #InsertInto = 'INSERT INTO DataBaseB.dbo.'+#TargetTable+' ('+#TableAttribute+')
SELECT '+#TableAttribute+' FROM DataBaseA.dbo.'+#SourceViewName+'
where '+#SourceID+' in (select nu.ID from DataBaseC.Inno.REF_ENTITIES_SYNC sync inner join #TempTable_NewOrUpdated nu on nu.SourceEntity = sync.TargetName where sync.TargetName = '''+#TargetTable+''' group by nu.ID )'
EXEC sp_sqlexec #insertInto
--Delete the records from [DataBaseC].[dbo].[REF_POSTS_NewOrUpdated] that we have inserted.
--DELETE FROM [DataBaseC].[dbo].[REF_POSTS_NewOrUpdated]
-- WHERE ID = (select [ID] from #TempTable_NewOrUpdated)
-- AND TimeStamp = (select [Timestamp] from #TempTable_NewOrUpdated)
----alt2
--DELETE FROM [DataBaseC].[dbo].[REF_POSTS_NewOrUpdated]
-- where exists (select * from #TempTable_NewOrUpdated)
--End TRAN
COMMIT
--End TRY
END TRY
--Catch possible errors
BEGIN CATCH
--IF there is an open transaction then roll back and print error messages.
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
DECLARE #ErrorNumber INT = ERROR_NUMBER();
DECLARE #ErrorLine INT = ERROR_LINE();
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE #ErrorSeverity INT = ERROR_SEVERITY();
DECLARE #ErrorState INT = ERROR_STATE();
PRINT 'Actual error number: ' + CAST(#ErrorNumber AS VARCHAR(10));
PRINT 'Actual line number: ' + CAST(#ErrorLine AS VARCHAR(10));
PRINT 'Actual error message: ' + CAST(#ErrorMessage AS VARCHAR(MAX));
PRINT 'Actual error Severity: ' + CAST(#ErrorSeverity AS VARCHAR(MAX));
PRINT 'Actual error State: ' + CAST(#ErrorState AS VARCHAR(MAX));
END CATCH
FETCH NEXT FROM EntitiesInsert
INTO #TargetTable
END
CLOSE EntitiesInsert
DEALLOCATE EntitiesInsert
GO

How to insert with table creation

I am looking for a more appropriate way to execute several inserts into a nonexistent table.
To create the table beforehand is not easily possible, as I don't know the data type of the selected column.
An "insert with create" would do, but I don't think there is anything like that.
Is there any better way to do so than to select into and then to insert?
Here is the "bad" way I do it, in an example very much stripped down to demonstrate the problem.
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = ''
declare test cursor for
select top 10 a.name from sys.tables a inner join sys.columns b on a.object_id = b.object_id --and b.name = 'description'
open test
fetch next from test into #name
while (##FETCH_STATUS <> -1)
begin
if #i = 0 begin
set #sql = 'select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
else begin
set #sql = 'insert #t1 select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
set #i = #i + 1
fetch next from test into #name
end
close test
deallocate test
if object_id ('tempdb..#t1') is not null select * from #t1
This solution is "bad" as you need the statement at two positions. In the case shown here this is trivial, but when the statement gets more complex this can become an issue.
You can simplify your query into this one:
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = N''
if object_id ('tempdb..#t1') is not null DROP TABLE #t1
;WITH cte AS (
select top 10 a.[name]
from sys.tables a
inner join sys.columns b
on a.object_id = b.object_id --and b.name = 'description'
)
SELECT #sql = #sql + N'UNION ALL
select distinct top 10 description
from ' + QUOTENAME([name]) + CHAR(13)
FROM cte
SELECT #sql = N';WITH cte AS (' + STUFF(#sql,1,10,') SELECT * INTO #t1 FROM cte')
PRINT #sql
--EXEC (#sql)
select * from #t1
No cursor or while loop;
Temporary table is dropped (if exists) before query execution;
You got a weird query, as for now it takes the first table from sys.tables and SELECT TOP 10 Descriptions from this table as many times as there are columns in this table.
The SELECT INTO statement copies data from one table into a new table, this might help you.
Example:-
SELECT *
INTO newtable
FROM oldtable
WHERE condition
The above also supports joins.

SQL Update Join Multiple Tables (if field exists)

This is my first post to StackOverflow. I've been using this amazing resource for a number of years to answer hundreds of SQL and PowerShell questions, however this one has me stumped for a number of days.
I am using SQL Server 2014 SP2 and I am trying to do an update to DATABASE1, FIELD1, then FIELD2 then FIELD3 from multiple other database.
FIELD1 may exist in one of multiple other databases.
FIELD1 may not exist in ALL databases - which is where I have the problem.
Database Design Link
I have the following (anonymised) query and it appears to be working:
EXEC sp_MSforeachdb 'IF ''?'' IN (''DATABASE2'',''DATABASE3'',''DATABASE4'')
BEGIN
UPDATE DATABASE1.PARAMETER
SET B.[VALUE] = A.[FIELD1]
FROM DATABASE1.TABLE1 B
INNER JOIN ?.dbo.[TABLE2] A
ON A.JOINVALUE = B.JOINVALUE
WHERE B.COLUMN2 = ''SOMETHING''
AND COLUMN3= ''PF.T.FIELD1''
END ;'
Until I get to say FIELD8, as it exists in DATABASE1 but not in DATABASE2, DATABASE3 or DATABASE4. I then get the following error:
Msg 207, Level 16, State 1, Line 30
Invalid column name 'FIELD8'.
From my Google and StackOverflow searches, I've tried to use (for the first time) a:
IF EXISTS (SELECT COLUMN1 FROM Database2.Table2 WHERE Column1='Field8')
EXEC .......
But that's where I started to really struggle.
Hope the above makes sense.
Any tips or assistance would be greatly appreciated.
N.B. I have about 3,000 fields in Database1 which require updating. I've so-far built all my UPDATE statements dynamically.
You can create stored proc, that will search for columns and tables in system tables:
ALTER PROCEDURE dbo.check_table_exists
-- Add the parameters for the stored procedure here
#table_name nvarchar(255),
#column_name nvarchar(255)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #SQLString nvarchar(max),
#ParmDefinition nvarchar(500) = N'#table_name nvarchar(255), #column_name nvarchar(255)';
IF OBJECT_ID(N'tempdb..#check_column_exists') is not null DROP TABLE #check_column_exists
CREATE TABLE #check_column_exists (
db nvarchar(500) NULL,
column_exists bit NULL
)
SELECT #SQLString =
(
SELECT N'USE '+QUOTENAME([name]) +'; '+
'INSERT INTO #check_column_exists '+
'SELECT '''+[name]+''' as [db], '+
' COUNT(*) as column_exists ' +
'FROM sys.tables t ' +
'INNER JOIN sys.columns c ' +
' ON t.[object_id] = c.[object_id] ' +
'WHERE t.[name] = #table_name and c.[name] = #column_name; '
FROM sys.databases
WHERE [name] NOT IN (
'msdb',
'model',
'tempdb',
'master'
)
FOR XML PATH('')
) + 'SELECT [db] FROM #check_column_exists WHERE column_exists = 1; DROP TABLE #check_column_exists;'
EXEC sp_executesql #SQLString, #ParmDefinition, #table_name = #table_name, #column_name = #column_name
END
GO
You can change it to search only for columns and output the database and table name or whatever.
The output is:
db
-----------
DATABASE1
DATABASE4
...
etc
After that you can write this to table and use for dynamic SQL update query:
DECLARE #table_name nvarchar(255) = 'SomeTable',
#column_name nvarchar(255) = 'SomeField'
DECLARE #results TABLE (
db nvarchar(500)
)
INSERT INTO #results
EXEC dbo.check_table_exists #table_name, #column_name
--...Here goes building of dynamic SQL query to update data
First, sp_MSforeachdb is not reliable. For a working alternative, check here: Making a more reliable and flexible sp_MSforeachdb - Aaron Bertrand
Second, you can use system views to check if a column exists in a given table using sys.columns like so:
if exists (
select 1
from sys.columns c
where c.name = 'pilots_id' /* column name */
and c.object_id = object_id(N'pilots') /* table name */
)
begin
select 'Pilots_Id exists' /* do stuff */
end
rextester demo: http://rextester.com/UUXCB18567

Find out which database a stored procedure is in

I have a list of stored procedure ids and names and i need to find out which database in the server they are located. Is there an easy way to do this? Like a system table that stores this information?
Thanks
Generally for this type of query you would need to loop through the list of databases (perhaps using sp_MSforeachdb) and query the individual system tables in each database.
The below might work for you though which avoids this. The first method checks object_id and name but doesn't do any validation that the objects are actually stored procedures.
The second one just uses name as requested in the comments, does also validate object type, but only checks the default schema.
WITH objects(name, id)
AS (SELECT 'uspGetBillOfMaterials', 23671132 UNION ALL
SELECT 'uspPrintError', 37575172) SELECT 'Using Id and Name',
sys.databases.name,
objects.name
FROM sys.databases,
objects
WHERE OBJECT_NAME(id, database_id) = objects.name
UNION ALL
SELECT 'Using Name (assumes default schema)',
sys.databases.name,
objects.name
FROM sys.databases,
objects
WHERE OBJECT_ID(databases.name + '..uspGetBillOfMaterials', 'P') IS NOT NULL
Give this a whirl.
DROP TABLE #Databases
CREATE TABLE #Databases (ID INTEGER IDENTITY (0,1), DbName NVARCHAR(128))
INSERT INTO #Databases(DbName)
SELECT name FROM sys.databases
DECLARE #CurrentID INTEGER = 0,
#MaxID INTEGER = (SELECT MAX(ID) FROM #Databases)
DECLARE #DbName NVARCHAR(128) = (SELECT DbName FROM #Databases WHERE ID = #CurrentID),
#SqlCommand VARCHAR(MAX)
WHILE (#CurrentID <= #MaxID)
BEGIN
SET #SqlCommand = 'SELECT name,ROUTINES.SPECIFIC_SCHEMA,ROUTINES.SPECIFIC_NAME
FROM sys.databases AS DatabaseNames
INNER JOIN ' + #DbName + '.INFORMATION_SCHEMA.ROUTINES AS ROUTINES
ON ROUTINES.SPECIFIC_CATALOG = DatabaseNames.name
WHERE ROUTINES.ROUTINE_TYPE = ''PROCEDURE''
AND ROUTINES.SPECIFIC_NAME IN (''X'',''Y'')'
EXEC (#SqlCommand)
SET #CurrentID = #CurrentID + 1
SELECT #DbName = DbName
FROM #Databases
WHERE ID = #CurrentID
END

Resources