Database Reverse Engineering

August 28th, 2020

In our fast-changing world, observations of certain events, operations, and processes determine the development of business and implementation of any solutions or ideas. Such observations are captured in the form of data usually stored and processed in a database.

The importance lies not only in designing a database but also in implementing your solution in such a way that you can reverse engineer the database without any documentation or external information apart from the code of the solution. This is also true for the whole information system.

In this article, we are going to describe the operation that is opposite to the database design, more specifically, database reverse engineering exemplified by MS SQL Server. Also, we will provide examples of how to obtain various types of information for reverse engineering.

What is reverse engineering and what is it used for?

Reverse engineering of an information system means obtaining the information on software solution architecture via its implementation, that is via code reproduction.

Let’s start by saying that we have already examined SQL Server database design basics with an example of a database for a recruitment service.

It is often the case that a certain information system has been working for quite a long time, and even after many specialists, who were developing and supporting it, leave the company. However, a part of the documentation was kept very comprehensible for those specialists, and some elements of the information system were not documented at all.

Finally, there comes a time when the information system needs major changes or has to be rewritten. In such a situation, it is necessary to go into much detail in order to understand how the information system is working at the moment. As you are probably aware of, the most relevant information lies in the very code, not in the documentation system. However, it has been only 5 years (approximately) since people started writing proper code. Just as the task estimation came to incorporate documentation on software development/changes, the code became more accurate and relevant. Despite this, when the solution requires urgent improvement or development, the quality and relevance of documentation may still be affected.

When this occurs, we usually obtain relevant information on the software architecture through its implementation, which is code. We call this method reverse engineering.

Sooner or later, any information system needs a database to store and process the ever-growing volume of data so that we can analyze it in various ways and make important business decisions based on the analysis.

In this article, we will talk about this kind of database reverse engineering.

But first, let’s define “DB reverse engineering”.

Database reverse engineering is the process that involves obtaining a database schema through its implementation, that is, through the definition of its objects.

Reverse Engineering Support for Database Design

The following key elements are considered important in a database for reverse engineering:

  • primary keys
  • foreign keys (what is referenced)
  • uniqueness constraints

While primary keys and uniqueness constraints are almost always present in the object definition as they are vital for proper code operation, foreign keys may be written right in the table definition.

There are several reasons for that, but the most compelling ones are the following:

  1. A historical factor.
  2. The foreign keys of a replicated database can affect data exchange performance.
  3. To boost essential processes related to data modification (since foreign keys require extra time when changing related data).
  4. Data integrity within foreign keys is not supported at the database level.

The third option is mainly used in real-time systems where even a tiny delay can cause many deaths (transport security, medicine, etc.).

If foreign keys have not been set explicitly in the table definition, then one of the following methods is used:

  • Name a field that is a foreign key in such a way that it is clear which field of which table it refers to. For instance, you could use the name of a referenced table field. At the same time, the field contains the name of its table in the name.

For example:

Reverse engineering database to a diagram
Img.1. The example of foreign keys support via field names

You can see here that the EmployeeID field of the [dbo].[JobHistory] table references the EmployeeID field of the [dbo].[Employee] table.

  • Add the description of the field and what it references to the metadata. In MS SQL Server, one usually uses extended properties with the MS_Description key:
EXEC sys.sp_addextendedproperty @name=N'MS_Description'
							  , @value=N'refers to the field [dbo].[Employee].[EmployeeID]'
							  , @level0type=N'SCHEMA'
							  , @level0name=N'dbo'
							  , @level1type=N'TABLE'
							  , @level1name=N'JobHistory'
							  , @level2type=N'COLUMN'
							  , @level2name=N'EmployeeID';

To learn more about database documenting, refer to Documenting MS SQL Server Databases.

Nonetheless, the better option for reverse engineering support would be to introduce foreign keys in the definition of tables. You can keep several foreign keys disabled if necessary.

To disable a foreign key, use the script below:

ALTER TABLE [dbo].[JobHistory]
NOCHECK CONSTRAINT [FK_JobHistory_Employee_EmployeeID];

