CiAgICA8IS0tIExpbmtlZEluIC0tPgogICAgPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPgogICAgICAgIF9saW5rZWRpbl9wYXJ0bmVyX2lkID0gIjEyMzUwNzMiOwogICAgICAgIHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyA9IHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyB8fCBbXTsKICAgICAgICB3aW5kb3cuX2xpbmtlZGluX2RhdGFfcGFydG5lcl9pZHMucHVzaChfbGlua2VkaW5fcGFydG5lcl9pZCk7CiAgICA8L3NjcmlwdD48c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgICAgICAgKGZ1bmN0aW9uKCl7dmFyIHMgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgic2NyaXB0IilbMF07CiAgICAgICAgICAgIHZhciBiID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7CiAgICAgICAgICAgIGIudHlwZSA9ICJ0ZXh0L2phdmFzY3JpcHQiO2IuYXN5bmMgPSB0cnVlOwogICAgICAgICAgICBiLnNyYyA9ICJodHRwczovL3NuYXAubGljZG4uY29tL2xpLmxtcy1hbmFseXRpY3MvaW5zaWdodC5taW4uanMiOwogICAgICAgICAgICBzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGIsIHMpO30pKCk7CiAgICA8L3NjcmlwdD4KICAgIDxub3NjcmlwdD4KICAgICAgICA8aW1nIGhlaWdodD0iMSIgd2lkdGg9IjEiIHN0eWxlPSJkaXNwbGF5Om5vbmU7IiBhbHQ9IiIgc3JjPSJodHRwczovL3B4LmFkcy5saW5rZWRpbi5jb20vY29sbGVjdC8/cGlkPTEyMzUwNzMmZm10PWdpZiIgLz4KICAgIDwvbm9zY3JpcHQ+CiAgICA8IS0tIEVuZCBMaW5rZWRJbiAtLT4KICAgIA==
Generic filters
Exact matches only
Search in title
Search in excerpt
Search in content

Datenbankübergreifende Dokumentation von SQL-Objekten

In diesem Blogbeitrag werden die pragmatischen Aspekte einer datenbankübergreifenden Dokumentation von SQL-Objekten stichpunktartig erläutert und einige ihrer Facetten näher betrachtet.

Eine solide Datenprojekt-Dokumentation mit den vielfältigen Verknüpfungen innerhalb von Datenbanken kann recht umfangreich werden und daher stellt sich die Frage, welche einfachen Hilfsmittel hierfür generell angebracht sind, die jeweiligen Strukturen und deren Beziehungen darzulegen.

Wer sich sinnvollerweise die Mühe macht und seine erstellten SQL-Objekte und vor allem deren Verknüpfungen grundlegend dokumentiert, hat bei späteren Anpassungen ein leichteres Vorgehen.
Die folgenden SQL-Skripte können bei der grundlegenden generischen Dokumentation von Datenbanken helfen. Der hier beschriebene Aufwand hält sich in Grenzen und lohnt sich auf alle Fälle.

Allgemeine Situation

Jeder, der SQL-Skripte schreibt, weiß, dass eine Dokumentation sehr wesentlich ist: Zu jeder Programmierung gehört auch die Dokumentation als verständliche Beschreibung des Datenmodells.
Aber nicht immer wird eine Dokumentation formuliert, entweder weil die notwendige Zeit einfach fehlt oder die disziplinierte Beschreibung des Quellcodes zu lästig ist.
In der Regel basieren Datenmodelle auf vorgelagerten Datenbanken, welche als verschiedene Daten-quellen zur Verfügung stehen. In diesen laufen vielfältige Skripte, welche bestimmte Tabellen bearbeiten und aufbereiten, so dass die Datenbanken voneinander abhängig werden.
Solange noch aktiv an Skripten im Projekt gearbeitet wird, besteht meistens kein Problem zu verstehen, wie der Code und die Zusammenhänge funktionieren.
Anders sieht es aus wenn mehrere Personen an einem Projekt arbeiten, ein größerer zeitlicher Abstand zur letzten Änderung besteht oder eine Weiterentwicklung eines möglicherweise zusätzlichen Projekts ansteht.
Somit sind in den meisten Fällen die erstellten Skripte letztlich nicht mehr im Ursprungszustand, weil sich die Anforderungen im Nachhinein schrittweise geändert haben.
Manchmal ist es sogar der Fall, dass solange herumgebastelt wird, bis alle Wünsche irgendwie erfüllt werden konnten und ohne eine gute Dokumentation blickt dann leider niemand mehr so richtig durch.
Im Allgemeinen sollte es Ansätze geben, die dem Verfasser von SQL-Skripten das Leben leichter, effektiver und effizienter macht.
In diesem Blogbeitrag geht es darum, wie die Erzeugung und Aktualisierung von gängigen Grundlagen automatisiert und vereinfacht werden kann. Es werden einige interessante Möglichkeiten aufgezeigt.
Der hier beschriebene Aufwand hält sich in Grenzen und lohnt sich auf alle Fälle.

