SQL : Select a dynamic number of rows as columns - sql

I need to select static colums + a dynamic number of rows as columns in SQL
TABLE 1
-------
HotelID
BlockID
BlockName
TABLE 2
-------
BlockDate (unknown number of these)
NumberOfRooms
Desired Result Row
------------------
HotelID | BlockID | BlockName | 02/10/10 | 02/11/10 | 02/12/10 | ...N
Where the date columns are the unknown number of BlockDate rows.

Do this in the client.
SQL is a fixed column language: you can't have a varible number of columns (even with PIVOT etc). Dynamic SQL is not a good idea.

What you require is a pivot query, to convert row data into columnar:
SELECT t.hotelid,
t.blockid,
t.blockname,
MAX(CASE WHEN t2.blockdate = '02-10-10' THEN t.numberofrooms ELSE NULL END) AS 02_10_10,
MAX(CASE WHEN t2.blockdate = '02-11-10' THEN t.numberofrooms ELSE NULL END) AS 02_11_10,
MAX(CASE WHEN t2.blockdate = '02-12-10' THEN t.numberofrooms ELSE NULL END) AS 02_12_10,
...
FROM TABLE_1 t
JOIN TABLE_2 t2
GROUP BY t.hotelid, t.blockid, t.blockname
Mind that there's nothing to link the tables - realistically TABLE_2 needs hotelid and blockid attributes. As-is, this will return the results of TABLE_2 for every record in TABLE_1...
The database is important, because it will need dynamic SQL to create the MAX(CASE... statements

you are missing a foreign key. I have to assume that BlockId should be PK in table 2?
Also, assuming that this is a legacy db and changing the structure is not an option, i have to ask which platform?
If ms sql, this could easily be achieved using a dynamic sql statement.

I once wrote a stored procedure that did just something like this. Given are a users table with basic details and a variable number of profile properties for users. (The number of profile properties varies per DotNetNuke Portal)
This is straight from DotNetNuke 4.9, check the database schema from there and you'll get the details. Tables involved are Users, UserPortals, UserProfile, ProfilePropertyDefinition
In short words :
I create a temp table with all the profile properties as columns (dynamically)
I fill the temp table with userid (as foreign key to link to users table and all the profile properties data
I do a join on users table and temp table
This gives me one row per user with all profile properties.
Here the code - not perfect but hey its tailored for my needs but should be easy enough to re-use (tried to avoid cursors btw)
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[rows2cols] #portalId INT
AS BEGIN
print 'PortalID=' + convert(varchar,#portalId)
--SET NOCOUNT ON;
declare #idx int
declare #rowcount int
declare #tmpStr nvarchar(max)
declare #ctype nvarchar(max)
declare #cname nvarchar(max)
declare #clen int
declare #createStr nvarchar(max)
---------------------------------------------------------------------------
-- create tmp table --
---------------------------------------------------------------------------
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[xxxx]') AND type in (N'U'))
DROP TABLE [dbo].[xxxx]
print 'Building Temp Table Cols for profile properties...'
set #rowcount = (select count(*) from ProfilePropertyDefinition where PortalID=0 and deleted=0)
set #idx = 1
set #tmpStr = ''
while (#idx <= #rowcount)
begin
-- dynamically generate rownumbers to be able to do loop over them (avoid cursors)
select #cname = t1.PropertyName from
( select ROW_NUMBER() OVER (ORDER BY ViewOrder) as Idx, PropertyName from ProfilePropertyDefinition
where PortalID=0 and deleted=0
) as t1 where t1.Idx = #idx
if (#cname = 'Email' or #cname = 'FirstName' or #cname = 'LastName') begin
set #clen = 1
end else begin
set #tmpStr = #tmpStr + '[' + #cname + '] [nvarchar](500), '
end
set #idx = #idx + 1
end
set #tmpStr = #tmpStr + '[userid] [int] '
set #createStr = 'create table xxxx ( ' + #tmpStr + ' )'
Print #createStr
Exec (#createStr)
---------------------------------------------------------------------------
-- fill tmp table --
---------------------------------------------------------------------------
declare #propName nvarchar(max)
declare #propVal nvarchar(max)
declare #userId int
declare #idx2 int
declare #rowcount2 int
declare #inscol nvarchar(max)
declare #insval nvarchar(max)
set #rowcount = (select count(*) FROM Users LEFT OUTER JOIN UserPortals ON Users.UserID = UserPortals.UserId WHERE UserPortals.PortalId = #portalId)
set #idx = 1
while (#idx <= #rowcount)
begin
-- get userId
select #userId = t1.UserID from (select u.UserID, ROW_NUMBER() OVER (ORDER BY u.UserID) as Idx
from Users as u LEFT OUTER JOIN UserPortals as up ON u.UserID = up.UserId where up.PortalId = #portalId) as t1
where t1.Idx = #idx
set #idx2 = 1
set #rowcount2 = (select count(*) from UserProfile where UserID = #userId)
set #inscol = ''
set #insval = ''
while (#idx2 < #rowcount2)
begin
-- build insert for a specific user
select #propName = t1.PropertyName , #propVal=t1.PropertyValue from
( select ROW_NUMBER() OVER (ORDER BY ProfileID) as Idx, up.PropertyDefinitionID,ppd.PropertyName, up.PropertyValue
from UserProfile as up
inner join ProfilePropertyDefinition as ppd on up.PropertyDefinitionID = ppd.PropertyDefinitionID
where UserID = #userId
) as t1 where t1.Idx = #idx2
if (#propName != 'Firstname' and #propName != 'LastName' and #propName != 'Email')
begin
set #inscol = #inscol + #propName + ', '
set #insval = #insval + 'N''' + replace(#propVal,'''','''''') + ''', '
end
set #idx2 = #idx2 + 1
end
set #inscol = #inscol + 'userid'
set #insval = #insval + convert(nvarchar,#userId)
set #tmpStr = 'insert into xxxx (' + #inscol + ') values (' + #insval + ')'
--print #tmpStr
Exec(#tmpStr)
set #idx = #idx + 1
end
-- -------------------------------------------------------------------------
-- return tmp table & dump --
-- -------------------------------------------------------------------------
SELECT Users.*, xxxx.* FROM xxxx INNER JOIN Users ON xxxx.userid = Users.UserID
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[xxxx]') AND type in (N'U'))
DROP TABLE [dbo].[xxxx]
END

Related

T-SQL function in Select to Only Return Columns with Values

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.

SQL Server loop to table

so I have received the following piece of code and am asked to rewrite it as a table:
USE [Database_name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[DumLoop]
AS
SET nocount ON
IF OBJECT_ID('dbo.DummyLoopy','U') IS NOT NULL DROP TABLE dbo.DummyLoopy
create table dbo.DummyLoopy
(value VARCHAR(1000))
DECLARE
#Counter INT = 1
,#MaxInteger INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_2 INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_3 INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_4 INT = (select count(*) from dbo.OUTPUT_TABLE)
,#MyNumber NVARCHAR(100)
,#JustAChar NVARCHAR (100)
,#SecondRow NVARCHAR(500)
,#1 NVARCHAR(100)
,#2 NVARCHAR(100)
,#3 NVARCHAR(100)
WHILE(#Counter <= #MaxInteger)
BEGIN
SELECT
#MyNumber = convert(varchar(100))
,#JustAChar = '&'
,#SecondRow = '{8181:ABC12345' + convert(varchar(100), [ID]) + '}{123:45678}{LALA:'
,#1 = ':2020:' + '123456789' + convert(varchar(100), [ID])
,#2 = ':2323:ZUP'
,#3 = ':3333:1111' + convert(varchar(100), [ID]) + ',123'
FROM dbo.OUTPUT_TABLE
WHERE main.[ID] = #Counter
insert into dbo.DummyLoopy Values('')
insert into dbo.DummyLoopy Values(#JustAChar)
insert into dbo.DummyLoopy Values(#SecondRow)
insert into dbo.DummyLoopy Values(#1)
insert into dbo.DummyLoopy Values(#2)
insert into dbo.DummyLoopy Values(#3)
SET #Counter = #Counter
END
So this just writes every value on a newline and creates a table with only one column - the column value. I would like it also to write away every value as a new column. I don't even know how to go about it.
Output would then look something like:
&
:2020:1234567891
:2323:ZUP
:3333:11111,123
&
:2020:1234567892
:2323:ZUP
:3333:11112,123
&
:2020:1234567893
:2323:ZUP
:3333:11113,123
etc.
You can use pivot to convert rows to columns in your final output instead of physically creating new columns for every value. Following link can help you out.
Efficiently convert rows to columns in sql server
If there is a must requirement to physically create new columns for every new value in while loop, then one way to achieve that is to use dynamic SQL commands.
Execute Dynamic SQL commands in SQL Server
you could do this :
DECLARE
#Counter INT = 1
, #ID INT
, #MaxInteger INT
IF OBJECT_ID('TempDB..#ID') IS NOT NULL
DROP TABLE #ID
SELECT
ROW_NUMBER() OVER(ORDER BY ID) AS RN
, ID
INTO #ID
FROM
dbo.OUTPUT_TABLE
IF OBJECT_ID('TempDB..#DummyLoopy') IS NOT NULL
DROP TABLE #DummyLoopy
CREATE TABLE #DummyLoopy([value] VARCHAR(1000))
SET #MaxInteger = (SELECT MAX(RN) FROM #ID)
WHILE #Counter <= #MaxInteger
BEGIN
SET #ID = (SELECT ID FROM #ID WHERE RN = #Counter)
INSERT INTO #DummyLoopy
VALUES
(' '),
('&'),
('{8181:ABC12345' + CONVERT(VARCHAR(100), #ID) + '}{123:45678}{LALA:'),
(':2020:' + '123456789' + CONVERT(VARCHAR(100), #ID)),
(':2323:ZUP'),
(':3333:1111' + CONVERT(VARCHAR(100), #ID) + ',123')
SET #Counter = #Counter + 1
END
SELECT * FROM #DummyLoopy

SQL : Apply filter based on the user selection and Filter should match all the values

I have a scenario where i have to Apply filter based on the user selection and Filter should match all the values.
Ex: If user apply the filter for Hotel Amenities it should filter hotel that match all the Amenities selected by User.
HotelID AmenitiesID
1 1
1 2
1 3
2 1
2 4
3 2
3 3
Create PROCEDURE [dbo].[usp_GetHotelListing]
#HotelID INT = 1,
#Amenities varchar(50) = '1,3'
AS
BEGIN
select * from DTHotelAmenities where HotelID = #HotelID and <Searching For Condition>
END
Dynamic query version:
Create PROCEDURE [dbo].[usp_GetHotelListing]
#HotelID INT = 1,
#Amenities varchar(50) = '1,3'
AS
BEGIN
declare #sql nvarchar(max) =
'select * from DTHotelAmenities where HotelID = ' +
cast(#HotelID as nvarchar(max)) +
' and AmenitiesID in(' + #Amenities + ')'
exec(#sql)
END
In general beware of possibility of sql injection threat. You may want to look at sp_executesql alternative.
Maybe something like....
Create PROCEDURE [dbo].[usp_GetHotelListing]
#HotelID INT = 1,
#Amenities varchar(50) = '1,3'
AS
BEGIN
SET NOCOUNT ON;
Declare #xml XML;
Declare #t table (ID INT);
SET #xml = '<root><x>' + REPLACE(#Amenities , ',' , '</x><x>') + '</x></root>'
INSERT INTO #t(ID)
Select r.value('.', 'VARCHAR(max)') value
FROM #xml.nodes('//root/x') as records(r)
select o.*
from DTHotelAmenities o
INNER JOIN #t t ON t.ID = o.AmenitiesID
where o.HotelID = #HotelID
END

How to find orphaned records that point to different tables (paramerization required)?

Table ( A ) has a 1-1 relation with many tables ( B, C, D, ... ) and is defined by two columns:
ObjectType(nvarchar(100)) // name of the other table
_Guid(uniqueidentifier) // record ID in the other table
Additionally, all tables contain an IsDeleted(bit) column.
The question is:
How to list all the records from (A) that point to non-existing record in B, C, D, (...) OR to the record that has IsDeleted = 1 set?
The following will not work because ObjectType must be a parameter:
SELECT ObjectType, _Guid FROM A
where
NOT EXISTS (
select * from ObjectType where oid = _Guid
)
The following will also not work:
SELECT ObjectType, _Guid FROM A
where
NOT EXISTS (
exec('select * from '+ObjectType+' where oid =''' + _Guid + '''')
)
What am I missing?
Queries must have statically known structure to be optimized and executed. You can't use dynamic table names.
Join all possible tables.
SELECT *
FROM A
LEFT JOIN B ON ...
LEFT JOIN C ON ...
LEFT JOIN D ON ...
WHERE
(B.IsDeleted IS NULL AND C.IsDeleted IS NULL AND D.IsDeleted IS NULL)
OR (B.IsDeleted = 1 OR C.IsDeleted = 1 OR D.IsDeleted = 1)
I think I didn't get the condition exactly right because we need to match on ObjectType as well. I'll leave that for you to fix. The important idea is to join all possible tables, though.
This terrible SQL below seems to work:
DECLARE #A_tmpTable TABLE (
idx smallint Primary Key IDENTITY(1,1)
, ObjectType nvarchar(200)
, _Guid nvarchar(100)
, Oid nvarchar(100)
)
DECLARE #orphanedA_tmpTable TABLE (
Oid nvarchar(100)
)
DECLARE #_objectType nvarchar(200)
DECLARE #_Guid nvarchar(100)
DECLARE #_oid nvarchar(100)
DECLARE #i int, #numrows int
DECLARE #found bit
-- populate temp table
INSERT #A_tmpTable SELECT ObjectType, _Guid, Oid FROM A WHERE IsDeleted = 0
--SELECT * FROM #A_tmpTable
-- foreach the #A_tmpTable
SET #i = 1;
SET #numrows = (SELECT COUNT(*) FROM #A_tmpTable)
--SELECT #i, #numrows
IF #numrows > 0
WHILE (#i <= #numrows)
BEGIN
SET #ObjectType = (SELECT TOP 1 ObjectType FROM #A_tmpTable WHERE idx = #i);
SET #_Guid = (SELECT TOP 1 _Guid FROM #A_tmpTable WHERE idx = #i);
SET #_oid = (SELECT TOP 1 Oid FROM #A_tmpTable WHERE idx = #i);
DECLARE #SQL nvarchar(max) = 'IF EXISTS (SELECT * FROM '+#ObjectType+' WHERE OID =''' + #_Guid + ''' AND IsDeleted = 0)
SET #found = 1;
ELSE
SET #found = 0;
';
-- check if table record exists and save the result in the #found variable
exec sp_executesql #SQL, N'#found bit out', #found out
IF #found = 0
INSERT INTO #orphanedA_tmpTable (Oid) VALUES (#_oid);
SET #i = #i + 1
END
SELECT * FROM A WHERE Oid IN (SELECT Oid FROM #orphanedA_tmpTable)

Getting SQL Server Cross database Dependencies

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;