Here, the [FK_JobHistory_Employee_EmployeeID] foreign key linking the EmployeeID field of the [dbo].[JobHistory] table with the EmployeeID field of the [dbo].[Employee] table is disabled for the [dbo].[JobHistory] table.

To sum up, this method allows you to quickly get the database schema from the definitions of its tables.

Obtaining Information for Reverse Engineering

To obtain the information on the database schema, you can refer to the following system views:

To find a complete diagram of system views, use this link.

In addition, system information schema views are used.

Obtaining Information about Primary Keys

You can use the following query to get the list of primary keys:

SELECT
  SCHEMA_NAME(tab.schema_id) AS [schema_name]
 ,pk.[name] AS pk_name
 ,ic.index_column_id AS column_id
 ,col.[name] AS column_name
 ,tab.[name] AS table_name
FROM sys.tables tab
INNER JOIN sys.indexes AS pk
  ON tab.object_id = pk.object_id
    AND pk.is_primary_key = 1
INNER JOIN sys.index_columns AS ic
  ON ic.object_id = pk.object_id
    AND ic.index_id = pk.index_id
INNER JOIN sys.columns AS col
  ON pk.object_id = col.object_id
    AND col.column_id = ic.column_id
ORDER BY schema_name(tab.schema_id),
pk.[name],
ic.index_column_id;

We will receive the following output:

Database reverse engineering
Img.2. The list of primary keys (case 1)

As you see, the output shows the following fields:

  • schema_name is the name of the table schema.
  • pk_name is the primary key name.
  • column_id is the number of table column included in the primary key definition.
  • column_name is the table column included in the primary key.
  • table_name is the table name for which the primary key is defined.

The following system views are used:

You can obtain similar information with another query:

SELECT 
    tc.[CONSTRAINT_CATALOG]
	,tc.[CONSTRAINT_SCHEMA]
	,tc.[CONSTRAINT_NAME]
	,tc.[TABLE_NAME]
	,ccu.[COLUMN_NAME]
FROM 
    INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
    INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
        ON tc.[CONSTRAINT_NAME] = ccu.[Constraint_name]
WHERE 
    tc.[CONSTRAINT_TYPE] = 'Primary Key'
ORDER BY tc.[CONSTRAINT_SCHEMA]
	   , tc.[TABLE_NAME]
	   , ccu.[COLUMN_NAME];

The output is as follows:

List of primary keys in database reverse engineering
Img.3. The list of primary keys (case 2)

As a result, the output gives the following fields:

  • CONSTRAINT_CATALOG is the database.
  • CONSTRAINT_SCHEMA is the table schema name.
  • CONSTRAINT_NAME is the primary key name.
  • TABLE_NAME is the table in which the primary key is defined.
  • COLUMN_NAME is the table field included in the primary key.

Here, the following system views from INFORMATION_SCHEMA are used:

Obtaining Information about Foreign Keys

To obtain the list of foreign keys, apply the query below:

SELECT
  f.name AS ForeignKey
 ,SCHEMA_NAME(f.SCHEMA_ID) AS SchemaName
 ,OBJECT_NAME(f.parent_object_id) AS TableName
 ,COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName
 ,SCHEMA_NAME(o.SCHEMA_ID) ReferenceSchemaName
 ,OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName
 ,COL_NAME(fc.referenced_object_id, fc.referenced_column_id)
  AS ReferenceColumnName
 ,f.create_date
FROM sys.foreign_keys AS f
INNER JOIN sys.foreign_key_columns AS fc
  ON f.OBJECT_ID = fc.constraint_object_id
INNER JOIN sys.objects AS o
  ON o.OBJECT_ID = fc.referenced_object_id
ORDER BY SchemaName, TableName, ColumnName, ReferenceSchemaName,
ReferenceTableName, ReferenceColumnName;

The result will be as follows:

Information about foreign keys
Img.4. The list of foreign keys (case 1)

The output contains the following columns:

  • ForeignKey is the name of the foreign key constraint.
  • SchemaName is the table schema name that contains the foreign key.
  • TableName is the table name that contains the foreign key.
  • ColumnName is the referenced table column.
  • ReferenceSchemaName is the referenced table schema name.
  • ReferenceTableName is the referenced table name.
  • ReferenceColumnName is the referenced table column.
  • create_date is the date and time of the foreign key creation.