Per SQL eine kleine Datenbankdokumentation erstellen

Die folgenden SQL-Skripte können bei der grundlegenden generischen Dokumentation von Datenbanken helfen.

Die Objekte einer Datenbank

Zu Beginn ein kleines SQL-Skript, welches alle Objekte einer Datenbank aktuell ausgibt:

SELECT 	'Datenbank'       			AS [DataBase]
 	, 	sys.schemas.name        		AS SchemaName
 	,   	sys.objects.name           	AS ObjectName
 	,	sys.objects.type        		AS ObjectType
 	,	sys.objects.create_date 		AS DateCreated
 	, 	sys.objects.modify_date 	AS DateModified
FROM   	sys.objects 
INNER JOIN sys.schemas
	ON	sys.schemas.schema_id = sys.objects.schema_id
ORDER BY 	sys.schemas.name
 	, 	sys.objects.type, sys.objects.name

Die Felder der Tabellen und Sichten einer Datenbank

Hier ein kleines SQL-Skript, welches alle Tabellen und Sichten einer Datenbank mit Spalten und Datentypen aktuell ausgibt:


SELECT 	'Datenbank'       			AS [DataBase]
 	, 	sys.schemas.name    		AS SchemaName
 	,	sys.objects.type    		AS ObjectType
 	,	sys.objects.name     		AS ObjectName
 	,	sys.columns.name     		AS ColumnName
 	, 	sys.types.name       		AS ColumnType
 	,   	sys.columns.max_length	AS ColumnLength
FROM   	sys.objects
INNER JOIN sys.columns
 	ON 	sys.objects.[object_id] = sys.columns.[object_id]
INNER JOIN sys.schemas
 	ON 	sys.schemas.schema_id = sys.objects.schema_id
INNER JOIN sys.types
	ON 	sys.columns.system_type_id = sys.types.system_type_id
WHERE  	sys.objects.type IN ('U', 'V')
			-- U=USER_TABLE, S=SYSTEM_TABLE
			-- V=VIEW, P=SQL_STORED_PROCEDURE
	AND 	sys.types.name <> 'sysname'
ORDER BY 	sys.schemas.name
 	,	sys.objects.type, sys.objects.name
 	, 	sys.columns.column_id

Die Anzahl der Tabellenzeilen schnell und einfach ausgeben

Die Fragestellung „Wie viele Zeilen haben die Tabellen?“ gibt es immer. Die Standardantwort als klassi-scher Ansatz lautet dann:


SELECT 
		COUNT(*)
FROM 
 		[dbo].[Tabelle]

Allerdings können hierbei zwei Probleme auftreten: Der SQL-Server muss zum Bearbeiten dieser Abfrage einen Table-Scan durchführen. Das kann bei Tabellen mit mehreren Millionen Datensätzen eine Weile dauern. Und man kann mit dieser Abfrage nur jeweils eine Tabelle abfragen.
Was macht man aber, wenn man die Zeilenanzahl aller Tabellen einer Datenbank gleichzeitig bestimmen möchte? Mit Hilfe der folgenden Abfrage kann diese Fragestellung problemlos und aktuell gelöst werden:


SELECT 
 		'Datenbank'       					AS [DataBase]
 	, 	sys.schemas.name        				AS SchemaName
 	, 	sys.objects.name 					AS ObjectName
 	, 	sys.dm_db_partition_stats.row_count  	AS [RowCount]
FROM 
 		sys.indexes 
INNER JOIN 
 		sys.objects 
	ON 	sys.indexes.OBJECT_ID = sys.objects.OBJECT_ID
INNER JOIN 
		sys.schemas
	ON	sys.schemas.schema_id = sys.objects.schema_id
INNER JOIN 
		sys.dm_db_partition_stats 
	ON 	sys.indexes.OBJECT_ID = sys.dm_db_partition_stats.OBJECT_ID
	AND 	sys.indexes.index_id = sys.dm_db_partition_stats.index_id
WHERE 
		sys.indexes.index_id < 2
	AND 	sys.objects.is_ms_shipped = 0
ORDER BY 
		sys.schemas.name
	,  	sys.objects.name

Dieses kurze SQL-Skript zeigt alle Benutzertabellen mit der zugehörigen aktuellen Zeilenanzahl.

