Find out which database a stored procedure is in - database

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

Related

Creating table of DB and table info on SQL Server Instance using while loop

I was wondering why i can't substitute the DB name for the variable #DBNAME in the following while loop:
I can use the variable as the DB_NAME but it wont let me substitute the DB name for sys.tables
it is giving me the following error: Incorrect syntax near '.'.
DROP TABLE #TABLE_INFO;
CREATE TABLE #TABLE_INFO
(
DB_NAME VARCHAR(100),
TABLE_NAME VARCHAR(100),
CREATE_DATE DATE
);
DECLARE #COUNT AS INT = 1
DECLARE #DBNAME AS VARCHAR(100)
WHILE #COUNT < (SELECT MAX(DATABASE_ID) +1 FROM SYS.DATABASES)
BEGIN
SET #DBNAME = (SELECT NAME FROM SYS.DATABASES WHERE DATABASE_ID = #COUNT)
INSERT INTO #TABLE_INFO
SELECT
#DBNAME AS DB_NAME,
A1.NAME AS TABLE_NAME,
A1.CREATE_DATE
FROM #DBNAME.SYS.TABLES A1
SET #COUNT = #COUNT+1
END
SELECT * FROM #TABLE_INFO;
T-SQL does not allow database object-identifiers to be parameteried.
...So you have to use Dynamic SQL (i.e. building up a query inside a string value and passing it to sp_executesql).
Obviously this is dangerous due to the risk of SQL injection, or simply forgetting to escape names correctly (use QUOTENAME) which may cause inadvertent data-loss (e.g. if someone actually named a table as [DELETE FROM Users]).
So it's important to use Dynamic SQL as little as possible.
What you can do is use INSERT INTO #tableVariable EXECUTE sp_executesql #query which allows you to store the results of a stored procedure - or any dynamic SQL - into a table-variable or temporary-table without them being output directly to your client connection and then process the results using non-dynamic SQL.
You can also use CROSS APPLY too if you can move all of your dynamic-SQL logic to a stored-procedure.
Try this:
BTW, I changed your WHILE loop to use a STATIC READ_ONNLY CURSOR which avoids your assumptions about how sys.databases's database_id work (e.g. what if sys.databases lists only these 4 database_id values 1, 2, 99997, 99998? Your WHILE loop would be wasting time checking for 3, 4, 5, 6, ..., 99996).
I also use a table-variable rather than a temporary-table because the scope and lifetime of a table-variable is easier to reason about compared to a temporary-table. That said, there aren't really any performance benefits of TVs over TTs (though you can pass TVs as table-valued-parameters, which is nice and generally much better than passing data to sprocs using TTs).
DECLARE #results TABLE (
DatabaseName nvarchar(100) NOT NULL,
SchemaName nvarchar(50) NOT NULL,
TableName nvarchar(100) NOT NULL,
Created datetime NOT NULL
);
DECLARE #dbName nvarchar(100);
DECLARE c CURSOR STATIC READ_ONLY FOR
SELECT QUOTENAME( [name] ) FROM sys.databases;
OPEN c;
FETCH NEXT FROM c INTO #dbName;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #dbQuery nvarchar(1000) = N'
SELECT
''' + #dbName + N''' AS DatabaseName,
s.[name] AS SchemaName,
t.[name] AS TableName,
t.create_date AS Created
FROM
' + #dbName + N'.sys.tables AS t
INNER JOIN sys.schemas AS s ON t.schema_id = s.schema_id
ORDER BY
s.[name],
t.[name]
';
INSERT INTO #results
( DatabaseName, SchemaName, TableName, Created )
EXECUTE sp_executesql #dbQuery;
FETCH NEXT FROM c INTO #dbName;
END;
CLOSE c;
DEALLOCATE c;
--------------------------
SELECT
*
FROM
#results
ORDER BY
DatabaseName,
SchemaName,
TableName;
I am able to copy+paste this query into SSMS and run it against my SQL Server 2017 box without modifications and it returns the expected results.

Is it possible to define full SELECT clause in USE GO clause?