The following system views are used here:

You can also get similar information with a different query:

SELECT
  ccu.table_schema AS SourceSchemaName
 ,ccu.table_name AS SourceTable
 ,ccu.constraint_name AS SourceConstraint
 ,ccu.column_name AS SourceColumn
 ,kcu.table_schema AS TargetSchemaName
 ,kcu.table_name AS TargetTable
 ,kcu.column_name AS TargetColumn
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
  ON ccu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
  ON kcu.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
ORDER BY SourceSchemaName, SourceTable, SourceColumn,
TargetSchemaName, TargetTable, TargetColumn;

The output is as follows:

Information obtained about database foreign keys
Img.5. The list of foreign keys (case 2)

As a result, the following columns are shown in the output:

  • SourceSchemaName is the schema name of the table containing the foreign key.
  • SourceTable is the name of the table containing the foreign key.
  • SourceConstraint is the name of the foreign key constraint.
  • SourceColumn is the referenced table column.
  • TargetSchemaName is the referenced table schema name.
  • TargetTableName is the referenced table name.
  • TargetColumnName is the referenced table column.

The following system views from INFORMATION_SCHEMA are used:

  • CONSTRAINT_COLUMN_USAGE indicates the table fields that take part in constraints.
  • REFERENTIAL_CONSTRAINTS indicate foreign key constraints.
  • KEY_COLUMN_USAGE indicates the table columns included in the keys.

Obtaining Information about Uniqueness Constraints

To get the list of uniqueness constraints, run the following query:

SELECT SCHEMA_NAME(t.[schema_id]) AS [schema_name],
   i.[name] AS constraint_name,
   t.[name] AS table_name,
   c.[name] AS column_name,
   ic.key_ordinal AS column_position,
   ic.is_descending_key AS is_desc
FROM sys.indexes i
   INNER JOIN sys.index_columns ic
      ON i.index_id = ic.index_id AND i.[object_id] = ic.[object_id]
   INNER JOIN sys.tables AS t 
      ON t.[object_id] = i.[object_id]
   INNER JOIN sys.columns c
      ON t.[object_id] = c.[object_id] AND ic.[column_id] = c.[column_id]
WHERE i.is_unique_constraint = 1
ORDER BY [schema_name], constraint_name, column_position;

The output is as follows:

List of uniqueness constraints obtained with database reverse engineering
Img.6.  The list of uniqueness constraints (case 1)

Accordingly, the following fields are output:

  • schema_name is the table schema name that has the definition of a uniqueness constraint.
  • constraint_name is the uniqueness constraint name.
  • table_name is the table name that has a definition of uniqueness constraint.
  • column_name is the table column included in the uniqueness constraint.
  • column_position is the table column position in the definition of uniqueness constraint.
  • is_desc means that sorting by table column in the unique index is descending (1 indicates a descending order, 0 indicates an ascending order).

The following system views are used here:

Additionally, similar information can be obtained in the following way:

SELECT 
    tc.[CONSTRAINT_CATALOG]
	,tc.[CONSTRAINT_SCHEMA]
	,tc.[CONSTRAINT_NAME]
	,tc.[TABLE_NAME]
	,ccu.[COLUMN_NAME]
FROM 
    INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
    INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
        ON tc.[CONSTRAINT_NAME] = ccu.[Constraint_name]
WHERE 
   tc.[CONSTRAINT_TYPE] = 'UNIQUE'
ORDER BY tc.[CONSTRAINT_SCHEMA]
	   , tc.[TABLE_NAME]
	   , ccu.[COLUMN_NAME];

We will obtain the following result:

List of uniqueness constraints obtained with database reverse engineering
Img.7. The list of uniqueness constraints (case 2)

As a result, we can see the following fields in the output:

  • CONSTRAINT_CATALOG is the database.
  • CONSTRAINT_SCHEMA is the table schema name.
  • CONSTRAINT_NAME is the uniqueness constraint name.
  • TABLE_NAME is the table that has a definition of the uniqueness constraint.
  • COLUMN_NAME is the table column included in the uniqueness constraint.