Der „index_id < 2“ filtert nach „clustered index“ (1) und „hash table“ (0).

Damit auch die Systemtabellen angezeigt werden, muss der Filter “is_ms_shipped = 0” entfernt werden.

Die Abhängigkeiten der Datenbankobjekte

Hier ein kleines SQL-Skript, welches die Objekte und deren Abhängigkeiten einer Datenbank aktuell ausgibt. Hierbei werden auch materialisierte Sichten zusätzlich auf ihre originären Sichten verlinkt:


SELECT	--   Objekte ohne Abhängigkeiten
		''         		AS referencing_server
	,	'Datenbank'       	AS referencing_database		--Datenbankname
 	,  	'dbo'              	AS referencing_schema 		--Datenbankschema
 	, 	O.name             	AS referencing_name
	,  	O.type             	AS referencing_type
	, 	O.object_id        	AS referencing_id
	,	''       	      		AS referenced_server
	, 	''   	        		AS referenced_database
	,  	''              		AS referenced_schema
	,   	''            		AS referenced_name
	, 	NULL     		AS referenced_id
FROM 
		sys.objects AS O
UNION ALL
SELECT	--   Materialisierte Sichten und deren originäre Sichten
		'' AS referencing_server
	,	'Datenbank' AS referencing_database		--Datenbankname
	,  	'dbo' AS referencing_schema				--Datenbankschema
	,	O.name AS referencing_name
	,	O.type AS referencing_type
	,	O.object_id AS referencing_id
	,  	'' AS referenced_server
	,	'Datenbank' AS referenced_database		--Datenbankname
 	,	'dbo' AS referenced_schema 				--Datenbankschema
	, 	REPLACE(O.name, 'TMV_', 'V_') AS referenced_name
 	,  	NULL AS referenced_id
FROM
		sys.objects AS O


WHERE 
		LEFT(O.name,4) = 'TMV_'   
UNION ALL 
SELECT 	--   Objektabhängigkeiten
 		'' AS referencing_server
 	,	'Datenbank' AS referencing_database 		--Datenbankname
	, 	'dbo' AS referencing_schema     			--Datenbankschema
 	,	OBJECT_NAME(D.referencing_id) AS referencing_name
 	,  	ISNULL(O.type,'') AS referencing_type
 	,	D.referencing_id 
 	,    	ISNULL(D.referenced_server_name,'') AS referenced_server
 	,   	ISNULL(D.referenced_database_name,'Datenbank') AS referenced_database


 	,  	ISNULL(D.referenced_schema_name,'dbo') AS referenced_schema
 	,  	ISNULL(D.referenced_entity_name,'') AS referenced_name    
 	, 	D.referenced_id
FROM 
 		sys.sql_expression_dependencies AS D
LEFT OUTER JOIN
		sys.objects AS O
 	ON	OBJECT_NAME(D.referencing_id) = O.name

Ein solches SQL-Skript sollte in jeder Datenbank angelegt sein, welche man in diesem Zusammenhang mitbetrachten möchte.
Diese vorgelagerten SQL-Skripte können dann an dieser Stelle per „union all“ ebenfalls hinzugezogen werden, um eine datenbankübergreifende Dokumentation zu erstellen.

Die Reihenfolge der Objektabhängigkeiten (Ziel zu allen Quellen)

Hier ein kleines SQL-Skript, welches die zusammenhängende Reihenfolge der Objektabhängigkeiten vom Ziel zu allen Quellen basierend der SQL-Sicht von 3.4 aktuell ausgibt:


SELECT  
 		O.referencing_server    				AS [Server]
 	, 	O.referencing_database  			AS [Database]
 	, 	O.referencing_schema    			AS [Schema]
 	,  	O.referencing_type           			AS ObjectType
 	, 	O.referencing_object    				AS ObjectName
 	,  	ISNULL(O.referenced_object5,
 		ISNULL(O.referenced_object4,
 		ISNULL(O.referenced_object3, 
 		ISNULL(O.referenced_object2,
 		ISNULL(O.referenced_object1,
 		''))))) 							AS ReferenceName
 	,	ISNULL(O.referenced_object1, '')   		AS ReferenceObject1
 	,	ISNULL(O.referenced_object2, '')   		AS ReferenceObject2
 	,	ISNULL(O.referenced_object3, '')   		AS ReferenceObject3
 	,	ISNULL(O.referenced_object4, '')   		AS ReferenceObject4
 	,	ISNULL(O.referenced_object5, '')   		AS ReferenceObject5