I'm trying to define full SELECT clause in USE for a list of databases.
I'm trying to query across all databases in a server to list all available functions. There are about 80 databases so I'm going to use variable table to pass the list of databases to USE clause to be able to switch between every database and run simple query to list functions in there, all through a WHILE loop.
Is it possible to do something like,
USE
(SELECT dBname
From dBList
where rowID=#currentRow)
GO
IF not, any suggestion to do it less painfully than querying databases one by one?
This is what I have done in MS SQL Server 2014 but no joy,
declare #dBaseList Table
( RowID int not null primary key identity(1,1), dBname varchar(100)
)
declare #SeldBname varchar(100)
declare #rowsdBaseList int
declare #currentRow int
insert into #dBaseList (dBname)
SELECT name FROM sys.databases
set #rowsdBaseList=##ROWCOUNT
set #currentRow=0
while #currentRow<#rowsdBaseList
begin
set #currentRow+=1
use
(select #SeldBname=dBname
from #dBaseList
where RowID=#currentRow)
Go
select * from sys.objects where type='FN'
end
It should look something like,
USE
dBname1
GO
select * from sys.objects where type='FN'
USE
dBname2
GO
select * from sys.objects where type='FN'
USE
dBname3
GO
select * from sys.objects where type='FN'
.
.
.
.
.
You should just be able to use a 3 part name in your query:
select * from dBname1.sys.objects where type='FN'
select * from dBname2.sys.objects where type='FN'
select * from dBname3.sys.objects where type='FN'
If you'd prefer to do it programmatically, you can use the (unsupported and undocumented) sp_MSforeachdb proc:
EXEC sp_MSforeachdb 'IF DB_ID(''?'') > 4 BEGIN select * from ?.sys.objects where type=''FN'' END'
The IF DB_ID(''?'') > 4 conditional excludes system databases, which you presumably don't want.
With regard to using a SELECT statement in a USE statement, you can't do it as you have specified in your question. However, you can incorporate it into the sp_MSforeachdb call, should you need to:
EXEC sp_MSforeachdb 'IF DB_ID(''?'') > 4 BEGIN USE ?; select DB_NAME() AS [DatabaseName], * from sys.objects where type=''FN'' END'
Have you tried Dynamic-SQL? Something like the code below, this is just a sample as I don't have right now my machine to actually test it.
SELECT name AS DatabaseName INTO #TEMP FROM master.dbo.sysdatabases
DECLARE #Count INT, #Database VARCHAR(MAX), #Query
SET #Count = (SELECT COUNT(*) FROM #TEMP)
WHILE (#Count) > 0
BEGIN
SET #Database = (SELECT TOP(1) DatabaseName FROM #TEMP)
SET #Query = 'SELECT * FROM ' + #Database + '.sys.objects WHERE type = ''FN'''
EXEC (#sqlCommand)
DELETE FROM #TEMP WHERE DatabaseName = #Database
SET #Count = (SELECT COUNT(*) FROM #TEMP)
END
Below is a method to return the query results from all databases as a single result set and include the database name.
DECLARE #SQL nvarchar(MAX) =
STUFF((
SELECT ' UNION ALL SELECT N''' + name + N''' AS DatabaseName, * FROM ' + QUOTENAME(name) + N'.sys.objects where type=''FN'''
FROM sys.databases
FOR XML PATH(''), TYPE).value('.','nvarchar(MAX)'), 1, 11,'') + N';';
EXEC(#SQL);
use sp_MSforeachdb
use [yourdatabase_in_which_you_store_the_results]
go
--create table functions_in_alldb(db nvarchar(100),fn_name nvarchar(200))
declare #functions_in_alldb as table(db nvarchar(100),fn_name nvarchar(200))
go
--truncate table functions_in_alldb
go
DECLARE #command varchar(1000)
SELECT #command = 'USE ?
if ''?'' not in (''master'',''msdb'',''tempdb'',''model'',''msdb'')
insert into #functions_in_alldb(db,fn_name) SELECT ''?'',name FROM sysobjects WHERE xtype = ''FN'' ORDER BY name'
EXEC sp_MSforeachdb #command
select * from #functions_in_alldb

T-SQL function that returns TABLE using dynamic SQL

EDIT: Some of those who would offer help are unclear about the nature of the requirement, so I will try to state it as clearly as I can:
We need to instantiate a view of an underlying table, and this view must be able to be joined to another table; the difficulty is that the identity of the underlying table is not known until runtime of the ad hoc query doing the join.
We would like to do something like this:
select * from foo
inner join dynamicallyInstantiatedTable(condition) DT
on foo.zipcode = DT.zipcode
It doesn't seem possible to create a function that returns TABLE if the function uses dynamic SQL. This is not valid:
declare #tablename varchar(50);
-- <snip> code to determine the name of #tablename
declare #statement varchar(1000);
set #statement = 'select * from ' + #tablename;
exec( #statement);
The error:
Invalid use of a side-effecting operator 'EXECUTE STRING' within a
function.
If the table name is not known beforehand for whatever reason (e.g. tables are constantly being added and we must select against the most recent one, say), is it possible to do the select dynamically and return a table, either in a stored proc or function?
Here we go.
I don't use synonyms often, but CREATE SYNONYM supports dynamic SQL.
declare #tablename nvarchar(128);
-- <some code to set #tablename>
declare #sql nvarchar(500);
if object_id(N'dbo.TodaysData', N'SN') is not null
drop synonym dbo.TodaysData;
set #sql =
'create synonym dbo.TodaysData
for ' + #tablename;
execute(#sql);
select top 5
*
from
dbo.TodaysData as t
join
dbo.SomeOtherTable as s
on
s.FieldName = t.HeresHopingYourSchemaDoesntChange
Dynamic SQL in function. No.
is it possible to do the select dynamically and return a table, either
in a stored proc or function?
Perhaps I'm missing something (would not be a first) but this seems simple as a stored proc:
The Proc
create proc dbo.getRowsFrom #tablename varchar(50) as
exec('select * from ' + #tablename);
Use
exec dbo.getRowsFrom '<my table>';
Is that what you're looking for?
you should once explain your requirement with example.It is not clear to anybody.
I think everything thing can be done within single proc,no need of another proc or UDF.
declare #tblname varchar(500)
select #tblname=name from sys.objects
where type_desc ='USER_TABLE'
order by create_date DESC
declare #Sql varchar(max)=''
set #Sql='select * into #tmp from '+#tblname+' '
set #Sql=#Sql+' select * from #tmp drop table #tmp'
exec (#Sql)
Little detail given about your 'join' situation, but it might be easier to jump to your final joined results, rather than focusing on the input table in isolation.
Here I am joining my input table 'a' to a lookup table 'ref', and outputting joined results.
If tomorrow your have another input table 'b' - this proc will join that to the lookup table instead.
The only requirement is that the join column is consistent.
declare
#inputTableName nvarchar(128)
,#sqlExec nvarchar(max)
set #inputTableName = 'b';
if(not exists (select 1 from INFORMATION_SCHEMA.TABLES where table_schema = 'test' and TABLE_NAME = 'myView'))
begin
select #sqlExec = 'create view test.myView as
select I.*,R.[text] from test.[' + #inputTableName + '] I inner join test.ref R on I.col0 = R.col0'
end else begin
select #sqlExec = 'alter view test.myView as
select I.*,R.[text] from test.[' + #inputTableName + '] I inner join test.ref R on I.col0 = R.col0'
end
exec (#sqlExec)
select * from test.myView

Query All Views In Database

I am looking to return a view name and all record ID's where billingAddress != shippingAddress to further review. I need to query all views in one database. This is my thought process and if their is a better way or faster way to write the query by all means help me out!
What I am stuck on is how to return the view name with the recordID?
Create Table #T (ID Int Identity Not Null, ViewNames VARCHAR(1000)
Create Table #2 (viewNames varchar(1000))
Insert Into #T (ViewNames)
Select '['+C.Table_Catalog+'].['+C.Table_Schema+'].['+C.Table_Name+']' TableName
FROM Information_Schema.Columns c
Join information_schema.Tables T on C.Table_Catalog = T.Table_Catalog
AND C.Table_Schema = T.Table_Schema
AND C.Table_Name = T.Table_Name
Where T.Table_Type = 'View'
Group By '['+C.Table_Catalog+'].['+C.Table_Schema+'].['+C.Table_Name+']'
---Now this is the piece that I am stuck on as I do not know how to insert the view name into the table as well on each iteration
Declare #N int, #Str nvarchar(2000), #viewname nvarchar(2000), #MaxID int
Set #N = 1
Select #MaxID = Max(ID)
From #T
While (#N<#MaxID)
Begin
Select #viewname= viewname
From #T
Set #Str = ' Insert Into #2(viewname)
Select Top 1 '''+#viewname+'''
From '+#viewname+'
where exists(Select recordID from '+#viewname+' where [shipaddress] != [billaddress] ) '
Exec sp_eecutesql #str
Set #N = #N + 1
End
Select * from #t
Try changing your dynamic query like this.
You said you wanted the view name, and record id, so you need to add a column to #2
SET #Str = 'INSERT INTO #2(viewname, recordid)
SELECT ''' + quotename(#viewname) + ''', recordID
FROM '+ quotename(#viewname) + '
WHERE [shipaddress] != [billaddress]'
EXEC sp_executesql #str
Unless you're sure of the object names, you should try and use quotename when building up dynamic SQL
You do have a problem in your logic though...
You are missing a where clause in the query that assigns the value to #viewname
Try this...
SELECT #viewname= viewname
FROM #T
WHERE ID = #N
I do not understand sql returns set of rows so your variable #viewname can not be assigned value row by row. By default your #viewname will be assigned last row of table T.

compare schema of tables and script of stored-procedures between 2 databases

source : MS-SQL 2005
destination : MS-SQL 2012
somehow I need to change developing database from one to another but unfortunately the "to database" does have had some tables and SPs already and the worse, those objects such as tables might have some columns with different name or types or even descriptions inconsistent between.
what am I supposed to do to achieve sth like, maybe easier or smarter,
append new columns to tables which already existed (and also put col's default value and description from source)
change types of columns consistent to source
prevent overwriting contents of SPs already appeared in destination (but will review manually later)
So far I can figure out some statistics by the follwing scripts
select name from sys.Tables order by name (export to left.txt and right.txt and compare them between)
select * from sys.all_objects where type='p' and is_ms_shipped=0 order by name (also compare them between)
get all column names in one line per table (and compare them between),
e.g.
--sth like SELECT * FROM INFORMATION_SCHEMA.COLUMNS, but select into only ONE line per table
declare #temp_table_list table(
id int identity not null,
name varchar(100) not null
)
insert #temp_table_list(name) select name from sys.tables
declare #id int
declare #name varchar(100)
declare #result as nvarchar(max)
set #result = N''
while 1 = 1
begin
select #id = min(id)
FROM #temp_table_list
where id > isnull(#id,0)
if #id is null break
select #name = name
FROM #temp_table_list
where id = #id
declare #tbName as nvarchar(max)
declare #sql as nvarchar(max)
declare #col as nvarchar(max)
Set #tbName = #name
DECLARE T_cursor CURSOR FOR
select c.name from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
inner join sys.types tp on tp.user_type_id = c.system_type_id
where t.name =#tbName
OPEN T_cursor
FETCH NEXT FROM T_cursor into #col
set #sql = N'select '
WHILE ##FETCH_STATUS = 0
BEGIN
set #sql = #sql+#col+','
FETCH NEXT FROM T_cursor into #col
END
set #sql =substring( #sql,0,len(#sql)) +' from '+ #tbName
CLOSE T_cursor
DEALLOCATE T_cursor
set #result = #result + #sql + '/r/n'
end
select #result
This isn't free software, but it does have a trial period:
http://www.red-gate.com/products/sql-development/sql-compare/
As it sounds like your requirement is a one off sync, then that should do what you want.

Resources