The following system views from INFORMATION_SCHEMA are used:

Obtaining Information about Heaps

It bears reminding that a table without a clustered index is called a heap.

To get a list of heaps, run the following query:

SELECT  OBJECT_SCHEMA_NAME(tbl.[object_id]) AS SchemaName ,
        OBJECT_NAME(tbl.[object_id]) AS TableName
FROM    sys.tables AS tbl
        JOIN sys.indexes i ON i.[object_id] = tbl.[object_id]
WHERE   i.[type_desc] = 'HEAP'
ORDER BY TableName;

There are no heaps in the JobEmplDB database, hence, we need to run this script in another database that has heaps. Take the SRV database as an example. In that case, you can get the following result:

Information about heaps in the database
Img.8. The list of heaps

As a result, the output shows the following fields:

  • SchemaName is the schema name of the heap.
  • TableName is the heap name.

The following system views are used:

Obtaining Key Information about Tables, Views, and Their Fields

To get the key information about tables and their fields, use the following query:

SELECT
  SCHEMA_NAME(tbl.[schema_id]) AS [SchemaName]
 ,tbl.[name] AS [Name]
 ,col.[name] AS [ColumnName]
 ,dc.[name] AS [DefaultConstraintName]
 ,dc.[definition] AS [DefaultDefinition]
 ,col.[column_id] AS [ColumnNum]
 ,t.[name] AS [TypeName]
 ,col.[max_length] AS [TypeMaxLength]
 ,col.[precision] AS [TypePrecision]
 ,col.[scale] AS [TypeScale]
 ,col.[is_nullable] AS [IsNull]
 ,col.[is_rowguidcol] AS [IsRowGUIDCol]
 ,col.[is_identity] AS [IsIdentiity]
FROM sys.tables AS tbl
INNER JOIN sys.columns AS col
  ON tbl.[object_id] = col.[object_id]
INNER JOIN sys.types AS t
  ON col.[user_type_id] = t.[user_type_id]
LEFT OUTER JOIN sys.default_constraints AS dc
  ON dc.[object_id] = col.[default_object_id];

There are no default values in the JobEmplDB database, so run this script against the SRV database. The following result appears:

Information about the tables and their fields
Img.9. Key information about the tables and their fields (case 1)

As a result, the following columns are output:

  • SchemaName is the table schema name.
  • Name is the table name.
  • ColumnName is the table column name.
  • DefaultConstraintName is the default constraint name.
  • DefaultDefinition is the default definition of the constraint. 
  • ColumnNum is the column position number in the table definition. 
  • TypeName is the name of the table column type.
  • TypeMaxLength is the maximum length of the table column type measured in bytes (-1 means that it is not limited by this parameter).
  • TypePrecision is the maximum precision of values of this data type if it is numeric, otherwise, it is 0.
  • TypeScale is the maximum precision of values of this data type if it is numeric, otherwise, it is 0.
  • IsNull shows whether the field can contain the NULL value or not. 
  • IsRowGUIDCol defines whether the field is the RowGUID type.
  • IsIdentity defines whether the field is autoincremented.

The following system views are applied:

  • sys.tables is the information about the tables.
  • sys.columns is the information about the table fields. 
  • sys.default_constraints is the information about the default values.
  • sys.types is the information about the data types.

These system views have a lot of useful information that you should take into account. But for the sake of simplicity, you will see here only general information about tables, table fields, default field values, and column types.

To get the same information for the views, change sys.tables to sys.views in the query:

SELECT
  SCHEMA_NAME(tbl.[schema_id]) AS [SchemaName]
 ,tbl.[name] AS [Name]
 ,col.[name] AS [ColumnName]
 ,dc.[name] AS [DefaultConstraintName]
 ,dc.[definition] AS [DefaultDefinition]
 ,col.[column_id] AS [ColumnNum]
 ,t.[name] AS [TypeName]
 ,col.[max_length] AS [TypeMaxLength]
 ,col.[precision] AS [TypePrecision]
 ,col.[scale] AS [TypeScale]
 ,col.[is_nullable] AS [IsNull]
 ,col.[is_rowguidcol] AS [IsRowGUIDCol]
 ,col.[is_identity] AS [IsIdentiity]
