I have a query that will return only columns with values. How do I add that to a function so I can use that with any query? Would it be a function in the where clause.
create table test1
(
s_no int not null,
name varchar(10) not null,
address varchar(10) null,
emailid varchar(100) null
)
insert into test1 (s_no, name)
values (1,'A'),(2,'B'),(3,'C')
declare #column_list varchar(8000),
#counter int
set #column_list = ''
set #counter = 0
while (Select max(colid) from syscolumns where id = object_id('test1') and isnullable= 0) > #counter
begin
select #counter = min(colid)
from syscolumns
where id = object_id('test1')
and isnullable = 0
and colid > #counter
select #column_list = #column_list + ',' + (Select name from syscolumns where id = object_id('test1') and isnullable= 0 and colid = #counter)
end
select #column_list = SUBSTRING(#column_list, 2, len(#column_list))
declare #sql varchar(8000)
select #sql = 'select ' + #column_list + ' from test1'
print #sql
exec (#sql)
SELECT * FROM [dbo].[test1]
I guess you could make a stored procedure where you provide the table name as parameter, and then build your query like you are doing already
create procedure ShowOnlyFilledColumns (#tablename varchar(100)) as
begin
set nocount on
declare #column_list varchar(8000),
#counter int
set #column_list = ''
set #counter = 0
while (Select max(colid) from syscolumns where id = object_id(#tablename) and isnullable= 0) > #counter
begin
select #counter = min(colid) from syscolumns where id = object_id(#tablename) and isnullable= 0 and colid > #counter
select #column_list = #column_list + ',' + (Select name from syscolumns where id = object_id(#tablename) and isnullable= 0 and colid = #counter)
end
select #column_list = SUBSTRING(#column_list,2,len(#column_list))
declare #sql varchar(8000)
select #sql = 'select ' + #column_list + ' from ' + #tablename
--print #sql
exec (#sql)
end
and use it like this
exec ShowOnlyFilledColumns 'test1'
See the complete example in this DBFiddle
EDIT: The OP asked how he can add joins on this
There are a few tricks to join with a stored procedure, for example in these answers
However, this won't work on this solution, because it requires to create a temp table to store the result of the procedure.
The trick looks like this
-- create a temporary table to store the results of the procedure
CREATE TABLE #Temp (
s_no int not null,
name varchar(10) not null,
address varchar(10) null,
emailid varchar(100) null
)
-- call the procedure and store the result in the temporary table
INSERT INTO #Temp
exec ShowOnlyFilledColumns 'test1'
-- now I can query the temp table, and join on it and write a where clause, and I can do whatever I want
select * from #Temp
Now, this won't work in this case, because the stored procedure can return different columns every time you run it, and to make the insert into #Temp exec ShowOnlyFilledColumns 'test1' work, the table #Temp must have the same number and type of columns as the procedure returns. And you just don't know that.
I will pass a table-valued input parameter into a stored procedure, and also a variable that contains query string, so I made my sproc like this.
CREATE PROCEDURE [dbo].[SP_SelectData_View]
(
#Sort VARCHAR(MAX),
#CONDITION VARCHAR(MAX) = ''
#Values dbo.FlowStatus READONLY
)
AS
BEGIN
DECLARE #STRQUERY NVARCHAR(MAX)
IF #CONDITION IS NOT NULL AND #CONDITION != ''
BEGIN
SET #CONDITION = 'WHERE ' + #CONDITION
END
ELSE
BEGIN
SET #CONDITION = ''
END
IF #Sort IS NULL OR #Sort = ''
BEGIN
SET #Sort = 'Id Desc'
END
BEGIN
SET #STRQUERY = 'SELECT A.*
FROM ' + #Values + ' as FlowStatus'
JOIN Tbl_A as A
ON A.status = FlowStatus.StatusNowId AND B.flow = FlowStatus.FlowNowId
' + #CONDITION + '
Order By ' + #Sort
EXEC(#STRQUERY)
END
END
But in the code above, I got an error
must declare scalar variable #Values
I've searched for it and I think it is because the aliases is not detected because it's inside a string. But if I didn't put it in a string query, the #condition and #sort variable will be error. Is there a solution where I can do both calling the table-valued variable and query string variable together?
There are several things wrong with the approach you currently have, as I and others have commented, Brent Ozar has a good reference on dynamic SQL https://www.brentozar.com/sql/dynamic/
I would say don't pass in some SQL, construct it in the stored proc; passing in parameters such as name which is used in the where, hence I have put a full working example. This also shows how to pass the user defined table type into the stored proc and then also pass it into the dynamic SQL.
I hope this is a good enough example of the techniques, I had a bit of time so thought I would try and help as much as possible :)
/*
--------------------------------------------
Create a test table to run the stored proc against
*/
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'MyTestTable'))
BEGIN
PRINT 'Creating table MyTestTable'
CREATE TABLE [dbo].[MyTestTable](
Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL
)
INSERT INTO dbo.MyTestTable ([Name])
VALUES ('Andrew'),
('Bob'),
('john')
-- SELECT * FROM MyTestTable
END
GO
/*
--------------------------------------------
Create the table type that we pass into the store proc
*/
IF NOT EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'FlowStatus')
BEGIN
PRINT 'Creating type [dbo].[FlowStatus]'
CREATE TYPE [dbo].FlowStatus AS TABLE (
MyId BIGINT PRIMARY KEY,
SomeText NVARCHAR(200)
)
END
GO
/*
--------------------------------------------
Create the stored proc with the User Defined table type
*/
CREATE OR ALTER PROCEDURE [dbo].[MyStoredProc]
(
#SortBy VARCHAR(50),
#SearchName VARCHAR(50),
#Values dbo.FlowStatus READONLY
)
AS
BEGIN
-- As your SQL gets more complex it is an idea to create seperate parts of the SQL
DECLARE #SqlToExecute NVARCHAR(MAX)
-- The primary data you want to get
SET #SqlToExecute = N'
SELECT T.Id, T.[Name], V.SomeText
FROM MyTestTable AS T
LEFT JOIN #Values AS V ON V.MyId = T.Id
WHERE 1 = 1' -- We do this so that we can have many AND statements which could be expanded upon
IF #SearchName IS NOT NULL
BEGIN
SET #SqlToExecute = #SqlToExecute + N'
AND T.[Name] LIKE ''%' + #SearchName + ''''
END
IF #SortBy IS NOT NULL
BEGIN
SET #SqlToExecute = #SqlToExecute + N'
ORDER BY ' +
CASE WHEN #SortBy LIKE 'Name%' THEN N'T.[Name]'
ELSE N'T.[Id]'
END
END
-- Print out the script that will be run, useful for debugging you code
PRINT #SqlToExecute
EXEC sp_executesql #SqlToExecute,
N'#Values dbo.FlowStatus READONLY', #Values
END
GO
/*
--------------------------------------------
Now lets test it
-- Test Andrew
*/
DECLARE #flowStatusType AS dbo.FlowStatus
INSERT INTO #flowStatusType(MyId, SomeText)
VALUES(1, 'Test1'),
(2, 'Test2')
EXEC [dbo].[MyStoredProc] #SearchName = 'Andrew', #SortBy = 'Name', #Values = #flowStatusType
GO
-- Test Bob
DECLARE #flowStatusType AS dbo.FlowStatus
INSERT INTO #flowStatusType(MyId, SomeText)
VALUES(1, 'Test1'),
(2, 'Test2')
EXEC [dbo].[MyStoredProc] #SearchName = 'Bob', #SortBy = 'Name', #Values = #flowStatusType
GO
Its also worth noting that if you can just join on the #Values without needing dynamic SQL then that is sure to be less work.
I'm trying to build a query which puts his output in a table.
The exec(#inloop_query) doesn't know a declared table from before.
(that part between the ------------------
Is this possible or do I try to do something that doesn't work?
please advise.
(The error I've is : Must declare the table variable "#inloop_table". Severity 15 State 2)
DECLARE #frame_db_name VARCHAR(max)
DECLARE #frame_db_id INT
DECLARE #frame_table TABLE (
db_id INT ,
names VARCHAR(max))
DECLARE #frame_count INT
DECLARE #frame_count_max INT
SET #frame_count = 1
SET #frame_count_max = 0
SELECT #frame_count_max = count (name) FROM sys.databases WHERE Name LIKE 'B%' and state_desc = 'online'
INSERT INTO #frame_table SELECT database_id , name FROM sys.databases WHERE Name LIKE 'B%' and state_desc = 'online' ORDER BY database_id
DECLARE #inloop_query VARCHAR(max)
DECLARE #Inloop_table TABLE (
IL_SchemaName VARCHAR(max) ,
IL_TableName VARCHAR(max) ,
IL_IndexName VARCHAR(max) ,
IL_IndexID INT ,
IL_Fragment INT)
IF #frame_count_max <= 0
PRINT '#count_max (<=0) = ' + CAST(#frame_count_max AS VARCHAR)
ELSE
WHILE #frame_count <= #frame_count_max
BEGIN
SELECT #frame_db_name = names , #frame_db_id = db_id FROM #frame_table WHERE db_id IN (SELECT TOP 1 db_id FROM #frame_table ORDER BY db_id)
PRINT '#count_max (>=0) = ' + CAST(#frame_count_max AS VARCHAR)
PRINT '#count = ' + CAST(#frame_count AS VARCHAR(max))
PRINT 'current DB name = ' + CAST(#frame_db_name AS VARCHAR(max))
PRINT 'current DB ID = ' + CAST(#frame_db_id AS VARCHAR(max))
------------------------------------------------------------
SET #inloop_query = '
USE ' + CAST(#frame_db_name AS VARCHAR(max)) +
' INSERT INTO #inloop_table
SELECT SCHEMA_NAME(o.schema_id) AS SchemaName,
OBJECT_NAME(a.object_id) AS TableName,
i.name AS IndexName,
a.index_id AS IndexID,
convert(tinyint,a.avg_fragmentation_in_percent) AS [Fragment]
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL,NULL, ''LIMITED'') AS a
INNER JOIN sys.indexes i ON i.index_id = a.index_id
AND i.object_id = a.object_id
INNER JOIN sys.objects o ON a.object_id = o.object_id
ORDER BY SchemaName, TableName, IndexID'
EXEC(#inloop_query)
------------------------------------------------------------
SET #frame_count = #frame_count + 1
DELETE FROM #frame_table WHERE db_id IN (SELECT TOP 1 db_id FROM #frame_table ORDER BY db_id)
END
#inloop_table is declared outside your #inloop_query; when the latter is executed, it has no idea about this variable. How about using an actual table?
/* comment this out:
DECLARE #inloop_query VARCHAR(max)
DECLARE #Inloop_table TABLE (
IL_SchemaName VARCHAR(max) ,
IL_TableName VARCHAR(max) ,
IL_IndexName VARCHAR(max) ,
IL_IndexID INT ,
IL_Fragment INT)
*/
-- Create an auxiliary table
CREATE TABLE InLoop_Table (
IL_SchemaName VARCHAR(max) ,
IL_TableName VARCHAR(max) ,
IL_IndexName VARCHAR(max) ,
IL_IndexID INT ,
IL_Fragment INT
);
-- ... And use this table in your dynamic sql:
SET #inloop_query = '
USE ' + CAST(#frame_db_name AS VARCHAR(max)) +
' INSERT INTO InLoop_Table ...
-- Finally, clean up:
DROP TABLE InLoop_Table;
The scope of a table variable is specific to a batch, so since your dynamic sql executes as a new batch it falls out of scope and is unrecognised. You could of course declare it within your dynamic sql, but that would be pretty pointless as you couldn't access it later. You have two decent choices:
You can put the insert outside of the sql, e.g.
DECLARE #inloop_query NVARCHAR(MAX) = 'USE Master; SELECT 1, 2, 3;';
DECLARE #inloop_table TABLE (A INT, B INT, C INT);
INSERT #inloop_table
EXEC(#inloop_query);
SELECT * FROM #inloop_table;
Or you can use a temporary table, rather than a table variable. A temporary table has session scope, so is still recognised with EXEC():
CREATE TABLE #inloop_table (A INT, B INT, C INT);
DECLARE #inloop_query NVARCHAR(MAX) = 'USE Master; INSERT #inloop_table SELECT 1, 2, 3;';
EXEC(#inloop_query);
SELECT * FROM #inloop_table;
I would also recommend using a properly declared cursor rather than a WHILE loop iterating through a table variable. The key aspect here being properly defined. Often people just use DECLARE .. CURSOR FOR SELECT.. and the default options are much slower and more memory consuming than if you tell the cursor that you won't be making updates, won't be moving backwards etc.
DECLARE DBCursor CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR
SELECT database_id , name
FROM sys.databases
WHERE Name LIKE 'B%' and state_desc = 'online'
ORDER BY database_id;
OPEN DBCursor;
FETCH NEXT FROM DBCursor INTO #frame_db_id, #frame_db_name;
WHILE ##FETCH_STATUS = 0
BEGIN
-- DO WHATEVER YOU NEED WITH EACH DB
FETCH NEXT FROM DBCursor INTO #frame_db_id, #frame_db_name;
END
CLOSE DBCursor;
DEALLOCATE DBCursor;
One final comment, is that I always perfer sp_executesql over EXEC() and this article pretty much covers why, in this instance it doesn't make much difference, but it is worth noting.
Requirement: To write stored Procedure(s) such that the values passed in stored procedures are matched against the values in columns in the table and then arranged with highest to lowest matching number of attributes.Then are inserted in a dynamically created temporary table inside the stored procedure.
Problem:
I have say 15-20 attributes that are matched against to confirm the suggestions made in response to record search. Basically, There is a table that stores Patients information and multiple parameters may be passed into the stored procedure to search through so that a Temporary table is created that suggests records in decreasing order of matching attributes.
To frame the basic structure, I tried with 3 attributes and respective stored procedures to match them which in turn are collectively called from a calling procedure based on the input parameter list which in turn creates the required temporary table.
Here is the SQL code(Just to give the gist of what I have tried so far):
But as a matter of fact it is, I realize this is way too naive to be used in a real time application which may require 80-90% of accuracy.So, what exactly can replace this technique for better efficiency?
Create Procedure NameMatch
(
#Name nvarchar(20),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name =' + #Name
set #temp = 0.1*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure AgeMatch
(
#Name nvarchar(20),
#Age int,
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name =#Name and Age = + #Age)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age
set #temp = 0.2*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure Nationality
(
#Name nvarchar(20),
#Age int,
#Nation nvarchar(10),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name and Age = #Age and Nationality = #Nation )
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age + ' and Nationality = ' + #Nation
set #temp = 0.3*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
create procedure CallingProcedure
(
#Name nvarchar(20),
#Age int = null,
#Nation nvarchar(10)= null
)
As
declare #PercentMatch nvarchar(4)
Begin
create table #results(PatientName nvarchar(30), PercentMatch nvarchar(4))
if(#Nation IS NOT NULL)
Insert into #results exec Nationality #Nation, #Name output, #PercentMatch output
else if(#Age is not Null)
Insert into #results exec AgeMatch #Age, #Name output, #PercentMatch output
else
Insert into #results exec NameMatch #Name, #Name output, #PercentMatch output
End
Setting aside nuances of stored procedure syntax, given parameters 1-n that if not null should match columns 1-n and the results sorted by highest number of matches first, a dynamic query is not needed - plain SQL can do it.
select *
from patient
where #param1 is null or column1 = #param1
or #param2 is null or column2 = #param2
...
or #paramN is null or columnN = #paramN
order by if(column1 = #param1, 1, 0)
+ if(column2 = #param2, 1, 0)
...
+ if(columnN = #paramN, 1, 0) desc
SQL Server Version - 2008 R2
I am working on evaluating a DMS solution, with an objective of taking over maintenance. The original solution has one central database, that has data pertaining to the manufacturer. It also has one database for each dealer, which means there are a lot of cross database dependencies.
The problems:
No DB documentation
No code comments
Lots of heaps
No standard object naming conventions
The central DB has 460+ tables and 900+ SProcs, in addition to other
objects
Each dealer DB has 370+ tables and 2350+ SProcs, in addition to other
objects
As a first step, I am recommending a complete clean-up of the DB, for which it is critical to understand object dependencies, including cross database dependencies. I tried using Red Gate's solution, but the output is way too voluminous. All I want is a list of objects in the databases that do not have any dependencies - they neither depend on other objects, nor are there any objects that depend on them.
Here is the script I have used to get a list of dependencies:
SELECT
DB_NAME() referencing_database_name,
OBJECT_NAME (referencing_id) referencing_entity_name,
ISNULL(referenced_schema_name,'dbo') referenced_schema_name,
referenced_entity_name,
ao.type_desc referenced_entity_type,
ISNULL(referenced_database_name,DB_NAME()) referenced_database_name
FROM sys.sql_expression_dependencies sed
JOIN sys.all_objects ao
ON sed.referenced_entity_name = ao.name
I will be creating a table - Dependencies - into which I will be inserting this result set from each DB. As a next step, I will also be creating another table - AllObjects- which will contain a list of all objects in the Databases. Here is the script to do this:
SELECT
DB_NAME() DBName,
name,
type_desc
FROM sys.all_objects
WHERE type_desc IN
(
'VIEW',
'SQL_TABLE_VALUED_FUNCTION',
'SQL_STORED_PROCEDURE',
'SQL_INLINE_TABLE_VALUED_FUNCTION',
'USER_TABLE',
'SQL_SCALAR_FUNCTION'
)
Now, a list of name from this table, that do not appear in the referenced_entity_name column in the dependencies table should give a list of objects that I am looking for.
SELECT
AO.DBName,
AO.name,
AO.type_desc
FROM AllObjects AO
LEFT OUTER JOIN Dependencies D ON
D.referenced_database_name = AO.DBName AND
D.referenced_entity_name = AO.name AND
D.referenced_entity_type = AO.type_desc
WHERE
D.referenced_database_name IS NULL AND
D.referenced_entity_name IS NULL AND
D.referenced_entity_type IS NULL
Now the questions:
Some object dependencies seem to be missing in the output. What am I
missing?
How do I validate that my findings are correct?
I mean is there a different way to do this, so I can compare the
results and double check?
Thanks in advance,
Raj
You can compare your results to the ones that the following script finds.
Here is the full article
CREATE PROCEDURE [dbo].[get_crossdatabase_dependencies] AS
SET NOCOUNT ON;
CREATE TABLE #databases(
database_id int,
database_name sysname
);
INSERT INTO #databases(database_id, database_name)
SELECT database_id, [name]
FROM sys.databases
WHERE 1 = 1
AND [state] <> 6 /* ignore offline DBs */
AND database_id > 4; /* ignore system DBs */
DECLARE
#database_id int,
#database_name sysname,
#sql varchar(max);
CREATE TABLE #dependencies(
referencing_database varchar(max),
referencing_schema varchar(max),
referencing_object_name varchar(max),
referenced_server varchar(max),
referenced_database varchar(max),
referenced_schema varchar(max),
referenced_object_name varchar(max)
);
WHILE (SELECT COUNT(*) FROM #databases) > 0 BEGIN
SELECT TOP 1 #database_id = database_id,
#database_name = database_name
FROM #databases;
SET #sql = 'INSERT INTO #dependencies select
DB_NAME(' + convert(varchar,#database_id) + '),
OBJECT_SCHEMA_NAME(referencing_id,'
+ convert(varchar,#database_id) +'),
OBJECT_NAME(referencing_id,' + convert(varchar,#database_id) + '),
referenced_server_name,
ISNULL(referenced_database_name, db_name('
+ convert(varchar,#database_id) + ')),
referenced_schema_name,
referenced_entity_name
FROM ' + quotename(#database_name) + '.sys.sql_expression_dependencies';
EXEC(#sql);
DELETE FROM #databases WHERE database_id = #database_id;
END;
SET NOCOUNT OFF;
SELECT * FROM #dependencies;
Oh, MS made a good effort at detecting cross-database dependencies with sys.sql_expression_dependencies, but I've seen it miss things before. In your case, I'd find an example of a missing dependency, and start backtracking: have you dropped it from your query some how? If so, fix your query. Does sys.sql_expression_dependencies omit a certain class of dependencies? Under what conditions? Is dynamic SQL to blame? etc.
You should also run sp_refreshsqlmodule for each object in sys.sql_modules, and then rerun your code. It forces SQL Server to refresh the dependency info (to the best of its ability).
Now, for validation, set up a trace, and listen for event 114, "Audit Schema Object Access Event", plus the starting and completed events for stored procedure and/or RPC calls. Include columns DatabaseName, ParentName, ObjectName, ServerName, SPID and RequestID (for MARS-enabled connections). Maybe some others too. "Audit Schema Object Access Event" happens anytime an object is accessed, so exercise the app while this trace is running, then collate the data using SPID + RequestId and compare it to your results using sys.sql_expression_dependencies. If anything is in the trace data that doesn't appear in your dependencies data, then you've missed something.
If you have to deal with linked servers, I adapted #MilicaMedic's answer to work for cross-server dependencies. I also output column names where available in a dependency.
You can use it like this:
create table #dependencies (
referencing_server nvarchar(128),
referencing_database nvarchar(128),
referencing_schema nvarchar(128),
referencing_object_name nvarchar(128),
referencing_column nvarchar(128),
referenced_server nvarchar(128),
referenced_database nvarchar(128),
referenced_schema nvarchar(128),
referenced_object_name nvarchar(128),
referenced_column nvarchar(128)
);
insert #dependencies
exec crossServerDependencies
'ThisServerName, LinkedServerName, LinkedServerName2, etc'
From there you join it to your AllObjects table as you described in your answer.
My code requires two external functions: "splitString", and "AddBracketsWhenNecessary". You can simplify the former and completely eliminate the latter, as you desire. But I use them for other things so they make it into my implementation. The code for both is at the bottom.
Here is the main procedure:
create procedure crossServerDependencies
#server_names_csv nvarchar(500) = null -- csv list of server names you want to pull dependencies for
as
-- Create output table
if object_id('tempdb..#dependencies') is not null
drop table #dependencies;
create table #dependencies (
referencing_server nvarchar(128),
referencing_database nvarchar(128),
referencing_schema nvarchar(128),
referencing_object_name nvarchar(128),
referencing_column nvarchar(128),
referenced_server nvarchar(128),
referenced_database nvarchar(128),
referenced_schema nvarchar(128),
referenced_object_name nvarchar(128),
referenced_column nvarchar(128)
);
-- Split server csv into table
set #server_names_csv = isnull(#server_names_csv, ##servername);
declare #server_names table (
server_row int,
server_name nvarchar(128),
actuallyExists bit
);
insert #server_names
select server_row = id,
server_name,
actuallyExists = case when sv.name is not null then 1 else 0 end
from dbo.splitString(#server_names_csv, ',') sp
cross apply (select server_name = dbo.AddBracketsWhenNecessary(val)) ap
left join sys.servers sv on sp.val = dbo.AddBracketsWhenNecessary(sv.name);
-- Loop servers
declare
#server_row int = 0,
#server_name nvarchar(50),
#server_exists bit = 0,
#server_is_local bit = 0,
#server_had_some_inserts bit = 0;
while #server_row <= (select max(server_row) from #server_names)
begin
-- Server loop initializations
set #server_row += 1;
set #server_had_some_inserts = 0;
select #server_name = server_name,
#server_exists = actuallyExists
from #server_names
where server_row = #server_row;
set #server_is_local =
case when #server_name = dbo.AddBracketsWhenNecessary(##servername) then 1 else 0 end;
-- Handle non-existent server (and prevent sql injection)
if #server_exists = 0
begin
print
'"' + #server_name + '" does not exist. ' +
'Please check your spelling and/or access to view the linked server ' +
'(running under ' + user_name() + ').';
continue;
end
-- Get database list
if object_id('tempdb..#databases') is not null
drop table #databases;
create table #databases (
rownum int identity(1,1),
database_id int,
database_name nvarchar(128)
);
declare #sql nvarchar(max) = '
select database_id, [name]
from master.sys.databases
where state <> 6 -- ignore offline dbs
and database_id > 4 -- ignore system dbs
and has_dbaccess([name]) = 1
and [name] not in (''ReportServer'', ''ReportServerTempDB'')
';
if #server_is_local = 0
begin
set #sql = replace(#sql, '''', '''''');
set #sql = 'select * from openquery( #server_name, ''' + #sql + ''')';
end
set #sql = 'insert #databases (database_id, database_name)' + #sql;
set #sql = replace(#sql, '#server_name', #server_name);
exec (#sql);
delete #databases
where database_name = 'ReportServer';
-- Loop databases
declare #rowNum int = 0;
while #rowNum <= (select max(rownum) from #databases)
begin
-- Database loop initializations
set #rowNum += 1;
declare
#database_id nvarchar(max),
#database_name nvarchar(max);
select #database_id = database_id,
#database_name = dbo.AddBracketsWhenNecessary(database_name)
from #databases
where rownum = #rowNum;
-- Get object dependency info
set #sql = '
with
getTableColumnIds as (
select table_id = o.object_id,
table_name = o.name,
column_id = c.column_id,
column_name = c.name
from #database_name.sys.objects o
join #database_name.sys.all_columns c on o.object_id = c.object_id
)
#insertStatement
select ''#server_name'',
db_name(#database_id),
object_schema_name(referencing_id, #database_id),
object_name(referencing_id, #database_id),
referencing_column = ringTCs.column_name,
isnull(referenced_server_name, ''#server_name''),
isnull(referenced_database_name, db_name(#database_id)),
isnull(referenced_schema_name, ''dbo''),
referenced_entity_name,
referenced_column = redTCs.column_name
from #database_name.sys.sql_expression_dependencies d
left join getTableColumnIds ringTCs
on d.referencing_id = ringTCs.table_id
and d.referencing_minor_id = ringTCs.column_id
left join getTableColumnIds redTCs
on d.referenced_id = redTCs.table_id
and d.referenced_minor_id = redTCs.column_id
';
set #sql = replace(#sql, '#database_id', #database_id);
set #sql = replace(#sql, '#database_name', #database_name);
if #server_is_local = 0
begin
set #sql = replace(#sql, '''', '''''');
set #sql = replace(#sql, '#insertStatement', '');
set #sql = 'select * from openquery(#server_name, ''' + #sql + ''')';
end
set #sql = replace(#sql, '#insertStatement', 'insert #dependencies ');
set #sql = replace(#sql, '#server_name', #server_name);
exec (#sql);
-- Database loop terminations
if ##rowcount > 0
set #server_had_some_inserts = 1;
end -- database loop
-- server loop terminations
if #server_had_some_inserts = 0
begin
declare #remote_user_name nvarchar(255);
select #remote_user_name = remote_name
from sys.linked_logins li
join sys.servers s on li.server_id = s.server_id
where remote_name is not null
and s.name = 'sisag'
print (
'No dependencies found for ' + #server_name + '. ' +
'If this is unexpected, you may need to run "grant view any definition to ' +
'[' + isnull(#remote_user_name, '?') + ']" ' +
'on the remote server.'
);
end
end -- server loop
-- Terminate
select * from #dependencies
The code for AddBracketsWhenNecessary:
create function AddBracketsWhenNecessary (
#objectName nvarchar(250)
)
returns nvarchar(250) as
begin
if left(#objectName, 1) = '[' and right(#objectName, 1) = ']'
return #objectName;
declare #hasInvalidCharacter bit;
select #hasInvalidCharacter = max(isInvalid)
from dbo.splitString(#objectName, null) chars
cross apply (select
isLetter = patindex('[a-z,_]', val),
isNumber = PATINDEX('[0-9]', val)
) getCharType
cross apply (select
isInvalid =
case
when isLetter = 1 then 0
when isNumber = 1 and not chars.id = 1 then 0
else 1
end
) getValidity
return
case when #hasInvalidCharacter = 1 then '[' else '' end
+ #objectName
+ case when #hasInvalidCharacter = 1 then ']' else '' end;
end
Any finally, my splitter function (but see Arnold Fribble here if you want a simpler version, or use the built in function if you have SqlServer 2016 or above):
create function splitString (
#stringToSplit nvarchar(max),
#delimiter nvarchar(50)
)
returns table as
return
with
split_by_delimiter as (
select id = 1,
start = 1,
stop = convert(int,
charindex(#delimiter, #stringToSplit)
)
union all
select id = id + 1,
start = newStart,
stop = convert(int,
charindex(#delimiter, #stringToSplit, newStart)
)
from split_by_delimiter
cross apply (select newStart = stop + len(#delimiter)) ap
where Stop > 0
),
split_into_characters as (
select id = 1,
chr = left(#stringToSplit,1)
union all
select id = id + 1,
chr = substring(#stringToSplit, ID + 1, 1)
from split_into_characters
where id < len(#stringToSplit)
)
select id,
val =
ltrim(rtrim(substring(
#stringToSplit,
start,
case
when stop > 0 then stop - start
else len(#stringtosplit)
end
)))
from split_by_delimiter
where len(#delimiter) > 0
union all
select id,
val = chr
from split_into_characters
where #delimiter = ''
or #delimiter is null
I had to make some small changes from the real code I use, so if there are any reference errors, please let me know in the comments and I'll edit.
A Query I often use to find the tables used from other databases is the following:
SELECT OBJECT_NAME (referencing_id) AS referencing_object, referenced_database_name,
referenced_schema_name, referenced_entity_name
FROM sys.sql_expression_dependencies
WHERE referenced_database_name IS NOT NULL
AND is_ambiguous = 0;
This gives you all tables used in the stored procedures / views that origin from this database, but also other databases.
source
I upgraded one of the answer adding referencing/referenced id and referencing/referenced type like table, view, etc.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[get_crossdatabase_dependencies] AS
SET NOCOUNT ON;
CREATE TABLE #databases(
database_id int,
database_name sysname
);
INSERT INTO #databases(database_id, database_name)
SELECT database_id, [name]
FROM sys.databases
WHERE 1 = 1
AND [state] <> 6 /* ignore offline DBs */
AND database_id NOT IN (4, 5, 6, 7, 11, 13, 14); /* ignore system DBs */
DECLARE
#database_id int,
#database_name sysname,
#sql varchar(max);
CREATE TABLE #dependencies(
referencing_id int,
referencing_database varchar(max),
referencing_schema varchar(max),
referencing_object_name varchar(max),
referencing_type varchar(max),
referenced_id int,
referenced_server varchar(max),
referenced_database varchar(max),
referenced_schema varchar(max),
referenced_object_name varchar(max),
referenced_type varchar(max),
);
WHILE (SELECT COUNT(*) FROM #databases) > 0 BEGIN
SELECT TOP 1 #database_id = database_id,
#database_name = database_name
FROM #databases;
SET #sql = 'INSERT INTO #dependencies select
referencing_id,
DB_NAME(' + convert(varchar,#database_id) + '),
OBJECT_SCHEMA_NAME(referencing_id,'
+ convert(varchar,#database_id) +'),
OBJECT_NAME(referencing_id,' + convert(varchar,#database_id) + '),
CASE
WHEN OBJECTPROPERTY(referencing_id, ''IsTable'') = 1 THEN ''Table''
WHEN OBJECTPROPERTY(referencing_id, ''IsView'') = 1 THEN ''View''
WHEN OBJECTPROPERTY(referencing_id, ''IsProcedure'') = 1 THEN ''Procedure''
WHEN OBJECTPROPERTY(referencing_id, ''IsTableFunction'') = 1 THEN ''Table-valued Function''
ELSE ''Unknown''
END AS referencing_type,
referenced_id,
referenced_server_name,
ISNULL(referenced_database_name, db_name('
+ convert(varchar,#database_id) + ')),
referenced_schema_name,
referenced_entity_name,
CASE
WHEN OBJECTPROPERTY(referenced_id, ''IsTable'') = 1 THEN ''Table''
WHEN OBJECTPROPERTY(referenced_id, ''IsView'') = 1 THEN ''View''
WHEN OBJECTPROPERTY(referenced_id, ''IsProcedure'') = 1 THEN ''Procedure''
WHEN OBJECTPROPERTY(referenced_id, ''IsTableFunction'') = 1 THEN ''Table-valued Function''
ELSE ''Unknown''
END AS referenced_type
FROM ' + quotename(#database_name) + '.sys.sql_expression_dependencies
ORDER BY
referencing_type';
EXEC(#sql);
DELETE FROM #databases WHERE database_id = #database_id;
END;
SET NOCOUNT OFF;
SELECT * FROM #dependencies;