forked from ktaranov/sqlserver-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dbo.sp_helpExpandView.sql
455 lines (396 loc) · 16.7 KB
/
dbo.sp_helpExpandView.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
IF OBJECT_ID('dbo.sp_helpExpandView', 'P') IS NULL
EXECUTE ('CREATE PROCEDURE dbo.sp_helpExpandView AS SELECT 1');
GO
ALTER PROCEDURE dbo.sp_helpExpandView (
@ViewName NVARCHAR(512),
@OutputFormat VARCHAR(15) = 'All',
@ShowObjectCount BIT = 0,
@Debug BIT = 0
)
AS
/*===============================================================================
-- sp_helpExpandView
--
-- Written By: Andy Yun
-- Created On: 2015-02-27
--
-- Summary:
-- This tool script was written to aid in de-constructing nested views and
-- identifying all base objects underneath.
--
-- Supports:
-- Scalar & Table-Valued Functions
-- Schemas
-- Synonyms to other DBs
--
--
-- Updates:
-- Date Developer Remarks
-- 2015-03-02 AYun V1: Initial Release
-- 2015-03-03 AYun V2: Rewrote to properly handle external database
-- references in child objects.
-- 2015-03-03 AYun V2.1: Tweaked to remove duplicate results.
-- Added sorting to output
-- 2015-03-09 AYun V2.11: Adjusted horizontal output ORDER BY clause.
-- 2015-03-30 AYun V2.2: New Output - List all objects referenced & count
-- 2015-05-20 AYun V2.3: Corrected CURSOR & Database Name with space issue.
-- Thanks Mickey Stuewe!
---------------------------------------------------------------------------------
-- License:
-- This code is free to use for personal, educational, and internal corporate
-- purposes provided that this header is preserved.
-- (c) 2015 Andy Yun
===============================================================================*/
BEGIN
BEGIN TRY
IF OBJECT_ID(@ViewName) IS NULL
BEGIN
RAISERROR('@ViewName undefined or does not exist.', 11, 1);
END
DECLARE @BaseObject_DBName NVARCHAR(256),
@ChildObjectsFound SMALLINT = 1,
@HierarchyLevelToSearch TINYINT = 1,
@MinObjectHierarchyID INT = 0,
@TmpObjectHierarchyID INT = NULL,
@MaxObjectHierarchyID INT = 1,
@DBName NVARCHAR(256),
@sqlCMD NVARCHAR(4000);
-- sys.dm_sql_referenced_entities() dislikes brackets. Must strip out.
SET @ViewName = REPLACE(REPLACE(@ViewName, ']', ''), '[', '');
-- if no schema specified, append dbo
IF CHARINDEX('.', @ViewName, 1) = 0
SET @ViewName = 'dbo.' + @ViewName;
IF @Debug = 1 SELECT '@ViewName', @ViewName
-----
-- Temp Tables
IF OBJECT_ID('tempdb.dbo.#tmpObjectHierarchy', 'U') IS NOT NULL
DROP TABLE #tmpObjectHierarchy;
IF OBJECT_ID('tempdb.dbo.#tmpAllObjects', 'U') IS NOT NULL
DROP TABLE #tmpAllObjects;
IF OBJECT_ID('tempdb.dbo.#tmpExternalObjectsToProcess', 'U') IS NOT NULL
DROP TABLE #tmpExternalObjectsToProcess;
-- Create final output table containing full object hierarchy
CREATE TABLE #tmpObjectHierarchy (
ObjectHierarchyID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
HierarchyLvl TINYINT,
BaseObject_DBName NVARCHAR(512),
BaseObject_FullName NVARCHAR(512),
Child_DBName NVARCHAR(512),
Child_SchemaName NVARCHAR(256),
Child_ObjectName NVARCHAR(256),
Child_FullName NVARCHAR(512),
Child_Type CHAR(2),
Child_ObjectID INT,
Synonym_BaseObjectName NVARCHAR(1035),
Synonym_BaseObjectType NVARCHAR(2),
ParentObjectHierarchyID INT
);
-- Populate #tmpObjectHierarchy with base level data
INSERT INTO #tmpObjectHierarchy (
BaseObject_DBName,
BaseObject_FullName,
Child_FullName,
Child_SchemaName,
Child_ObjectName,
Child_Type,
Child_ObjectID,
HierarchyLvl,
Child_DBName,
ParentObjectHierarchyID
)
SELECT DISTINCT
DB_NAME(),
@ViewName,
COALESCE(schemas.name, dm_sql_referenced_entities.referenced_schema_name) + '.' + dm_sql_referenced_entities.referenced_entity_name,
COALESCE(schemas.name, dm_sql_referenced_entities.referenced_schema_name),
dm_sql_referenced_entities.referenced_entity_name,
COALESCE(schemas.type, NULL),
dm_sql_referenced_entities.referenced_id,
1 AS HierarchyLvl,
COALESCE(dm_sql_referenced_entities.referenced_database_name, DB_NAME()),
0 AS ParentObjectHierarchyID
FROM sys.dm_sql_referenced_entities(@ViewName, 'OBJECT')
LEFT OUTER JOIN (
SELECT objects.object_id, schemas.name, objects.type
FROM sys.objects
INNER JOIN sys.schemas
ON objects.schema_id = schemas.schema_id
) schemas
ON dm_sql_referenced_entities.referenced_id = schemas.object_id
WHERE referenced_minor_id = 0;
SELECT @MaxObjectHierarchyID = MAX(ObjectHierarchyID)
FROM #tmpObjectHierarchy;
IF @Debug = 1 SELECT '#tmpObjectHierarchy', * FROM #tmpObjectHierarchy;
--SELECT '1-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy;
-----
-- Loop for each new Hierarchy Level #tmpObjectHierarchy
WHILE @MinObjectHierarchyID < @MaxObjectHierarchyID
BEGIN
IF @Debug = 1 SELECT @MinObjectHierarchyID AS '@MinObjectHierarchyID', @MaxObjectHierarchyID AS '@MaxObjectHierarchyID';
-----
-- For each DB found in #tmpObjectHierarchy, loop through each recordset of each Database
DECLARE cursorEachDatabase CURSOR FAST_FORWARD
FOR
SELECT DISTINCT Child_DBName
FROM #tmpObjectHierarchy;
OPEN cursorEachDatabase;
FETCH NEXT
FROM cursorEachDatabase
INTO @DBName
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Debug = 1 SELECT @DBName AS '@DBName';
SET @sqlCMD = N'
INSERT INTO #tmpObjectHierarchy (
BaseObject_DBName,
BaseObject_FullName,
Child_FullName,
Child_SchemaName,
Child_ObjectName,
Child_ObjectID,
Child_Type,
HierarchyLvl,
Child_DBName,
ParentObjectHierarchyID
)
SELECT DISTINCT
#tmpObjectHierarchy.Child_DBName,
#tmpObjectHierarchy.Child_FullName,
COALESCE(dm_sql_referenced_entities.referenced_schema_name, schemas.name) + ''.'' + dm_sql_referenced_entities.referenced_entity_name,
COALESCE(dm_sql_referenced_entities.referenced_schema_name, schemas.name),
dm_sql_referenced_entities.referenced_entity_name,
dm_sql_referenced_entities.referenced_id,
COALESCE(schemas.type, NULL),
#tmpObjectHierarchy.HierarchyLvl + 1 AS HierarchyLvl,
COALESCE(dm_sql_referenced_entities.referenced_database_name, #tmpObjectHierarchy.Child_DBName),
#tmpObjectHierarchy.ObjectHierarchyID
FROM #tmpObjectHierarchy
CROSS APPLY [@DatabaseName].sys.dm_sql_referenced_entities(#tmpObjectHierarchy.Child_FullName, ''OBJECT'')
LEFT OUTER JOIN (
SELECT objects.object_id, schemas.name, objects.type
FROM [@DatabaseName].sys.objects
INNER JOIN [@DatabaseName].sys.schemas
ON objects.schema_id = schemas.schema_id
) schemas
ON dm_sql_referenced_entities.referenced_id = schemas.object_id
WHERE dm_sql_referenced_entities.referenced_minor_id = 0
AND #tmpObjectHierarchy.Child_DBName = ''@DatabaseName''
AND #tmpObjectHierarchy.ObjectHierarchyID >= @MinObjectHierarchyID;'
SET @sqlCMD = REPLACE(@sqlCMD, '@MinObjectHierarchyID', @MinObjectHierarchyID)
SET @sqlCMD = REPLACE(@sqlCMD, '@HierarchyLevelToSearch', @HierarchyLevelToSearch)
SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName)
IF @Debug = 1 PRINT @sqlCMD;
EXECUTE sp_executesql @sqlCMD
-- Must identify if any of these objects are synonyms.
SET @sqlCMD = N'
-- Update synonym objects with fully qualified base_object_name values
UPDATE #tmpObjectHierarchy
SET Child_Type = synonyms.type,
Child_DBName = PARSENAME(synonyms.base_object_name, 3),
Synonym_BaseObjectName = synonyms.base_object_name
FROM [@DatabaseName].sys.synonyms
INNER JOIN [@DatabaseName].sys.schemas
ON synonyms.schema_id = schemas.schema_id
WHERE #tmpObjectHierarchy.Child_DBName = ''@DatabaseName''
AND #tmpObjectHierarchy.ObjectHierarchyID >= @MinObjectHierarchyID
AND synonyms.object_id = #tmpObjectHierarchy.Child_ObjectID;';
SET @sqlCMD = REPLACE(@sqlCMD, '@MinObjectHierarchyID', @MinObjectHierarchyID)
SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName)
IF @Debug = 1 PRINT @sqlCMD;
EXECUTE sp_executesql @sqlCMD
FETCH NEXT
FROM cursorEachDatabase
INTO @DBName
END
CLOSE cursorEachDatabase;
DEALLOCATE cursorEachDatabase;
SELECT
@TmpObjectHierarchyID = @MinObjectHierarchyID,
@MinObjectHierarchyID = @MaxObjectHierarchyID + 1,
@MaxObjectHierarchyID = MAX(ObjectHierarchyID)
FROM #tmpObjectHierarchy;
-- Serialize
INSERT INTO #tmpObjectHierarchy (
BaseObject_DBName,
BaseObject_FullName,
Child_FullName,
Child_SchemaName,
Child_ObjectName,
Child_ObjectID,
HierarchyLvl,
Child_DBName,
ParentObjectHierarchyID
)
SELECT DISTINCT
#tmpObjectHierarchy.BaseObject_DBName AS BaseObject_DBName,
#tmpObjectHierarchy.Child_FullName AS BaseObject_FullName,
PARSENAME(Synonym_BaseObjectName, 2) + '.' + PARSENAME(Synonym_BaseObjectName, 1) AS Child_FullName,
PARSENAME(Synonym_BaseObjectName, 2) AS Child_SchemaName,
PARSENAME(Synonym_BaseObjectName, 1) AS Child_ObjectName,
OBJECT_ID(#tmpObjectHierarchy.Synonym_BaseObjectName) AS Child_ObjectID,
#tmpObjectHierarchy.HierarchyLvl + 1 AS HierarchyLvl,
#tmpObjectHierarchy.Child_DBName AS Child_DBName,
#tmpObjectHierarchy.ObjectHierarchyID AS ParentObjectHierarchyID
FROM #tmpObjectHierarchy
WHERE #tmpObjectHierarchy.Child_Type = 'SN'
AND #tmpObjectHierarchy.ObjectHierarchyID > @TmpObjectHierarchyID;
IF @Debug = 1 SELECT '2-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy;
END
IF @Debug = 1 SELECT '3-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy;
-- All children of synonyms will be missing a Type. Need to go back through all DBs and materialize these.
IF EXISTS(SELECT 1 FROM #tmpObjectHierarchy WHERE Child_Type IS NULL)
BEGIN
-----
-- For each DB found in #tmpObjectHierarchy, loop through each recordset of each Database
DECLARE cursorEachDatabase_2 CURSOR FAST_FORWARD
FOR
SELECT DISTINCT Child_DBName
FROM #tmpObjectHierarchy
WHERE Child_Type IS NULL;
OPEN cursorEachDatabase_2;
FETCH NEXT
FROM cursorEachDatabase_2
INTO @DBName
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Debug = 1 SELECT @DBName AS '@DBName';
SET @sqlCMD = N'
UPDATE #tmpObjectHierarchy
SET Child_Type = objects.type
FROM [@DatabaseName].sys.objects
WHERE #tmpObjectHierarchy.Child_DBName = ''@DatabaseName''
AND #tmpObjectHierarchy.Child_Type IS NULL
AND #tmpObjectHierarchy.Child_ObjectID = objects.object_id;';
SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName)
IF @Debug = 1 PRINT @sqlCMD;
EXECUTE sp_executesql @sqlCMD
FETCH NEXT
FROM cursorEachDatabase_2
INTO @DBName
END
CLOSE cursorEachDatabase_2;
DEALLOCATE cursorEachDatabase_2;
END
-----
-- Unflattened Output
IF (
@OutputFormat = 'Vertical'
OR @OutputFormat = 'All'
)
BEGIN
SELECT DISTINCT
HierarchyLvl,
BaseObject_FullName,
Child_DBName,
Child_FullName,
Child_Type,
ObjectHierarchyID,
ParentObjectHierarchyID
FROM #tmpObjectHierarchy
ORDER BY HierarchyLvl,
BaseObject_FullName,
Child_DBName,
Child_FullName
END
-----
-- Dynamically create flattened output
IF (
@OutputFormat = 'Horizontal'
OR @OutputFormat = 'All'
)
BEGIN
DECLARE @MaxHierarchyID INT,
@HierarchyID INT = 2;
SELECT @MaxHierarchyID = MAX(HierarchyLvl)
FROM #tmpObjectHierarchy;
DECLARE @sqlSelect NVARCHAR(4000) = N'
SELECT DISTINCT
Lvl1.BaseObject_FullName
, Lvl1.HierarchyLvl AS Lvl_1
, Lvl1.Child_FullName AS Obj_1
, Lvl1.Child_Type AS Typ_1',
@sqlFrom NVARCHAR(4000) = N'
FROM #tmpObjectHierarchy Lvl1';
WHILE @HierarchyID <= @MaxHierarchyID
BEGIN
SET @sqlSelect = @sqlSelect + N'
, COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.HierarchyLvl, ''' + CAST(@HierarchyID AS NVARCHAR(4000)) + ''') AS Lvl_' + CAST(@HierarchyID AS NVARCHAR(4000)) + '
, COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.Child_FullName, SPACE(0)) AS Obj_' + CAST(@HierarchyID AS NVARCHAR(4000)) + '
, COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.Child_Type, SPACE(0)) AS Typ_' + CAST(@HierarchyID AS NVARCHAR(4000));
SET @sqlFrom = @sqlFrom + N'
LEFT OUTER JOIN #tmpObjectHierarchy Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '
ON Lvl' + CAST((@HierarchyID - 1) AS NVARCHAR(4000)) + '.ObjectHierarchyID = Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.ParentObjectHierarchyID
AND Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.HierarchyLvl = ' + CAST(@HierarchyID AS NVARCHAR(4000));
SET @HierarchyID = @HierarchyID + 1;
END
SET @sqlCMD = @sqlSelect + @sqlFrom + N'
WHERE Lvl1.HierarchyLvl = 1
ORDER BY Lvl1.BaseObject_FullName, Lvl1.HierarchyLvl, Lvl1.Child_FullName;';
EXEC sp_executesql @sqlCMD;
END
-----
-- If enabled, show list of all objects referenced & # of times referenced.
IF @ShowObjectCount = 1
BEGIN
SELECT
CASE
WHEN #tmpObjectHierarchy.Child_Type = 'U'
THEN 'User Table'
WHEN #tmpObjectHierarchy.Child_Type = 'S'
THEN 'System Table'
WHEN #tmpObjectHierarchy.Child_Type = 'V'
THEN 'View'
WHEN #tmpObjectHierarchy.Child_Type = 'FN'
THEN 'Scalar Function'
WHEN #tmpObjectHierarchy.Child_Type = 'IF'
THEN 'Inline Function'
WHEN #tmpObjectHierarchy.Child_Type = 'TF'
THEN 'Table Valued Function'
WHEN #tmpObjectHierarchy.Child_Type = 'P'
THEN 'Stored Procedure'
WHEN #tmpObjectHierarchy.Child_Type = 'SN'
THEN 'Synonym'
ELSE #tmpObjectHierarchy.Child_Type
END AS ObjectType,
#tmpObjectHierarchy.Child_FullName AS Object_FullName,
COUNT(1) AS ReferencedCount
FROM #tmpObjectHierarchy
GROUP BY #tmpObjectHierarchy.Child_Type,
#tmpObjectHierarchy.Child_FullName
ORDER BY ObjectType,
#tmpObjectHierarchy.Child_FullName
END
END TRY
BEGIN CATCH
IF (SELECT CURSOR_STATUS('global','cursorEachDatabase')) >= -1
BEGIN
DEALLOCATE cursorEachDatabase
END
IF (SELECT CURSOR_STATUS('global','cursorEachDatabase_2')) >= -1
BEGIN
DEALLOCATE cursorEachDatabase_2
END
DECLARE
@ErrorNumber INT,
@ErrorSeverity INT,
@ErrorState INT,
@ErrorProcedure NVARCHAR(128),
@ErrorLine INT,
@ErrorMessage NVARCHAR(4000);
SELECT
@ErrorNumber = ERROR_NUMBER(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorProcedure = ERROR_PROCEDURE(),
@ErrorLine = ERROR_LINE(),
@ErrorMessage = ERROR_MESSAGE();
SELECT
@ErrorNumber AS ErrorNumber,
@ErrorSeverity AS ErrorSeverity,
@ErrorState AS ErrorState,
@ErrorProcedure AS ErrorProcedure,
@ErrorLine AS ErrorLine,
@ErrorMessage AS ErrorMessage;
END CATCH
END
GO