FROM sys.views AS tbl
INNER JOIN sys.columns AS col
  ON tbl.[object_id] = col.[object_id]
INNER JOIN sys.types AS t
  ON col.[user_type_id] = t.[user_type_id]
LEFT OUTER JOIN sys.default_constraints AS dc
  ON dc.[object_id] = col.[default_object_id];

The approximate query result is as follows:

Information about views and their fields obtained from database reverse engineering
Img.10. General information about views and their fields (case 1)

You can get similar information using the following script, which outputs the information about both tables and views at the same time:

SELECT
  [TABLE_SCHEMA]
 ,[TABLE_NAME]
 ,[COLUMN_NAME]
 ,[ORDINAL_POSITION]
 ,[COLUMN_DEFAULT]
 ,[IS_NULLABLE]
 ,[DATA_TYPE]
 ,[CHARACTER_MAXIMUM_LENGTH]
 ,[CHARACTER_OCTET_LENGTH]
 ,[NUMERIC_PRECISION]
 ,[NUMERIC_PRECISION_RADIX]
 ,[NUMERIC_SCALE]
 ,[DATETIME_PRECISION]
FROM INFORMATION_SCHEMA.COLUMNS;

The approximate output is as follows:

Information about views and their fields obtained from database reverse engineering
Img.11. Basic information about views and their fields (case 2)

You can see the following columns in the result:

  • TABLE_SCHEMA is the schema name of the table/view.
  • TABLE_NAME is the name of the table/view.
  • COLUMN_NAME is the column name of the table/view.
  • ORDINAL_POSITION is the ordinal position of the field in the table/view definition.
  • COLUMN_DEFAULT is the default value for the column.
  • IS_NULLABLE indicates whether the column can contain NULL values.
  • DATA_TYPE is the data type of the field.
  • CHARACTER_MAXIMUM_LENGTH is the maximum character length for binary data, character, or text data and images; -1 is for XML data type and big values. The column returns NULL otherwise.
  • CHARACTER_OCTET_LENGTH is the maximum length measured in bytes for binary data, character, or text data and images; -1 is for XML data type and big values. The column returns NULL otherwise.
  • NUMERIC_PRECISION is the precision of approximate and exact numeric data, integer data, or money data. The column returns NULL otherwise.
  • NUMERIC_PRECISION_RADIX is the precision radix of the approximate and exact numeric data, integer data, or money data; otherwise, NULL is returned.
  • NUMERIC_SCALE is the scale of the approximate and exact numeric data, integer data, or money data. The column returns NULL otherwise.
  • DATETIME_PRECISION is the subtype code for the interval data types like DateTime and ISO. For other types of data, NULL is returned.

In this case, we use the COLUMNS system view from the system information schema views INFORMATION_SCHEMA.

Obtaining Information about Stored Procedures, Functions, and Their Parameters

To get the information about stored procedures and functions, use the following script:

SELECT
  s.[name] AS SchemaName
 ,obj.[name] AS 'ViewName'
 ,obj.[type]
 ,obj.Create_date
 ,sm.[definition] AS 'Definition script'
FROM sys.objects as obj
INNER JOIN sys.schemas as s
  ON obj.schema_id = s.schema_id
INNER JOIN sys.sql_modules as sm
  ON obj.object_id = sm.object_id
WHERE obj.[type] IN ('P', 'PC', 'FN', 'AF', 'FS', 'FT', 'IF', 'TF');

The approximate script result is as follows:

Information about stored procedures and functions obtained from database reverse engineering
Img.12.Basic information on stored procedures and functions (case 1)

