Getting metadata from another database in an sproc in SQL Server? - tsql

How do I create a stored procedure that exists in one database but runs the below code against another (any) database?
SET #sql1 = N'INSERT INTO #Tables SELECT'
+ N' t.TABLE_NAME as TableName'
+ N',t.TABLE_SCHEMA as SchemaName'
+ N',(SELECT OBJECTPROPERTY(OBJECT_ID(t.TABLE_SCHEMA + ''.'' + t.TABLE_NAME),''TableHasIdentity'')) '
+ N'FROM ' + QUOTENAME(#TargetDBName)
+ N'.INFORMATION_SCHEMA.TABLES t'
IF #Verbose = 1
PRINT #sql1
EXEC(#sql1)
I get TABLE_NAME and SCHEMA_NAME successfully, but the main issue is that OBJECTPROPERTY() runs in the context of the stored procedure's database, not in the context of #TargetDBName. So, OBJECTPROPERTY() will always return null, unless #TargetDBName is the same as the database the sproc is in.
I am currently using SQL Server 2008.

Query the sys views directly like this
SELECT Tbl.name AS TableName, sch.name AS SchemaName,
HasIdentity = CASE WHEN EXISTS (SELECT * FROM your_target_db.sys.columns AS cols WHERE Tbl.object_id = cols.object_id and is_identity = 1) THEN 1 ELSE 0 END
FROM your_target_db.sys.tables AS Tbl INNER JOIN your_target_db.sys.schemas AS SCH ON Tbl.schema_id = Sch.schema_id

Could you instead use OPENQUERY (see here)?

Have you considered using IDENT_SEED in your dynamic query?
This will return the seed value of a table's identity column or NULL if one doesn't exist.
Example:
USE master
GO
CREATE DATABASE Test
GO
USE Test
GO
CREATE TABLE Test1 (ColA INT IDENTITY(100,1))
CREATE TABLE Test2 (ColA INT)
GO
USE master
GO
DECLARE #TargetDBName NVARCHAR(MAX), #sql1 NVARCHAR(MAX)
SET #TargetDBName = 'Test'
SET #sql1 = N'SELECT'
+ N' t.TABLE_NAME as TableName'
+ N',t.TABLE_SCHEMA as SchemaName'
+ N',(SELECT CASE WHEN IDENT_SEED('''
+ QUOTENAME(#TargetDBName) + '.''
+ t.TABLE_SCHEMA + ''.''
+ t.TABLE_NAME) IS NOT NULL THEN 1 ELSE 0 END) as HasIdentity '
+ N'FROM ' + QUOTENAME(#TargetDBName)
+ N'.INFORMATION_SCHEMA.TABLES t'
EXEC(#sql1)
GO
DROP DATABASE Test
GO
The results:
TableName SchemaName HasIdentity
---------- ----------- -----------
Test1 dbo 1
Test2 dbo 0
One caveat you may need to consider:
Returns NULL on error or if a caller
does not have permission to view the
object.

Related

Dynamic SQL Query to Drop table

Looking for way to create some dynamic SQL to drop a table if it exists. However I can not seem to get the syntax correct. Here is the query so far (renamed fields for security)
DECLARE #TableNameNew NVARCHAR(MAX)
DECLARE #DynamicSQL2 NVARCHAR(MAX)
SET #TableNameNew = (SELECT 'tbl1_' + REPLACE(StaffCode,'.','') AS TableName
FROM tblEmployee WHERE (PCLogin = REPLACE(SYSTEM_USER, 'DOMAIN\', '')))
SET #DynamicSQL2 = 'IF OBJECT_ID(' + '''' + #TableNameNew + '''' + +','+'''U''' + ') IS NOT NULL DROP TABLE ' + #TableNameNew
EXEC #DynamicSQL2
This returns an error:
Could not find stored procedure 'IF OBJECT_ID('tbl1_ghewitt','U') IS NOT NULL DROP TABLE tbl1_ghewitt'.
Try
EXECUTE sp_executesql #DynamicSQL2
instead of
EXEC #DynamicSQL2

Listing all databases, all tables, and all columns on a SQL Server instance

I am trying to write a query that for a single SQL Server instance, lists all columns in all tables in all databases on the server instance. I have found examples that list all columns in all tables but you have to know the database name. I have found examples that list databases on a SQL Server instance but not the tables in the databases. Now I am trying to find something that combines the two, but I am not having much luck.
Would anyone know if such a thing exists or is it a manual process to bridge the gap?
Thanks,
Tom
You can use dynamic query. I did not find any other way.
declare #str varchar(max) = ''
;with dbs as (
select *
from sys.databases
where dbs.name not in ('master', 'tempdb', 'model', 'msdb')
)
select #str = #str + 'select ''' + dbs.name + ''', tbl.name, col.name from ' +
dbs.name + '.sys.tables tbl inner join ' +
dbs.name + '.sys.columns col ON col.object_id = tbl.object_id; '
from dbs
print #str
exec(#str)
Please try this and feed back with comments.
The requirement was to find out one particular table from all the database. This was not possible by visual inspection as it might take lots of time and human error was possible. She was aware of the system view sys.tables.
SELECT *
FROM sys.Tables
WHERE name LIKE '%Address%'
The limitation of query mentioned above is that it only searches in one database and user has to keep on changing database manually and run the query again. I wrote down following quick script which looks into all the database on the server and provides the database name, schema name and table containing searched word in its name.
CREATE PROCEDURE usp_FindTableNameInAllDatabase
#TableName VARCHAR(256)
AS
DECLARE #DBName VARCHAR(256)
DECLARE #varSQL VARCHAR(512)
DECLARE #getDBName CURSOR
SET #getDBName = CURSOR FOR
SELECT name
FROM sys.databases
CREATE TABLE #TmpTable (DBName VARCHAR(256),
SchemaName VARCHAR(256),
TableName VARCHAR(256))
OPEN #getDBName
FETCH NEXT
FROM #getDBName INTO #DBName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #varSQL = 'USE ' + #DBName + ';
INSERT INTO #TmpTable
SELECT '''+ #DBName + ''' AS DBName,
SCHEMA_NAME(schema_id) AS SchemaName,
name AS TableName
FROM sys.tables
WHERE name LIKE ''%' + #TableName + '%'''
EXEC (#varSQL)
FETCH NEXT
FROM #getDBName INTO #DBName
END
CLOSE #getDBName
DEALLOCATE #getDBName
SELECT *
FROM #TmpTable
DROP TABLE #TmpTable
GO
EXEC usp_FindTableNameInAllDatabase 'Address'
GO

Calling one sp from another

On this link
http://gallery.technet.microsoft.com/Compare-Data-0c5bfc87#content
we can find a stored procedure example which can compare two tables data. WHat I would like is to call this sp for each table in database. I have found next sp that will enumerate through all tables
http://weblogs.sqlteam.com/joew/archive/2007/10/23/60383.aspx
The problem is that I cannot get to pass properly parameters. Here is what I tried (I have placed both databases on local server):
Exec sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, NULL, PARSENAME('?', 1)"
and that fails with
Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '[dbo].[Activities]'.
And the same message for every table. Can anyone help me with what I am doing wrong here?
I'll stick my neck out and post this as an answer because it's not formatted nicely as a comment.
Have you tried this:
sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, NULL, PARSENAME('[?]', 1)"
Update:
It looks like it doesn't like the PARSENAME. You could try this (I tried this on a version of sp_CompareTable with the EXEC changed to a PRINT).
Add this line to sp_CompareTable (before the EXEC):
SET #TableName = PARSENAME(#TableName,1)
Call it like this:
sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, dbo, '?'"
NB: This would be a quick fix for the case where you only have the "dbo" schema. It doesn't really answer the question of exactly why the original syntax doesn't work.
Update Again:
Here's a version of the Compare Table stored procedure which is tailored to run with sp_MSforeachtable
CREATE PROC [dbo].[uspCompareTable](#db1 varchar(250), #db2 sysname, #TableName sysname)
AS
declare #reason varchar(7)='Missing';
IF #TableName = '[dbo].[sysdiagrams]'
RETURN
IF CHARINDEX('.',#db1,1) <> 0
SET #db1=QUOTENAME(SUBSTRING(#db1,1, CHARINDEX('.',#db1,1)-1))+'.'+QUOTENAME(SUBSTRING(#db1, CHARINDEX('.',#db1,1)+1,DATALENGTH(#db1)-CHARINDEX('.',#db1,1)))
IF CHARINDEX('.',#db2,1) <> 0
SET #db2=QUOTENAME(SUBSTRING(#db2,1, CHARINDEX('.',#db2,1)-1))+'.'+QUOTENAME(SUBSTRING(#db2, CHARINDEX('.',#db2,1)+1,DATALENGTH(#db2)-CHARINDEX('.',#db2,1)))
EXEC ('
SELECT * FROM
(SELECT * FROM '+ #db1 + '.' + #TableName +'
EXCEPT
SELECT * FROM '+ #db2 + '.' + #TableName +') T
CROSS JOIN (SELECT '''+#reason +' in '+#db2 +'.' + #TableName+''' Reason) T2
UNION ALL
SELECT * FROM
(SELECT * FROM '+ #db2 + '.' + #TableName +'
EXCEPT
SELECT * FROM '+ #db1 + '.' + #TableName +' ) T
CROSS JOIN (SELECT ''' + #reason + ' in ' + #db1 + '.' + #TableName + ''' Reason) T2')
Here I'm assuming that schema will be part of the TableName (which it should be if you're calling from sp_MSforeachtable). Also a tweak to skip sysdiagrams which gets picked up on my system (SQL Server 2008 Express).
Usage would be
sp_MSforeachtable "EXEC uspCompareTable dbname1, dbname2, '?'"

EF can't infer return schema from Stored Procedure selecting from a #temp table

Suppose the following:
CREATE PROCEDURE [MySPROC]
AS
BEGIN
CREATE TABLE #tempSubset(
[MyPrimaryKey] [bigint] NOT NULL,
[OtherColumn] [int] NOT NULL)
INSERT INTO #tempSubset (MyPrimaryKey, OtherColumn)
SELECT SomePrimaryKey, SomeColumn
FROM SomeHugeTable
WHERE LimitingCondition = true
SELECT MyPrimaryKey, OtherColumn
FROM #tempSubset
WHERE SomeExpensiveCondition = true
END
When I generate a function import or map a return type, EF doesn't generate a complex type or tells me:
The selected stored procedure or function returns no columns
How to overcome this?
Other answers suggest using table variables (not going to do this for performance reasons) faking the return schema and commenting out the real stored procedure, other suggest doing similar with views... but there must be a way to do this without having to add unnecessary overhead or requiring me to break a stored procedure to update the model?
CREATE PROCEDURE [MySPROC]
AS
BEGIN
--supplying a data contract
IF 1 = 2 BEGIN
SELECT
cast(null as bigint) as MyPrimaryKey,
cast(null as int) as OtherColumn
WHERE
1 = 2
END
CREATE TABLE #tempSubset(
[MyPrimaryKey] [bigint] NOT NULL,
[OtherColumn] [int] NOT NULL)
INSERT INTO #tempSubset (MyPrimaryKey, OtherColumn)
SELECT SomePrimaryKey, SomeColumn
FROM SomeHugeTable
WHERE LimitingCondition = true
SELECT MyPrimaryKey, OtherColumn
FROM #tempSubset
WHERE SomeExpensiveCondition = true
END
Supplying a faux data contract for the result set is the easiest, cleanest and fastest way to take care of the issue. This same problem exists in data source controls in SSIS too. .NET will read the result set from the unreachable "contract" section of the query and supply the metadata for the complex type. No performance impact and no need to comment out the SQL that does the actual work.
Adding this to the top of the stored procedure definition: SET FMTONLY OFF allowed the model to infer the schema from the temporary table without issue. As a bonus, it doesn't require additional maintenance for a contract.
Example:
SET FMTONLY OFF
CREATE TABLE #tempTable (
...
)
...
SELECT * FROM #tempTable
Solution 1
Use a table variable instead of a temporary table.
Solution 2
Use the Set FMTONLY off; SQL command in the procedure and you will get the column information to create a new complex type.
Solution 3
This is not a good way, but it's a very easy way. Just add a select statement with dummy data and it will not execute because 1=0.
you can check details on this link
This is incomplete but when set fmtonly off does not work, you can generate the data contract using the following:
SELECT *
FROM tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..#u');
select case system_type_id
when 62 then 'cast(null as float) as '
when 175 then 'cast(null as char(' + cast(max_length as varchar(50)) + ')) as '
when 167 then 'cast(null as varchar(' + cast(max_length as varchar(50)) + ')) as '
when 56 then 'cast(null as int) as '
when 104 then 'cast(null as bit) as '
when 106 then 'cast(null as decimal(' + cast(precision as varchar(50)) + ',' + cast(scale as varchar(50)) + ')) as '
when 40 then 'cast(null as date) as '
end
+ name + ','
from tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..#u');

Create View in T-SQL Script

We are running SQL Server 2008 R2 and creating an archiving function that will create a new database (that can later be taken offline and stored elsewhere), then take data out of our primary database and put it in to the new DB and finally, create a view in the primary DB to look at the archived data in the new table.
I have the script to create the DB, create the archive table in the new DB, copy the records from the primary DB and put them in to the archive DB and delete the records from the primary DB. Now I am trying to script the creation of a view:
declare #sql varchar(8000)
set #sql = 'create view [' + #srcdb + '].[dbo].[vw_artrans] as
select * from [' + #srcdb + '].[dbo].artrans
union
select * from [' + #archdb + '].[dbo].artrans'
exec (#sql)
But you cannot pass the name of the DB to create view.
So I tried this instead:
declare #sql varchar(8000)
set #sql = 'use ' + #srcdb + '
go
create view [vw_artrans] as
select * from [' + #srcdb + '].[dbo].artrans
union
select * from [' + #archdb + '].[dbo].artrans'
exec (#sql)
But it now complains about the GO statement (Incorrect syntax).
The name of the database being created for the archived data is determined dynamically in the script (#archdb contains the name) so I can't script in the DB name and I can't run a second script.
Based on #Sebastien answer, here is the solution :
declare #sql varchar(8000)
set #sql = 'EXEC ' + #srcdb + '.sys.sp_executesql N''create view [vw_artrans] as
select * from [' + #srcdb + '].[dbo].artrans
union
select * from [' + #archdb + '].[dbo].artrans'';'
exec (#sql)
to execute a dynamic SQL statement in a different database than the one you are in you can use sp_executesql like this:
USE db1;
EXEC db2.sys.sp_executesql N'SELECT DB_NAME();';
This wil result in db2 being returned.
GO is not a T-SQL statement. It is interpreted by SSMS to break the query text into batches. It never gets send to SQL Server itself.