FROM	(
 		SELECT 
				A.referencing_server
			,	A.referencing_database
 			,   	A.referencing_schema
 			,	A.referencing_name
 			, 	A.referencing_type 
 			, 	A.referencing_id 
 			, 	A.referenced_server 
 			, 	A.referenced_database 
 			,	A.referenced_schema 
 			, 	A.referenced_name 
 			, 	A.referenced_id
 			, 	CASE 
 				WHEN 	A.referencing_name <> '' 
 					OR 	A.referencing_name IS NULL
 					THEN  	A.referencing_database+'.'+ 
 							A.referencing_schema+'.'+  
 							A.referencing_name
 				ELSE  NULL
 				END     		AS referencing_object
 			,	CASE
 				WHEN 	A.referenced_name <> ''
 					OR 	A.referenced_name IS NULL
 					THEN  	A.referenced_database+'.'+ 
 							A.referenced_schema+'.'+  
 							A.referenced_name
 				ELSE  NULL
 				END         	AS referenced_object1
 			,	CASE
 				WHEN 	B.referenced_name <> ''
 					OR 	B.referenced_name IS NULL
 					THEN  	B.referenced_database+'.'+ 
 							B.referenced_schema+'.'+  
 							B.referenced_name
 				ELSE  NULL
 				END         	AS referenced_object2
 			,	CASE
 				WHEN 	C.referenced_name <> ''
 					OR 	C.referenced_name IS NULL
 					THEN  	C.referenced_database+'.'+ 
 							C.referenced_schema+'.'+  
 							C.referenced_name
 				ELSE  NULL
 				END         	AS referenced_object3
 			,	CASE
 				WHEN 	D.referenced_name <> ''
 					OR 	D.referenced_name IS NULL
 					THEN  	D.referenced_database+'.'+ 
 							D.referenced_schema+'.'+  
 							D.referenced_name
 				ELSE  NULL
 				END         	AS referenced_object4
 			,	CASE
 				WHEN 	E.referenced_name <> ''
 					OR 	E.referenced_name IS NULL
 					THEN  	E.referenced_database+'.'+ 
 							E.referenced_schema+'.'+  
 							E.referenced_name
 				ELSE  NULL
 				END         	AS referenced_object5
     	FROM 
 				[SQL-Sicht aus 3.4] 	AS A
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS B
 			ON  	A.referenced_database 	= B.referencing_database
 			AND 	A.referenced_schema 	= B.referencing_schema
 			AND 	A.referenced_name	= B.referencing_name 
 		--  	AND 	A.referenced_server    	= B.referencing_server 
 		--	AND 	A.referenced_id  		= B.referencing_id
		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS C
 			ON  	B.referenced_database 	= C.referencing_database
 			AND 	B.referenced_schema 	= C.referencing_schema
 			AND 	B.referenced_name	= C.referencing_name 
 		--  	AND 	B.referenced_server    	= C.referencing_server 
 		--	AND 	B.referenced_id  		= C.referencing_id
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS D
 			ON  	C.referenced_database 	= D.referencing_database
 			AND 	C.referenced_schema 	= D.referencing_schema
 			AND 	C.referenced_name	= D.referencing_name 
 		--  	AND 	C.referenced_server    	= D.referencing_server 
 		--	AND 	C.referenced_id  		= D.referencing_id
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS E
 			ON  	D.referenced_database = E.referencing_database
 			AND 	D.referenced_schema 	= E.referencing_schema
 			AND 	D.referenced_name	= E.referencing_name 
 		--  	AND 	D.referenced_server    	= E.referencing_server 
 		--	AND 	D.referenced_id  		= E.referencing_id
 	) 	AS O
ORDER BY 	
 		O.referencing_server
 	, 	O.referencing_database
 	, 	O.referencing_schema 
 	,   	O.referencing_object
 	, 	O.referenced_object1 
 	, 	O.referenced_object2
 	, 	O.referenced_object3 
 	, 	O.referenced_object4
 	, 	O.referenced_object5

Die Reihenfolge der Objektabhängigkeiten (Quelle zu allen Zielen)

Hier ein kleines SQL-Skript, welches die zusammenhängende Reihenfolge der Objektabhängigkeiten von der Quelle zu allen Zielen basierend der SQL-Sicht von 3.4 aktuell ausgibt:


SELECT  
 		O.referenced_server    				AS [Server]
 	, 	O.referenced_database  			AS [Database]
 	, 	O.referenced_schema    			AS [Schema]
 	,  	O.referenced_type           			AS ObjectType
 	, 	O.referenced_object    				AS ObjectName
 	,  	ISNULL(O.referencing_object5,
 		ISNULL(O.referencing_object4,
 		ISNULL(O.referencing_object3, 
 		ISNULL(O.referencing_object2,
 		ISNULL(O.referencing_object1,
 		''))))) 							AS ReferenceName
 	,	ISNULL(O.referencing_object1, '')   		AS ReferenceObject1
 	,	ISNULL(O.referencing_object2, '')   		AS ReferenceObject2
 	,	ISNULL(O.referencing_object3, '')   		AS ReferenceObject3
 	,	ISNULL(O.referencing_object4, '')   		AS ReferenceObject4
 	,	ISNULL(O.referencing_object5, '')   		AS ReferenceObject5
FROM	(
 		SELECT 
 				A.referenced_server
			,	A.referenced_database
 			,   	A.referenced_schema
 			,	A.referenced_name
 			, 	A.referenced_type 
 			, 	A.referenced_id 
 			, 	A.referencing_server 
 			, 	A.referencing_database 
 			,	A.referencing_schema 
 			, 	A.referencing_name 
 			, 	A.referencing_id
 			, 	CASE 
 				WHEN 	A.referenced_name <> '' 
 					OR 	A.referenced_name IS NULL
 					THEN  	A.referenced_database+'.'+ 
 							A.referenced_schema+'.'+  
 							A.referenced_name
 				ELSE  NULL
 				END     		AS referenced_object
 			,	CASE
 				WHEN 	A.referencing_name <> ''
 					OR 	A.referencing_name IS NULL
 					THEN  	A.referencing_database+'.'+ 
 							A.referencing_schema+'.'+  
 							A.referencing_name
 				ELSE  NULL
 				END         	AS referencing_object1
 			,	CASE
 				WHEN 	B.referencing_name <> ''
 					OR 	B.referencing_name IS NULL
 					THEN  	B.referencing_database+'.'+ 
 							B.referencing_schema+'.'+  
 							B.referencing_name
 				ELSE  NULL
 				END         	AS referencing_object2
 			,	CASE
 				WHEN 	C.referencing_name <> ''
 					OR 	C.referencing_name IS NULL
 					THEN  	C.referencing_database+'.'+ 
 							C.referencing_schema+'.'+  
 							C.referencing_name
 				ELSE  NULL
 				END         	AS referencing_object3
 			,	CASE
 				WHEN 	D.referencing_name <> ''
 					OR 	D.referencing_name IS NULL
 					THEN  	D.referencing_database+'.'+ 
 							D.referencing_schema+'.'+  
 							D.referencing_name
 				ELSE  NULL
 				END         	AS referencing_object4
 			,	CASE
 				WHEN 	E.referencing_name <> ''
 					OR 	E.referencing_name IS NULL
 					THEN  	E.referencing_database+'.'+ 
 							E.referencing_schema+'.'+  
 							E.referencing_name
 				ELSE  NULL
 				END         	AS referencing_object5
     	FROM 
 				[SQL-Sicht aus 3.4] 	AS A
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS B
 			ON  	A.referencing_database = B.referenced_database
 			AND 	A.referencing_schema 	= B.referenced_schema
 			AND 	A.referencing_name	= B.referenced_name 
 		--  	AND 	A.referencing_server   	= B.referenced_server 
 		--	AND 	A.referencing_id  		= B.referenced_id
		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS C
 			ON  	B.referencing_database = C.referenced_database
 			AND 	B.referencing_schema 	= C.referenced_schema
 			AND 	B.referencing_name	= C.referenced_name 
 		--  	AND 	B.referencing_server   	= C.referenced_server 
 		--	AND 	B.referencing_id  		= C.referenced_id
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS D
 			ON  	C.referencing_database = D.referenced_database
 			AND 	C.referencing_schema 	= D.referenced_schema
 			AND 	C.referencing_name	= D.referenced_name 
 		--  	AND 	C.referencing_server   	= D.referenced_server 
 		--	AND 	C.referencing_id  		= D.referenced_id
 		LEFT OUTER JOIN
 				[SQL-Sicht aus 3.4] 	AS E
 			ON  	D.referencing_database = E.referenced_database
 			AND 	D.referencing_schema 	= E.referenced_schema
 			AND 	D.referencing_name	= E.referenced_name 
 		--  	AND 	D.referencing_server   	= E.referenced_server 
 		--	AND 	D.referencing_id  		= E.referenced_id
 	) 	AS O
ORDER BY 	
 		O.referenced_server
 	, 	O.referenced_database
 	, 	O.referenced_schema 
 	,   	O.referenced_object
 	, 	O.referencing_object1 
 	, 	O.referencing_object2
 	, 	O.referencing_object3 
 	, 	O.referencing_object4
 	, 	O.referencing_object5