You can see the following columns in the output:

  • SchemaName is the schema name of the stored procedure/function.
  • ViewName is the name of the stored procedure/function.
  • type is the type of stored procedure/function (P is a stored procedure in SQL, PC is an assembly stored procedure (CLR environment), FN is a scalar function in SQL, FS is an assembly scalar function (CLR environment), FT is an assembly function (CLR environment) with a table value, IF is a built-in SQL function with a table value, AF is an aggregate function (CLR environment), TF is a SQL function that returns a table value.
  • Create_date is the date and time of the stored procedure/function creation.
  • Stored Procedure script is the definition of the stored procedure/function.

The following system views are used:

You can get similar information with the following query:

SELECT
  [SPECIFIC_SCHEMA]
 ,[ROUTINE_NAME]
 ,[ROUTINE_DEFINITION]
 ,[ROUTINE_TYPE]
 ,[CREATED]
 ,[LAST_ALTERED]
FROM INFORMATION_SCHEMA.ROUTINES;

The approximate result of the query is as follows:

Information about stored procedures and functions obtained from database reverse engineering
Img.13. Basic information on stored procedures and functions (case 2)

As a result, the output shows the following fields:

  • SPECIFIC_SCHEMA is the schema name of the stored procedure/function.
  • ROUTINE_NAME is the name of the stored procedure/function.
  • ROUTINE_DEFINITION is the definition of the stored procedure/function.
  • ROUTINE_TYPE is the object type (PROCEDURE is a stored procedure, FUNCTION is a function).
  • CREATED is the date and time of the stored procedure/function creation.
  • LAST_ALTERED is the date and time of the last modification of the stored procedure/function.

In this case, we use the ROUTINES system view from the system information schema views INFORMATION_SCHEMA.

To obtain the information on parameters of stored procedures and functions, run the following script:

SELECT
  s.[name] AS SchemaName
 ,obj.[name] AS 'ViewName'
 ,obj.[type]
 ,p.[name] AS 'ParameterName'
 ,p.[parameter_id] AS [ParameterNum]
 ,p.[is_output] AS [IsOutput]
 ,p.[default_value] AS [DefaultValue]
 ,t.[name] AS [TypeName]
 ,p.[max_length] AS [TypeMaxLength]
 ,p.[precision] AS [TypePrecision]
 ,p.[scale] AS [TypeScale]
FROM sys.objects AS obj
INNER JOIN sys.schemas AS s
  ON obj.schema_id = s.schema_id
INNER JOIN sys.parameters AS p
  ON obj.object_id = p.object_id
INNER JOIN sys.types AS t
  ON t.[user_type_id] = p.[user_type_id]
WHERE obj.[type] IN ('P', 'PC', 'FN', 'AF', 'FS', 'FT', 'IF', 'TF');

The approximate script result is as follows:

Information about stored procedures and functions obtained from database reverse engineering
Img.14.Basic information on stored procedures and functions (case 2)

You can see the following fields in the output:

  • SchemaName is the schema name of the stored procedure/function.
  • ViewName is the name of the stored procedure/function. 
  • type is the type of the stored procedure/function (P is a stored procedure in SQL, PC is an assembly stored procedure (CLR environment), FN is a scalar function in SQL, FS is an assembly scalar function (CLR environment), FT is an assembly function (CLR environment) with a table value, IF is a built-in SQL function with a table value, AF is an aggregate function (CLR environment), TF is a SQL function that returns a table value.
  • ParameterName is the parameter name.
  • ParameterNum is the parameter position number in the stored procedure definition.
  • IsOutput shows whether it is an output parameter.
  • DefaultValue is the default value for the parameter.
  • TypeName is the parameter type.
  • TypeMaxLength is the parameter maximum length measured in bytes where -1 means that the parameter cannot be applied.
  • TypePrecision is the precision for a numeric parameter; otherwise, it is 0.
  • TypeScale is the numeric parameter scale; otherwise, it is 0.

 The following system views are used:

You can obtain similar information by running the following query:

SELECT
  r.[SPECIFIC_SCHEMA]
 ,r.[ROUTINE_NAME]
 ,r.[ROUTINE_TYPE]
 ,p.[PARAMETER_NAME]
 ,p.[ORDINAL_POSITION]
 ,p.[PARAMETER_MODE]
 ,p.[DATA_TYPE]
 ,p.[CHARACTER_MAXIMUM_LENGTH]
 ,p.[CHARACTER_OCTET_LENGTH]
 ,p.[NUMERIC_PRECISION]
 ,p.[NUMERIC_PRECISION_RADIX]
 ,p.[NUMERIC_SCALE]
 ,p.[DATETIME_PRECISION]
FROM INFORMATION_SCHEMA.PARAMETERS AS p
INNER JOIN INFORMATION_SCHEMA.ROUTINES AS r
  ON p.[SPECIFIC_SCHEMA] = r.[SPECIFIC_SCHEMA]
    AND p.[SPECIFIC_NAME] = r.[ROUTINE_NAME];

The approximate query result is as follows:

Img.15. Basic information about the parameters of stored procedures and functions (case 2)

You can find the following fields in the output:

  • SPECIFIC_SCHEMA is the schema name of the stored procedure/function.
  • ROUTINE_NAME is the name of the stored procedure/function.
  • ROUTINE_TYPE is the object type (PROCEDURE is the stored procedure, FUNCTION is the function). 
  • PARAMETER_NAME is the parameter of the stored procedure/function.
  • ORDINAL_POSITION is the parameter ordinal position in the definition of the stored procedure/function.
  • PARAMETER_MODE is the input (INOUT) or output (IN) parameter.
  • DATA_TYPE is the data type.
  • CHARACTER_MAXIMUM_LENGTH is the maximum character length for binary data, character or text data, and images, -1 is for XML data type and big values. The column returns NULL otherwise.
  • CHARACTER_OCTET_LENGTH is the maximum length measured in bytes for binary data, character or text data, and images, -1 is for XML data type and big values. The column returns NULL otherwise.
  • NUMERIC_PRECISION is the precision of approximate and exact numeric data, integer data, or money data. The column returns NULL otherwise.
  • NUMERIC_PRECISION_RADIX is the precision radix of approximate and exact numeric data, integer data, or money data, otherwise, NULL is returned.
  • NUMERIC_SCALE is the scale of approximate and exact numeric data, integer data, or money data. The column returns NULL otherwise.
  • DATETIME_PRECISION is the subtype code for the interval data types like DateTime and ISO. For other types of data, NULL is returned.

We use the PARAMETERS and ROUTINES system views from the system information schema views INFORMATION_SCHEMA.

Obtaining Information about Other Objects and Data

To avoid excessive wordiness in this article, we won’t provide the scripts to obtain the whole information about all database object types. Yet, it should be noted that the above-mentioned scripts allow obtaining key information about all basic types of database objects.

However, it is often the case that one needs to get information from the extended properties of database objects and also about other objects types like synonyms, sequences, statistics, and others).

For instance, you can obtain the information on database triggers using the following script:

SELECT
  t.name AS TriggerName
 ,t.parent_class_desc
 ,t.type AS TrigerType
 ,t.create_date AS TriggerCreateDate
 ,t.modify_date AS TriggerModifyDate
 ,t.is_disabled AS TriggerIsDisabled
 ,t.is_instead_of_trigger AS TriggerInsteadOfTrigger
 ,t.is_ms_shipped AS TriggerIsMSShipped
 ,t.is_not_for_replication
 ,s.name AS SchenaName
 ,ob.name AS ObjectName
 ,ob.type_desc AS ObjectTypeDesc
 ,ob.type AS ObjectType
 ,sm.[DEFINITION] AS 'Trigger script'
FROM sys.triggers AS t --sys.server_triggers
LEFT OUTER JOIN sys.objects AS ob
  ON t.parent_id = ob.object_id
LEFT OUTER JOIN sys.schemas AS s
  ON ob.schema_id = s.schema_id
LEFT OUTER JOIN sys.sql_modules sm
  ON t.object_id = sm.OBJECT_ID;

The following system views are used in the query:

To learn more about how to obtain information from the extended properties, refer to the article Documenting MS SQL Server Databases.

Conclusion

To sum up, the main focus of this article has been on database reverse engineering. Throughout the article, we have done our best to provide the readers with the key information considering this topic in the context of MS SQL Server. We started by defining reverse engineering and explaining its purpose, proceeded with its main elements, and finally, provided numerous examples of how to obtain various types of information for reverse engineering, including primary and foreign keys, uniqueness constraints, heaps, tables, views, and many other objects.

Look upon the functionality of the Database Diagram tool in dbForge Studio for SQL Server, which you can use for database design.

Evgeniy Gribkov

Latest posts by Evgeniy Gribkov (see all)

Leave a Comment