{"id":1881,"date":"2014-10-09T00:00:00","date_gmt":"2014-10-09T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/exploring-your-sql-server-databases-with-t-sql\/"},"modified":"2021-08-24T13:39:41","modified_gmt":"2021-08-24T13:39:41","slug":"exploring-your-sql-server-databases-with-t-sql","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/exploring-your-sql-server-databases-with-t-sql\/","title":{"rendered":"Exploring Your SQL Server Databases with T-SQL"},"content":{"rendered":"<div id=\"pretty\">\n<div class=\"float-right\">\n<ol>\n<li><a href=\"#second\">Exploring your Servers<\/a>\n<ol>\n<li><a href=\"#third\">Basic Server Information<\/a><\/li>\n<li><a href=\"#fourth\">Linked Servers<\/a><\/li>\n<li><a href=\"#fifth\">List All Databases<\/a><\/li>\n<li><a href=\"#sixth\">Last Databases Backup?<\/a><\/li>\n<li><a href=\"#seventh\">Active User Connections by Database<\/a><\/li>\n<\/ol>\n<\/li>\n<li><a href=\"#eighth\">Basic Database Exploration<\/a>\n<ol>\n<li><a href=\"#ninth\">Database File Location<\/a><\/li>\n<li><a href=\"#tenth\">Tables<\/a>\n<ol>\n<li><a href=\"#eleventh\">Row Counts for all Tables<\/a><\/li>\n<li><a href=\"#twelveth\">sp_msforeachtable<\/a><\/li>\n<li><a href=\"#thirteenth\">A faster way- use the clustered index<\/a><\/li>\n<li><a href=\"#fourteenth\">Finding Heaps <\/a><\/li>\n<li><a href=\"#fifteenth\">Investigating Table Activity<\/a><\/li>\n<\/ol>\n<\/li>\n<li><a href=\"#sixteenth\">Views<\/a><\/li>\n<li><a href=\"#seventeenth\">Synonyms<\/a><\/li>\n<li><a href=\"#eighteenth\">Stored Procedures<\/a><\/li>\n<li><a href=\"#nineteenth\">Functions<\/a><\/li>\n<li><a href=\"#twentieth\">Triggers<\/a><\/li>\n<li><a href=\"#twentyfirst\">CHECK Constraints<\/a><\/li>\n<\/ol>\n<\/li>\n<li><a href=\"#tewntysecond\">Exploring your Data Model in depth<\/a>\n<ol>\n<li><a href=\"#twentythird\">Columns<\/a>\n<ol>\n<li><a href=\"#twentyfourth\">Column Defaults<\/a><\/li>\n<li><a href=\"#twentyfifth\">Computed columns<\/a><\/li>\n<li><a href=\"#twentysixth\">Identity Columns<\/a><\/li>\n<\/ol>\n<\/li>\n<li><a href=\"#twentyseventh\">Keys and Indexes<\/a>\n<ol>\n<li><a href=\"#twentyeighth\">What indexes exist? \t\t\t<\/a><\/li>\n<li><a href=\"#twentyninth\">Which indexes are missing? \t\t\t<\/a><\/li>\n<li><a href=\"#thirtieth\">Foreign Keys<\/a><\/li>\n<li><a href=\"#thirtieth\"> \t\t\t<\/a><a href=\"#thirtyfirst\">Missing Indexes that support Foreign Keys. \t\t<\/a><\/li>\n<\/ol>\n<p><a href=\"#thirtyfirst\"> \t\t  <\/a><\/li>\n<\/ol>\n<p><a href=\"#thirtyfirst\"> \t\t<\/a><\/li>\n<li><a href=\"#thirtyfirst\"><\/a><a href=\"#thirtysecond\">Object Dependencies<\/a>\n<ol>\n<li><a href=\"#thirtythird\">sp_msdependencies<\/a><\/li>\n<li><a href=\"#thirtyfourth\">Query the system catalog views<\/a><\/li>\n<li><a href=\"#thirtyfifth\">Using a Common Table Expression (CTE)<\/a><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<\/div>\n<p class=\"start\">As a DBA and performance tuning consultant, for Ambient Consulting in Minneapolis,  I&#8217;m often asked to analyze performance issues on a SQL Server instance with which I&#8217;m unfamiliar. It can be a daunting  task. Let&#8217;s face it, most of the time companies do not have good documentation about their databases. Or if they do, it  is outdated, or it takes a few days to locate and retrieve it.<\/p>\n<p>In this article, I share a set of basic scripts that I&#8217;ve developed, over the  years, which mine the metadata in the various system functions, stored procedures, tables, catalog views, and dynamic  management views, Together, they reveal all the secrets of the databases on the instance, their size, file locations,  and their design, including columns, data types, defaults, keys and indexes.<\/p>\n<p>If you&#8217;ve been used to retrieving some of this information by clinging to the  mouse with whited knuckles and stabbing at the screen, then I hope you&#8217;ll be pleasantly surprised at the wealth of  information some of these simple scripts reveal, instantly. <\/p>\n<p>As with any scripts, always test them before running them in a production  environment. I recommend that you start first with one of the SQL Server sample databases like  <code> AdventureWorks<\/code> or  <code>  pubs<\/code> that you can download from: <span class=\"STHyperlink\"> <a href=\"http:\/\/msftdbprodsamples.codeplex.com\/releases\/view\/105902\">AdventureWorksDW Databases &#8211; 2012, 2008R2 and 2008<\/a><\/span>.<\/p>\n<p>OK, enough preamble, just show me the scripts!<\/p>\n<h2 id=\"second\">Exploring your Servers<\/h2>\n<p>We&#8217;ll start with some queries that offer the server-level view of your  databases.<\/p>\n<h3 id=\"third\">Basic Server Information<\/h3>\n<p>First, some simple <code> @@functions<\/code> that provide basic  server information.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Server and instance name \n\tSelect @@SERVERNAME as [Server\\Instance]; \n\t-- SQL Server Version \n\tSelect @@VERSION as SQLServerVersion; \n\t-- SQL Server Instance \n\tSelect @@ServiceName AS ServiceInstance;\n\t&#160;\n\t-- Current Database \n\tSelect DB_NAME() AS CurrentDB_Name; \n<\/pre>\n<p class=\"caption\">Listing 1: Basic server information<\/p>\n<p>How long has your server been running since the last SQL Server startup? Note  the <code> tempdb<\/code> system database is recreated every time the server  restarts. Thus this is one method to tell when the database server was last restarted.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Note the tempdb system database is recreated every time the server restarts\n\t-- Thus this is one method to tell when the database server was last restarted\n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date AS ServerStarted ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DATEDIFF(s, create_date, GETDATE()) \/ 86400.0 AS DaysRunning ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DATEDIFF(s, create_date, GETDATE()) AS SecondsRunnig\n\tFROM&#160;&#160;&#160; sys.databases\n\tWHERE&#160;&#160; name = 'tempdb'; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 2: how long has your server been running since startup?<\/p>\n<h3 id=\"fourth\">Linked Servers<\/h3>\n<p>Linked Servers are database connections set up to allow communication from SQL  Server to other data servers. Distributed queries can be ran against these linked servers. It&#8217;s good to know if your  database server is an isolated self-contained database server or if there are links to other database servers.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_helpserver; \n\t--OR \n\tEXEC sp_linkedservers; \n\t--OR \n\tSELECT&#160; @@SERVERNAME AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Server_Id AS LinkedServerID ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; name AS LinkedServer ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Product ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Provider ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Source ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Modify_Date\n\tFROM&#160;&#160;&#160; sys.servers\n\tORDER BY name; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 3: Linked Servers<\/p>\n<h3 id=\"fifth\">List All Databases<\/h3>\n<p>First step is to take inventory of all the databases found on the server. Note  the four or five system databases (<code>master<\/code>,  <code> model<\/code>, <code> msdb<\/code>, <code> tempdb<\/code>,  and <code>  distribution<\/code> if you are using replication). You may want to exclude these system  databases in future queries. It is very easy to see a list of database directly from SQL Server Management Studio SQL  Server (SSMS). However, these simple database queries are building blocks for more complicated queries.  <\/p>\n<p>There are several ways to get a list of the databases in T-SQL, and Listing 4  presents just a few of them. Each method produces a similar result set, but with subtle differences.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_helpdb; \n\t--OR \n\tEXEC sp_Databases; \n\t--OR \n\tSELECT&#160; @@SERVERNAME AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; name AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; recovery_model_Desc AS RecoveryModel ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Compatibility_level AS CompatiblityLevel ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; state_desc\n\tFROM&#160;&#160;&#160; sys.databases\n\tORDER BY Name; \n\t--OR \n\tSELECT&#160; @@SERVERNAME AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; d.name AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; compatibility_level ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; m.physical_name AS FileName\n\tFROM&#160;&#160;&#160; sys.databases d\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN sys.master_files m ON d.database_id = m.database_id\n\tWHERE&#160;&#160; m.[type] = 0 -- data files only\n\tORDER BY d.name; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 4: Database Inventory<\/p>\n<h3 id=\"sixth\">Last Databases Backup?<\/h3>\n<p>Stop! Before you go any further, every good DBA should be certain they have  recent database backup(s). <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; d.Name AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; MAX(b.backup_finish_date) AS LastBackupCompleted\n\tFROM&#160;&#160;&#160; sys.databases d\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; LEFT OUTER JOIN msdb..backupset b\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON b.database_name = d.name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND b.[type] = 'D'\n\tGROUP BY d.Name\n\tORDER BY d.Name; \n<\/pre>\n<p class=\"caption\">Listing 5: Last Database Backup<\/p>\n<p>Better still if you know the physical file location of the last backups.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; d.Name AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; b.Backup_finish_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; bmf.Physical_Device_name\n\tFROM&#160;&#160;&#160; sys.databases d\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN msdb..backupset b ON b.database_name = d.name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND b.[type] = 'D'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN msdb.dbo.backupmediafamily bmf ON b.media_set_id = bmf.media_set_id\n\tORDER BY d.NAME ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; b.Backup_finish_date DESC; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 6: Physical file location for recent backups<\/p>\n<h3 id=\"seventh\">Active User Connections by Database<\/h3>\n<p>It&#8217;s a good idea to have an understanding of what databases are being used at  any point in time, especially if you are experiencing performance problems. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Similar information can be derived from sp_who \n\tSELECT&#160; @@Servername AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME(database_id) AS DatabaseName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COUNT(database_id) AS Connections ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Login_name AS LoginName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; MIN(Login_Time) AS Login_Time ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; MIN(COALESCE(last_request_end_time, last_request_start_time))\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;AS Last_Batch\n\tFROM&#160;&#160;&#160; sys.dm_exec_sessions\n\tWHERE&#160;&#160; database_id &gt; 0\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND DB_NAME(database_id) NOT IN ( 'master', 'msdb' )\n\tGROUP BY database_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; login_name\n\tORDER BY DatabaseName;\n<\/pre>\n<p class=\"caption\">Listing 7: Active connections by database<\/p>\n<h2 id=\"eighth\">Basic Database Exploration<\/h2>\n<p>Let&#8217;s now drill down and see how we can gather information about the objects in  each of our databases, using various catalog views and Dynamic management Views. Most of the queries presented in this  section let&#8217;s look at the individual database so remember to change the current database in SSMS or by a  <code> Use database;<\/code> command. Remember to you can always check  the current default database with <code> Select DB_NAME();<\/code>.<\/p>\n<p>The <code> sys.objects<\/code> system table is one of the key system tables  for gathering a lot of information on the objects that comprise your data model, with <code> sys.objects.type<\/code> being the key column on which to filter.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- In this example U is for tables. \n\t-- Try swapping in one of the many other types. \n\tUSE MyDatabase;\n\tGO\n\tSELECT&#160; *\n\tFROM&#160;&#160;&#160; sys.objects\n\tWHERE&#160;&#160; type = 'U'; \n<\/pre>\n<p class=\"caption\">Listing 8: Listing out all user-defined tables in a database<\/p>\n<p>The following table shows the list of objects types on which we can filter (see  also the <a href=\"http:\/\/msdn.microsoft.com\/en-GB\/library\/ms190324.aspx\"> sys.objects<\/a> documentation on Microsoft&#8217;s MSDN  website).<\/p>\n<table>\n<tbody>\n<tr>\n<td colspan=\"3\">\n<p>sys.objects.type<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>AF = Aggregate function (CLR)<\/p>\n<\/td>\n<td>\n<p>P = SQL Stored Procedure<\/p>\n<\/td>\n<td>\n<p>TA = Assembly (CLR) DML trigger<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>C = CHECK constraint<\/p>\n<\/td>\n<td>\n<p>PC = Assembly (CLR) stored-procedure<\/p>\n<\/td>\n<td>\n<p>TF = SQL table-valued-function<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>D = DEFAULT (constraint or stand-alone)<\/p>\n<\/td>\n<td>\n<p>PG = Plan guide<\/p>\n<\/td>\n<td>\n<p>TR = SQL DML trigger<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>F = FOREIGN KEY constraint<\/p>\n<\/td>\n<td>\n<p>PK = PRIMARY KEY constraint<\/p>\n<\/td>\n<td>\n<p>TT = Table type<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>FN = SQL scalar function<\/p>\n<\/td>\n<td>\n<p>R = Rule (old-style, stand-alone)<\/p>\n<\/td>\n<td>\n<p>U = Table (user-defined)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>FS = Assembly (CLR) scalar-function<\/p>\n<\/td>\n<td>\n<p>RF = Replication-filter-procedure<\/p>\n<\/td>\n<td>\n<p>UQ = UNIQUE constraint<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>FT = Assembly (CLR) table-valued function <\/p>\n<\/td>\n<td>\n<p>S = System base table<\/p>\n<\/td>\n<td>\n<p>V = View<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>IF = SQL inline table-valued function<\/p>\n<\/td>\n<td>\n<p>SN = Synonym<\/p>\n<\/td>\n<td>\n<p>X = Extended stored procedure<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>IT = Internal table<\/p>\n<\/td>\n<td>\n<p>SQ = Service queue<\/p>\n<\/td>\n<td> \t\t \t\t<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Other catalog views, such as <code> sys.tables<\/code> and  <code>  sys.views<\/code>, inherit from <code>  sys.objects<\/code> and provide the information for that particular object type. With these  views, plus the <code>  OBJECTPROPERTY<\/code> metadata function, we can uncover a great deal of information on each of  the objects that make up our database schemas.<\/p>\n<h3 id=\"nineth\">Database File Location<\/h3>\n<p>Physical location and drive of the current database file, including the master  database file (<code>*.mdf<\/code>) and the Log database  file (<code>*.ldf<\/code>) can be found using these queries.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_Helpfile; \n\t--OR \n\tSELECT&#160; @@Servername AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; File_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Type_desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; LEFT(Physical_Name, 1) AS Drive ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Physical_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; RIGHT(physical_name, 3) AS Ext ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Size ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Growth\n\tFROM&#160;&#160;&#160; sys.database_files\n\tORDER BY File_id; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 9: Physical file location of the current database<\/p>\n<h3 id=\"tenth\">Tables<\/h3>\n<p>Of course, the Object Explorer in SSMS provides a convenient list of the tables  in a specific database, but using scripts we can unveil information that isn&#8217;t easily available via the GUI. The ANSI  Standard approach is to use the <code> INFORMATION_SCHEMA<\/code> views, but they won&#8217;t return  information regarding objects that are not part of the Standard (such as triggers, extended properties, and so on), so  use of the SQL Server Catalog views is common.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_tables; -- Note this method returns both table and views. \n\t--OR \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160; &#160;&#160;&#160;TABLE_CATALOG ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_SCHEMA ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_NAME\n\tFROM&#160;&#160;&#160; INFORMATION_SCHEMA.TABLES\n\tWHERE&#160;&#160; TABLE_TYPE = 'BASE TABLE'\n\tORDER BY TABLE_NAME ;\n\t--OR\n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'TableName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.Type = 'U' -- User table \n\tORDER BY o.name;\n\t--OR \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Name AS TableName,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.[Type],\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.create_date\n\tFROM&#160;&#160;&#160; sys.tables t\n\tORDER BY t.Name;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 10: Exploring table details<\/p>\n<h4 id=\"eleventh\">Row Counts for all Tables<\/h4>\n<p>If you know nothing about a table, all tables are equal. The more you know about  the tables the more you can mentally begin to determine which tables are more important and which tables are less  important. Generally speaking, tables with the largest number of rows tend to be the ones that suffer most often from  performance issues.<\/p>\n<p>From SSMS object explorer, we can right-click on any table name and select <span class=\"STBold\">Properties<\/span>, and view the <span class=\"STBold\">Storage<\/span> page will provide a row count  for that table. <\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img135.jpg\" alt=\"2066-img135.jpg\" \/><\/p>\n<p>However, it&#8217;s hard to collect this information for all tables, manually.  Likewise, the brute force approach of executing <code> SELECT COUNT(*) FROM TABLENAME;<\/code>  for every table is likely to involve a lot of typing.<\/p>\n<p>A much better approach is to use T-SQL as a script generator. The script in  Listing 11 will generate a set of T-SQL statements to return the row count for each table in the current database.  Simply execute it, then copy-and-paste the generated statements into the query window and execute them.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; 'Select ''' + DB_NAME() + '.' + SCHEMA_NAME(SCHEMA_ID) + '.'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + LEFT(o.name, 128) + ''' as DBName, count(*) as Count From ' + o.name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ';' AS ' Script generator to get counts for all tables'\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.[type] = 'U'\n\tORDER BY o.name;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 11: A script to generate a script to return row counts for all tables<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img138.jpg\" alt=\"2066-img138.jpg\" \/><\/p>\n<h4 id=\"twelveth\">sp_msforeachtable<\/h4>\n<p><code> sp_msforeachtable<\/code>  is an undocumented Microsoft function that will loop through all the tables in a database executing a query, and  replacing &#8216;?&#8217; with each table name. There is also a similar database level function called  <code> sp_msforeachdb<\/code>.  <\/p>\n<p>There are some known issues with these undocumented functions, as they do not  handle special characters in the object names. For example, table or database names containing a dash character (&#8220;-&#8220;)  will cause the stored procedure to fail.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tCREATE TABLE #rowcount\n\t&#160;&#160;&#160;  ( Tablename VARCHAR(128) ,\n\t&#160;&#160;&#160;&#160;&#160; Rowcnt INT ); \n\tEXEC sp_MSforeachtable 'insert into #rowcount select ''?'', count(*) from ?' \n\tSELECT&#160; *\n\tFROM&#160;&#160;&#160; #rowcount\n\tORDER BY Tablename ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Rowcnt; \n\tDROP TABLE #rowcount;\n<\/pre>\n<p class=\"caption\">Listing 12: Using sp_msforeachtable to return row counts for all tables<\/p>\n<h4 id=\"thirteenth\">A faster way to get row counts &#8211; use the clustered index<\/h4>\n<p>All of the previous methods to return row counts for each table rely on use of <code> COUNT(*)<\/code>, which performs poorly for tables with more than  about 500K rows, in my experience.<\/p>\n<p>A faster way to get table row counts is to get the record counts from the  clustered index or heap partition. Note while this method is much faster, Microsoft has indicated the record count  updates on indexes may not always match the record counts of the table, due to a delay in the index counts getting  updated. In most cases they are exactly the same or very, very close and will be the same shortly.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- A faster way to get table row counts. \n\t-- Hint: get it from an index, not the table.\n\tSELECT&#160; @@ServerName AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(p.object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(p.object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Type_Desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Name AS IndexUsedForCounts ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(p.Rows) AS Rows\n\tFROM&#160;&#160;&#160; sys.partitions p\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN sys.indexes i ON i.object_id = p.object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND i.index_id = p.index_id\n\tWHERE&#160;&#160; i.type_desc IN ( 'CLUSTERED', 'HEAP' )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;-- This is key (1 index per table) \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND OBJECT_SCHEMA_NAME(p.object_id) &lt;&gt; 'sys'\n\tGROUP BY p.object_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.type_desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Name\n\tORDER BY SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName; \n\t&#160;\n\t-- OR \n\t-- Similar method to get row counts, but this uses DMV dm_db_partition_stats \n\tSELECT&#160; @@ServerName AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(ddps.object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(ddps.object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Type_Desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Name AS IndexUsedForCounts ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(ddps.row_count) AS Rows\n\tFROM&#160;&#160;&#160; sys.dm_db_partition_stats ddps\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN sys.indexes i ON i.object_id = ddps.object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND i.index_id = ddps.index_id\n\tWHERE&#160;&#160; i.type_desc IN ( 'CLUSTERED', 'HEAP' )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;-- This is key (1 index per table) \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND OBJECT_SCHEMA_NAME(ddps.object_id) &lt;&gt; 'sys'\n\tGROUP BY ddps.object_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.type_desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Name\n\tORDER BY SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 13: Return table row counts from the index or table partition<\/p>\n<h4 id=\"fourteenth\">Finding Heaps (tables with no clustered index)<\/h4>\n<p>Working with heap tables is like working with a flat file, instead of a  database. If you want to guarantee a full table scan for any and all queries, use a heap table. My general  recommendation would be to add a primary key clustered index to all heap tables.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Heap tables (Method 1) \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Name AS HeapTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Create_Date\n\tFROM&#160;&#160;&#160; sys.tables t\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.indexes i ON t.object_id = i.object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND i.type_desc = 'HEAP'\n\tORDER BY t.Name \n\t--OR \n\t-- Heap tables (Method 2) \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Name AS HeapTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Create_Date\n\tFROM&#160;&#160;&#160; sys.tables t\n\tWHERE&#160;&#160; OBJECTPROPERTY(OBJECT_ID, 'TableHasClustIndex') = 0\n\tORDER BY t.Name; \n\t--OR \n\t-- Heap tables (Method 3) also provides row counts \n\tSELECT&#160; @@ServerName AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(ddps.object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(ddps.object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Type_Desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(ddps.row_count) AS Rows\n\tFROM&#160;&#160;&#160; sys.dm_db_partition_stats AS ddps\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN sys.indexes i ON i.object_id = ddps.object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND i.index_id = ddps.index_id\n\tWHERE&#160;&#160; i.type_desc = 'HEAP'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND OBJECT_SCHEMA_NAME(ddps.object_id) &lt;&gt; 'sys'\n\tGROUP BY ddps.object_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.type_desc\n\tORDER BY TableName; \n<\/pre>\n<p class=\"caption\">Listing 14: Finding heaps<\/p>\n<h4 id=\"fifteenth\">Investigating Table Activity<\/h4>\n<p>Knowing which tables have the most reads and writes is another important piece  of information when performance tuning your database. Previously, we examined queries to return the row counts for each  table. The following examples show the number of table reads and writes.<\/p>\n<p>Note these statistics from Dynamic Management Views are cleared out each time  SQL Server restarts (wait and latch statistics can also be <a href=\"http:\/\/msdn.microsoft.com\/en-gb\/library\/ms189768.aspx\">cleared out manually<\/a>). The longer the server has  been up, the more reliable the statistics. I have a lot more confidence with statistics that are over 30 days (assumes  the tables have been through a month end cycle) and a lot less confidence if they are less than 7 days.  <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Table Reads and Writes \n\t-- Heap tables out of scope for this query. Heaps do not have indexes. \n\t-- Only lists tables referenced since the last server restart \n\tSELECT&#160; @@ServerName AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(ddius.object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(ddius.user_seeks + ddius.user_scans + ddius.user_lookups)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;AS Reads ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(ddius.user_updates) AS Writes ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SUM(ddius.user_seeks + ddius.user_scans + ddius.user_lookups\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ddius.user_updates) AS [Reads&amp;Writes] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( SELECT&#160;&#160;&#160; DATEDIFF(s, create_date, GETDATE()) \/ 86400.0\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160;&#160;&#160; master.sys.databases\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160;&#160;&#160; name = 'tempdb'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) AS SampleDays ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( SELECT&#160;&#160;&#160; DATEDIFF(s, create_date, GETDATE()) AS SecoundsRunnig\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160;&#160;&#160; master.sys.databases\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160;&#160;&#160; name = 'tempdb'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) AS SampleSeconds\n\tFROM&#160;&#160;&#160; sys.dm_db_index_usage_stats ddius\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.indexes i ON ddius.object_id = i.object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND i.index_id = ddius.index_id\n\tWHERE&#160;&#160; OBJECTPROPERTY(ddius.object_id, 'IsUserTable') = 1\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ddius.database_id = DB_ID()\n\tGROUP BY OBJECT_NAME(ddius.object_id)\n\tORDER BY [Reads&amp;Writes] DESC;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 15: Read and write activity for all tables referenced since the last  server restart, in a database<\/p>\n<p>A more advanced version of the same query uses a cursor to consolidate the  information for all Tables for all databases on the server. While I am not a fan of cursors due to their slow  performance, navigating multiple databases does seem to be a good use for one.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Table Reads and Writes \n\t-- Heap tables out of scope for this query. Heaps do not have indexes. \n\t-- Only lists tables referenced since the last server restart \n\t-- This query uses a cursor to identify all the user databases on the server \n\t-- Consolidates individual database results into a report, using a temp table. \n\tDECLARE DBNameCursor CURSOR\n\tFOR\n\t&#160;&#160; &#160;SELECT&#160; Name\n\t&#160;&#160;&#160; FROM&#160;&#160;&#160; sys.databases\n\t&#160;&#160;&#160; WHERE&#160;&#160; Name NOT IN ( 'master', 'model', 'msdb', 'tempdb',\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'distribution' )\n\t&#160;&#160;&#160; ORDER BY Name; \n\tDECLARE @DBName NVARCHAR(128) \n\tDECLARE @cmd VARCHAR(4000) \n\tIF OBJECT_ID(N'tempdb..TempResults') IS NOT NULL\n\t&#160;&#160;&#160; BEGIN \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DROP TABLE tempdb..TempResults \n\t&#160;&#160;&#160; END \n\tCREATE TABLE tempdb..TempResults\n\t&#160;&#160;&#160;  (\n\t&#160;&#160;&#160;&#160;&#160; ServerName NVARCHAR(128) ,\n\t&#160;&#160;&#160;&#160;&#160; DBName NVARCHAR(128) ,\n\t&#160;&#160;&#160;&#160;&#160; TableName NVARCHAR(128) ,\n\t&#160;&#160;&#160;&#160;&#160; Reads INT ,\n\t&#160;&#160;&#160;&#160;&#160; Writes INT ,\n\t&#160;&#160;&#160;&#160;&#160; ReadsWrites INT ,\n\t&#160;&#160;&#160;&#160;&#160; SampleDays DECIMAL(18, 8) ,\n\t&#160;&#160;&#160;&#160;&#160; SampleSeconds INT\n\t&#160;&#160;&#160; ) \n\tOPEN DBNameCursor \n\tFETCH NEXT FROM DBNameCursor INTO @DBName \n\tWHILE @@fetch_status = 0\n\t&#160;&#160;&#160; BEGIN \n\t---------------------------------------------------- \n\t-- Print @DBName \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @cmd = 'Use ' + @DBName + '; ' \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @cmd = @cmd + ' Insert Into tempdb..TempResults \n\tSELECT @@ServerName AS ServerName, \n\tDB_NAME() AS DBName, \n\tobject_name(ddius.object_id) AS TableName , \n\tSUM(ddius.user_seeks \n\t+ ddius.user_scans \n\t+ ddius.user_lookups) AS Reads, \n\tSUM(ddius.user_updates) as Writes, \n\tSUM(ddius.user_seeks \n\t+ ddius.user_scans \n\t+ ddius.user_lookups \n\t+ ddius.user_updates) as ReadsWrites, \n\t(SELECT datediff(s,create_date, GETDATE()) \/ 86400.0 \n\tFROM sys.databases WHERE name = ''tempdb'') AS SampleDays, \n\t(SELECT datediff(s,create_date, GETDATE()) \n\tFROM sys.databases WHERE name = ''tempdb'') as SampleSeconds \n\tFROM sys.dm_db_index_usage_stats ddius \n\tINNER JOIN sys.indexes i\n\tON ddius.object_id = i.object_id \n\tAND i.index_id = ddius.index_id \n\tWHERE objectproperty(ddius.object_id,''IsUserTable'') = 1 --True \n\tAND ddius.database_id = db_id() \n\tGROUP BY object_name(ddius.object_id) \n\tORDER BY ReadsWrites DESC;' \n\t--PRINT @cmd \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; EXECUTE (@cmd) \n\t----------------------------------------------------- \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; FETCH NEXT FROM DBNameCursor INTO @DBName \n\t&#160;&#160;&#160; END \n\tCLOSE DBNameCursor \n\tDEALLOCATE DBNameCursor \n\tSELECT&#160; *\n\tFROM&#160;&#160;&#160; tempdb..TempResults\n\tORDER BY DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName; \n\t--DROP TABLE tempdb..TempResults; \n<\/pre>\n<p class=\"caption\">Listing 16: Read and write activity for all tables referenced since the last  server restart, in all databases<\/p>\n<h3 id=\"sixteenth\">Views<\/h3>\n<p>Views are scripted queries that are stored in the database. You can think of  them as virtual tables. Data is not stored in the view but we reference the view in our queries in exactly the same way  we would reference a table.<\/p>\n<p>In SQL Server, we can even, in <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms180800.aspx\">some circumstances<\/a>, update data through a view. To  make a view read only, one trick is to use <code> SELECT DISTINCT<\/code> in the view  definition. A view is only updateable if each row in the view maps unambiguously to a single row in the underlying  table. Any view that fails this criteria, such as any view built on more than one table, or that uses grouping,  aggregations, and calculations in its definition, will be read only.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS ViewName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.[Type] = 'V' -- View \n\tORDER BY o.NAME \n\t&#160;\n\t--OR \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name AS ViewName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date\n\tFROM&#160;&#160;&#160; sys.Views\n\tORDER BY Name \n\t--OR\n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_CATALOG ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_SCHEMA ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_NAME ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TABLE_TYPE\n\tFROM&#160;&#160;&#160; INFORMATION_SCHEMA.TABLES\n\tWHERE&#160;&#160; TABLE_TYPE = 'VIEW'\n\tORDER BY TABLE_NAME \n\t--OR \n\t-- View details (Show the CREATE VIEW Code) \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'ViewName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; sm.[DEFINITION] AS 'View script'\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.sql_modules sm ON o.object_id = sm.OBJECT_ID\n\tWHERE&#160;&#160; o.Type = 'V' -- View \n\tORDER BY o.NAME;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 17: Exploring views<\/p>\n<h3 id=\"seventeenth\">Synonyms<\/h3>\n<p>A synonym is an &#8220;also known as (aka)&#8221; name for an object in the database. A few  times in my career I have been asked to review a query only to be scratching my head trying to figure out the table to  which the query is referring. For example, consider a simple query <code> Select * from Client<\/code>. I search  for the table named <code> Client<\/code> but I can&#8217;t find it. OK, it must be a view then,  search for view named <code> Client<\/code> and I still can&#8217;t find  it. I must have the wrong database? Turns out <code> Client<\/code> is a  synonym for a customer and the actual table is <code> Customer<\/code>. The marketing group  wanted to refer to this table as Client so created a synonym. Thankfully, use of synonyms is rare, but they can cause  confusion if you are not aware of them.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- which synonyms exist?\n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS ViewName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.[Type] = 'SN' -- Synonym \n\tORDER BY o.NAME;\n\t--OR \n\t-- synonymn details \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; s.name AS synonyms ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; s.create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; s.base_object_name\n\tFROM&#160;&#160;&#160; sys.synonyms s\n\tORDER BY s.name;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 18: Exploring synonyms<\/p>\n<h3 id=\"eighteenth\">Stored Procedures <\/h3>\n<p>A stored procedure is a group of script(s) that are compiled into a single  execution plan. We can use the catalog views to find out which stored procedures exist, what activity they perform, and  which tables they reference.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Stored Procedures \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS StoredProcedureName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.[Type] = 'P' -- Stored Procedures \n\tORDER BY o.name\n\t--OR \n\t-- Stored Procedure details \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'ViewName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; sm.[definition] AS 'Stored Procedure script'\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.sql_modules sm ON o.object_id = sm.object_id\n\tWHERE&#160;&#160; o.[type] = 'P' -- Stored Procedures \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- AND sm.[definition] LIKE '%insert%'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- AND sm.[definition] LIKE '%update%'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- AND sm.[definition] LIKE '%delete%'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- AND sm.[definition] LIKE '%tablename%'\n\tORDER BY o.name;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 19: Exploring stored procedures<\/p>\n<p>With a simply addition to the <code> WHERE<\/code> clause of the stored  procedure details query, we can investigate, for example, only those stored procedures that perform inserts.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t...\n\tWHERE&#160;&#160; o.[type] &#160;= 'P' -- Stored Procedures \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND sm.definition LIKE '%insert%'\n\tORDER BY o.name\n\t...\n<\/pre>\n<p>Simply modify the <code> WHERE<\/code> clause as required to  investigate stored procedures that do updates (<code>LIKE '%update%'<\/code>),  deletes (<code>LIKE '%delete%'<\/code>), or reference  a particular table (<code>LIKE '%tablename%'<\/code>).<\/p>\n<h3 id=\"nineeenth\">Functions<\/h3>\n<p>A function is stored SQL that accept parameters, performs an action or  calculation and returns a result.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Functions \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'Functions' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\tWHERE&#160;&#160; o.Type = 'FN' -- Function \n\tORDER BY o.NAME;\n\t--OR \n\t-- Function details \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'FunctionName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; sm.[DEFINITION] AS 'Function script'\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.sql_modules sm ON o.object_id = sm.OBJECT_ID\n\tWHERE&#160;&#160; o.[Type] = 'FN' -- Function \n\tORDER BY o.NAME;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 20: Exploring functions<\/p>\n<h3 id=\"twentieth\">Triggers<\/h3>\n<p>A trigger is like a stored procedure that executes in response to a particular  event that occurs on the table to which the trigger belongs. For example, we can create  <code> INSERT<\/code>,  <code> UPDATE<\/code>  and <code>  DELETE<\/code> triggers.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Table Triggers \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent.name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS TriggerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.objects parent ON o.parent_object_id = parent.object_id\n\tWHERE&#160;&#160; o.Type = 'TR' -- Triggers \n\tORDER BY parent.name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.NAME \n\t--OR \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Parent_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; name AS TriggerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date\n\tFROM&#160;&#160;&#160; sys.triggers\n\tWHERE&#160;&#160; parent_class = 1\n\tORDER BY name;\n\t--OR \n\t-- Trigger Details \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(Parent_object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'TriggerName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; sm.[DEFINITION] AS 'Trigger script'\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.sql_modules sm ON o.object_id = sm.OBJECT_ID\n\tWHERE&#160;&#160; o.Type = 'TR' -- Triggers \n\tORDER BY o.NAME;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 21: Exploring triggers<\/p>\n<h3 id=\"twentyfirst\">CHECK Constraints<\/h3>\n<p><code> CHECK<\/code>  constraints are a good way to implement business logic in a database. For example, certain fields must be positive or  negative or a date in one column must be later than a date in another column.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Check Constraints \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160; &#160;&#160;&#160;&#160;&#160;&#160;DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent.name AS 'TableName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'Constraints' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.objects parent\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON o.parent_object_id = parent.object_id\n\tWHERE&#160;&#160; o.Type = 'C' -- Check Constraints \n\tORDER BY parent.name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name \n\t--OR \n\t--CHECK constriant definitions\n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(parent_object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(parent_object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent_column_id AS Column_NBR ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name AS CheckConstraintName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; type_desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_DEFINITION(object_id) AS CheckConstraintDefinition\n\tFROM&#160;&#160;&#160; sys.Check_constraints\n\tORDER BY TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_NBR \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 22: Exploring CHECK constraints<\/p>\n<h2 id=\"twentysecond\">Exploring your Data Model in depth<\/h2>\n<p>Up to this point, we&#8217;ve examined scripts that gives us a &#8216;high level&#8217; view of  the objects that comprise our databases. Often though, we&#8217;ll want more in-depth knowledge of each table, including the  columns, their data types and any default values defined, which keys, constraints and indexes exist (or are missing) and  so on. <\/p>\n<p>The queries presented across the coming sections build on this foundation, and  provide a means almost to &#8220;reverse engineer&#8221; your existing data model.<\/p>\n<h3 id=\"twentythird\">Columns<\/h3>\n<p>The following script documents the tables and their column definitions, in a  specified database. The resulting output is a good one to cut and paste into Excel, where you can filter or sort on  column names to get a good understanding of the data types that exist in a given database. Watch out for and question  column names that are the same but have different data types or different lengths. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Table Columns \n\tSELECT&#160; @@Servername AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; isc.Table_Name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; isc.Table_Schema AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ordinal_Position AS Ord ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision AS Prec ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale AS Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length AS LEN , -- -1 means MAX like Varchar(MAX) \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Is_Nullable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_Default ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Table_Type\n\tFROM&#160;&#160;&#160; INFORMATION_SCHEMA.COLUMNS isc\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN information_schema.tables ist\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON isc.table_name = ist.table_name \n\t--&#160;&#160;&#160;&#160;&#160; WHERE Table_Type = 'BASE TABLE' -- 'Base Table' or 'View' \n\tORDER BY DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ordinal_position; \n\t&#160;\n\t-- Summary of Column names and usage counts \n\t-- Watch for column names with different data types or different lengths \n\tSELECT&#160; @@Servername AS Server ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision AS Prec ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale AS Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COUNT(*) AS Count\n\tFROM&#160;&#160;&#160; information_schema.columns isc\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN information_schema.tables ist\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON isc.table_name = ist.table_name\n\tWHERE&#160;&#160; Table_type = 'BASE TABLE'\n\tGROUP BY Column_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length;\n\t&#160;\n\t-- Summary of data types \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision AS Prec ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale AS Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length AS [Length] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COUNT(*) AS COUNT\n\tFROM&#160;&#160;&#160; information_schema.columns isc\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN information_schema.tables ist\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON isc.table_name = ist.table_name\n\tWHERE&#160;&#160; Table_type = 'BASE TABLE'\n\tGROUP BY Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length\n\tORDER BY Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length \n\t&#160;\n\t-- Large object data types or Binary Large Objects(BLOBs) \n\t-- Note if you are using Enterprise edition, these tables can't rebuild indexes \"Online\" \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; isc.Table_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ordinal_Position AS Ord ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Data_Type AS BLOB_Data_Type ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Precision AS Prec ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Numeric_Scale AS Scale ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Character_Maximum_Length AS [Length]\n\tFROM&#160;&#160;&#160; information_schema.columns isc\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN information_schema.tables ist\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON isc.table_name = ist.table_name\n\tWHERE&#160;&#160; Table_type = 'BASE TABLE'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ( Data_Type IN ( 'text', 'ntext', 'image', 'XML' )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OR ( Data_Type IN ( 'varchar', 'nvarchar', 'varbinary' )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND Character_Maximum_Length = -1\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) -- varchar(max), nvarchar(max), varbinary(max) \n\tORDER BY isc.Table_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ordinal_position;\n<\/pre>\n<p class=\"caption\">Listing 23: Exploring columns and their data types<\/p>\n<h4 id=\"twentyfourth\">Column Defaults <\/h4>\n<p>Column Defaults are values that are stored in the column, if no value is entered  for that column, when the record is first inserted. A common default for a column that stores dates is  <code> getdate() or current_timestamp<\/code>.  Another common default in auditing is <code> system_user<\/code>, to identify the  login that performed a certain action.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Table Defaults \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent.name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS Defaults ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.objects parent\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON o.parent_object_id = parent.object_id\n\tWHERE&#160;&#160; o.[Type] = 'D' -- Defaults \n\tORDER BY parent.name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.NAME\n\t&#160;\n\t--OR \n\t-- Column Defaults \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(parent_object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(parent_object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent_column_id AS Column_NBR ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name AS DefaultName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; [type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; type_desc ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; create_date ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_DEFINITION(object_id) AS Defaults\n\tFROM&#160;&#160;&#160; sys.default_constraints\n\tORDER BY TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_NBR \n\t--OR \n\t-- Column Defaults \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(t.object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Column_ID AS Ord ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Name AS Column_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(default_object_id) AS DefaultName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_DEFINITION(default_object_id) AS Defaults\n\tFROM&#160;&#160;&#160; sys.Tables t\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.columns c ON t.object_id = c.object_id\n\tWHERE&#160;&#160; default_object_id &lt;&gt; 0\n\tORDER BY TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Column_ID \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 24: Exploring column default values<\/p>\n<h4 id=\"twentyfifth\">Computed columns<\/h4>\n<p>Computed columns are columns where the values determined by an equation, usually  referencing other columns in the table.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Computed columns \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(object_id) AS Tablename ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name AS Computed_Column ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; [Definition] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; is_persisted\n\tFROM&#160;&#160;&#160; sys.computed_columns\n\tORDER BY SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Tablename ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; [Definition]; \n\t--Or \n\t-- Computed Columns \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(t.object_id) AS SchemaName,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Column_ID AS Ord ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Name AS Computed_Column\n\tFROM&#160;&#160;&#160; sys.Tables t\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.Columns c ON t.object_id = c.object_id\n\tWHERE&#160;&#160; is_computed = 1\n\tORDER BY t.Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; c.Column_ID \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 25: Exploring computed columns<\/p>\n<h4 id=\"twentysixth\">Identity Columns <\/h4>\n<p><code> IDENTITY<\/code>  columns are populated with unique system controlled numbers. A common example is an order number, where each time an  order is entered into the system, SQL Server assigns to the <code>  IDENTITY<\/code> column the next sequential number.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_SCHEMA_NAME(object_id) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_id ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Name AS IdentityColumn ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Seed_Value ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Last_Value\n\tFROM&#160;&#160;&#160; sys.identity_columns\n\tORDER BY SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Column_id; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 26: Exploring IDENTITY columns<\/p>\n<h3 id=\"twentyseventh\">Keys and Indexes<\/h3>\n<p>As discussed earlier, a general best practice all tables should have a primary  key clustered index. As a second general best practice, foreign keys should have a supporting index on the same columns  as the foreign key. Foreign key indexes provide the most likely way that tables be joined together in multi-table  queries. Foreign key indexes are also important for performance when deleting records. <\/p>\n<h4 id=\"twentyeighth\">What indexes exist?<\/h4>\n<p>To see which indexes exist on all tables in the current database.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Name AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.Name AS IndexName\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.indexes i ON o.object_id = i.object_id\n\tWHERE&#160;&#160; o.Type = 'U' -- User table \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND LEFT(i.Name, 1) &lt;&gt; '_' -- Remove hypothetical indexes \n\tORDER BY o.NAME ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; i.name; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 27: Exploring existing indexes<\/p>\n<h4 id=\"twentyninth\">Which indexes are missing?<\/h4>\n<p>The indexing related DMVs store statistics that SQL Server uses recommend  indexes that could offer performance benefits, based on previously executed queries.<\/p>\n<p>Do not add these indexes blindly. I would review and question each index  suggested. Included column my come with a high cost of maintaining duplicate data.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Missing Indexes DMV Suggestions \n\tSELECT&#160; @@ServerName AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; t.name AS 'Affected_table' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( LEN(ISNULL(ddmid.equality_columns, N'')\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + CASE WHEN ddmid.equality_columns IS NOT NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ddmid.inequality_columns IS NOT NULL THEN ','\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE ''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END) - LEN(REPLACE(ISNULL(ddmid.equality_columns, N'')\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + CASE WHEN ddmid.equality_columns\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IS NOT NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ddmid.inequality_columns\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IS NOT NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; THEN ','\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE ''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END, ',', '')) ) + 1 AS K ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COALESCE(ddmid.equality_columns, '')\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + CASE WHEN ddmid.equality_columns IS NOT NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ddmid.inequality_columns IS NOT NULL THEN ','\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE ''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END + COALESCE(ddmid.inequality_columns, '') AS Keys ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COALESCE(ddmid.included_columns, '') AS [include] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'Create NonClustered Index IX_' + t.name + '_missing_'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + CAST(ddmid.index_handle AS VARCHAR(20)) \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ' On ' + ddmid.[statement] COLLATE database_default\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ' (' + ISNULL(ddmid.equality_columns, '')\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + CASE WHEN ddmid.equality_columns IS NOT NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ddmid.inequality_columns IS NOT NULL THEN ','\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE ''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END + ISNULL(ddmid.inequality_columns, '') + ')'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ISNULL(' Include (' + ddmid.included_columns + ');', ';')\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AS sql_statement ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ddmigs.user_seeks ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ddmigs.user_scans ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; CAST(( ddmigs.user_seeks + ddmigs.user_scans )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; * ddmigs.avg_user_impact AS BIGINT) AS 'est_impact' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; avg_user_impact ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ddmigs.last_user_seek ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( SELECT&#160;&#160;&#160; DATEDIFF(Second, create_date, GETDATE()) Seconds\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160;&#160;&#160; sys.databases\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160;&#160;&#160; name = 'tempdb'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) SecondsUptime \n\t-- Select * \n\tFROM&#160;&#160;&#160; sys.dm_db_missing_index_groups ddmig\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.dm_db_missing_index_group_stats ddmigs\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON ddmigs.group_handle = ddmig.index_group_handle\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.dm_db_missing_index_details ddmid\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON ddmig.index_handle = ddmid.index_handle\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.tables t ON ddmid.OBJECT_ID = t.OBJECT_ID\n\tWHERE&#160;&#160; ddmid.database_id = DB_ID()\n\tORDER BY est_impact DESC;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 28: Finding missing indexes<\/p>\n<h4 id=\"thirtieth\">Foreign Keys <\/h4>\n<p>Foreign Keys define table dependencies and control referential integrity between  multiple tables. In an Entity Relationship Diagram (ERD), the lines between the tables indicate the foreign keys.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Foreign Keys \n\tSELECT&#160; @@Servername AS ServerName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DB_NAME() AS DB_Name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; parent.name AS 'TableName' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name AS 'ForeignKey' ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.[Type] ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.Create_date\n\tFROM&#160;&#160;&#160; sys.objects o\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.objects parent ON o.parent_object_id = parent.object_id\n\tWHERE&#160;&#160; o.[Type] = 'F' -- Foreign Keys \n\tORDER BY parent.name ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; o.name \n\t--OR \n\tSELECT&#160; f.name AS ForeignKey ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SCHEMA_NAME(f.SCHEMA_ID) AS SchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(f.parent_object_id) AS TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; SCHEMA_NAME(o.SCHEMA_ID) ReferenceSchemaName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; COL_NAME(fc.referenced_object_id, fc.referenced_column_id)\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AS ReferenceColumnName\n\tFROM&#160;&#160;&#160; sys.foreign_keys AS f\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.foreign_key_columns AS fc\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON f.OBJECT_ID = fc.constraint_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id\n\tORDER BY TableName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ReferenceTableName;\n\tGO\n<\/pre>\n<p class=\"caption\">Listing 29: Exploring Foreign keys<\/p>\n<p>It will produce output similar to the following:<\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<p class=\"STTableSmallerHeader\">ForeignKey <\/p>\n<\/td>\n<td>\n<p class=\"STTableSmallerHeader\">TableName <\/p>\n<\/td>\n<td>\n<p class=\"STTableSmallerHeader\">ColumnName <\/p>\n<\/td>\n<td>\n<p class=\"STTableSmallerHeader\">Reference<\/p>\n<p class=\"STTableSmallerHeader\">TableName <\/p>\n<\/td>\n<td>\n<p class=\"STTableSmallerHeader\">Reference<\/p>\n<p class=\"STTableSmallerHeader\">ColumnName <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__discounts__stor___286302EC <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">discounts <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stor_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stores <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stor_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__employee__job_id__34C8D9D1 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">employee <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">job_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">jobs <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">job_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__employee__pub_id__37A5467C <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">employee <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">publishers <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__pub_info__pub_id__300424B4 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_info <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">publishers <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__roysched__title___267ABA7A <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">roysched <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titles <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__sales__stor_id__239E4DCF <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">sales <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stor_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stores <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">stor_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__sales__title_id__24927208 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">sales <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titles <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__titleauth__au_id__1DE57479 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titleauthor <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">au_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">authors <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">au_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__titleauth__title__1ED998B2 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titleauthor <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titles <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">title_id <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"STTableTextSmaller\">FK__titles__pub_id__1A14E395 <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">titles <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">publishers <\/p>\n<\/td>\n<td>\n<p class=\"STTableTextSmaller\">pub_id<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"thirtyfirst\">Missing Indexes that support Foreign Keys. <\/h4>\n<p>As a general best practice, it is recommended to have an index associated with  each foreign key. This facilitates faster table joins, which are typically joined on foreign key columns anyway. Indexes  on foreign keys also facilitate faster deletes. If these supporting indexes are missing, SQL will perform a table scale  on the related table each time a record in the first table is deleted.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Foreign Keys missing indexes \n\t-- Note this script only works for creating single column indexes. \n\t-- Multiple FK columns are out of scope for this script. \n\tSELECT&#160; DB_NAME() AS DBName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; rc.Constraint_Name AS FK_Constraint , \n\t-- rc.Constraint_Catalog AS FK_Database, \n\t-- rc.Constraint_Schema AS FKSch, \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ccu.Table_Name AS FK_Table ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ccu.Column_Name AS FK_Column ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ccu2.Table_Name AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ccu2.Column_Name AS ParentColumn ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; I.Name AS IndexName ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; CASE WHEN I.Name IS NULL\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; THEN 'IF NOT EXISTS (SELECT * FROM sys.indexes\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE object_id = OBJECT_ID(N'''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + RC.Constraint_Schema + '.' + ccu.Table_Name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ''') AND name = N''IX_' + ccu.Table_Name + '_'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ccu.Column_Name + ''') '\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + 'CREATE NONCLUSTERED INDEX IX_' + ccu.Table_Name + '_'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ccu.Column_Name + ' ON ' + rc.Constraint_Schema + '.'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ccu.Table_Name + '( ' + ccu.Column_Name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + ' ASC ) WITH (PAD_INDEX = OFF, \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; STATISTICS_NORECOMPUTE = OFF,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SORT_IN_TEMPDB = ON, IGNORE_DUP_KEY = OFF,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; DROP_EXISTING = OFF, ONLINE = ON);'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE ''\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; END AS SQL\n\tFROM&#160;&#160;&#160; information_schema.referential_constraints RC\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON rc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu2\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON rc.UNIQUE_CONSTRAINT_NAME = ccu2.CONSTRAINT_NAME\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; LEFT JOIN sys.columns c ON ccu.Column_Name = C.name\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ccu.Table_Name = OBJECT_NAME(C.OBJECT_ID)\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; LEFT JOIN sys.index_columns ic ON C.OBJECT_ID = IC.OBJECT_ID\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND c.column_id = ic.column_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND index_column_id = 1\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- index found has the foreign key\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; --&#160; as the first column \n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; LEFT JOIN sys.indexes i ON IC.OBJECT_ID = i.OBJECT_ID\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ic.index_Id = i.index_Id\n\tWHERE&#160;&#160; I.name IS NULL\n\tORDER BY FK_table ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ParentColumn; \n\tGO\n<\/pre>\n<p class=\"caption\">Listing 30: Finding missing Foreign Key indexes<\/p>\n<h3 id=\"thirtysecond\">Object Dependencies<\/h3>\n<p>It depends&#8230;I&#8217;m sure you have heard that before. I will review three different  methods to &#8216;reverse engineer&#8217; database dependencies. The first method use the stored procedure  <code> sp_msdependencies<\/code>. The second  method uses the foreign key systems tables. The third method uses a CTE. <\/p>\n<h4 id=\"thirtythird\">sp_msdependencies<\/h4>\n<p><code> sp_msdependencies<\/code>  is a SQL Server undocumented stored procedure that can be helpful in navigating complex table interdependencies.  <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_msdependencies '?' -- Displays Help \n\t&#160;\n\tsp_MSobject_dependencies name = NULL, type = NULL, flags = 0x01fd\n\t&#160; name:&#160; name or null (all objects of type)\n\t&#160; type:&#160; type number (see below) or null\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; if both null, get all objects in database\n\t&#160; flags is a bitmask of the following values:\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x10000&#160; = return multiple parent\/child rows per object\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x20000&#160; = descending return order\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x40000&#160; = return children instead of parents\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x80000&#160; = Include input object in output result set\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x100000 = return only firstlevel (immediate) parents\/children\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0x200000 = return only DRI dependencies\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; power(2, object type number(s))&#160; to return in results set:\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 0 (1&#160; &#160;&#160;&#160;&#160;&#160; - 0x0001)&#160;&#160;&#160;&#160; - UDF\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 1 (2&#160; &#160;&#160;&#160;&#160;&#160; - 0x0002)&#160;&#160;&#160;&#160; - system tables or MS-internal objects\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 2 (4&#160; &#160;&#160;&#160;&#160;&#160; - 0x0004)&#160;&#160;&#160;&#160; - view\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 3 (8&#160; &#160;&#160;&#160;&#160;&#160; - 0x0008)&#160;&#160;&#160;&#160; - user table\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 4 (16&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; - 0x0010)&#160;&#160;&#160;&#160; - procedure\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 5 (32&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; - 0x0020)&#160;&#160;&#160;&#160; - log\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 6 (64 &#160;&#160;&#160;&#160;&#160; - 0x0040)&#160;&#160;&#160;&#160; - default\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 7 (128&#160;&#160;&#160;&#160;&#160; - 0x0080)&#160;&#160;&#160;&#160; - rule\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 8 (256&#160;&#160;&#160;&#160;&#160; - 0x0100)&#160;&#160;&#160;&#160; - trigger\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 12 (1024&#160;&#160;&#160; - 0x0400) - uddt\n\t&#160;&#160;&#160;&#160;&#160;&#160; &#160; shortcuts:\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 29&#160;&#160; (0x011c) - trig, view, user table, procedure\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 448&#160; (0x00c1) - rule, default, datatype\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 4606 (0x11fd) - all but systables\/objects\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; 4607 (0x11ff) - all\n<\/pre>\n<p class=\"caption\">Listing 31: sp_msdependencies help<\/p>\n<p>If we list all dependencies using <code> sp_msdependencies<\/code>, it will  return four columns: <code> Type<\/code>, <code> ObjName<\/code>,  <code> Owner<\/code>  (Schema) and <code>  Sequence<\/code>.<\/p>\n<p>Make special note of the Sequence number, this will start at 1 and will grow in  sequential order. The Sequence is the number of layers, or rows, of dependencies. <\/p>\n<p>I have used this method several times when asked to perform archiving or  deleting of data on some large database models. If you know the table dependencies, you have a road map of the order in  which you need to archive or delete records. Start with the table with the largest sequence number first, then work  backward from the largest number to the smallest number. Tables with the same sequence number can be removed at the same  time. This method does not violate any of the foreign key constraints, and thus allows you to move\/delete records  without temporarily dropping and rebuilding constraints. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tEXEC sp_msdependencies NULL&#160;&#160;&#160; -- List all database dependencies \n\tEXEC sp_msdependencies NULL, 3 -- List table dependencies \n<\/pre>\n<p class=\"caption\">Listing 32: using sp_msdependencies to view all dependencies<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img139.jpg\" alt=\"2066-img139.jpg\" \/><\/p>\n<p>In SSMS, if you right click on a table name you can click on &#8216;View Dependencies&#8217;  and &#8216;Objects that depend on tablename&#8217;.<\/p>\n<p> \t<img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img13A.jpg\" alt=\"2066-img13A.jpg\" \/><\/p>\n<p>We can see similar information from <code> sp_msdependencies<\/code> as follows:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- sp_MSdependencies sp_MSdependencies - First level only \n\t-- Objects that are dependent on the specified object \n\tEXEC sp_msdependencies N'Sales.Customer',null, 1315327 -- Change Table Name \n<\/pre>\n<p class=\"caption\">Listing 33: using sp_msdependencies to view objects that depend on a table  (first level only)<\/p>\n<p> \t<img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img13B.jpg\" alt=\"2066-img13B.jpg\" \/><\/p>\n<p>In SSMS if you right click on a table name you can click on &#8216;View Dependencies&#8217;  and &#8216;Objects that depend on tablename&#8217;, and then expand all the &#8216;+&#8217; signs to see all the levels it looks like this.<\/p>\n<p> \t<img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img13C.jpg\" alt=\"2066-img13C.jpg\" \/><\/p>\n<p>The following <code> msdependencies<\/code> report would  provide similar information.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- sp_MSdependencies - All levels \n\t-- Objects that are dependent on the specified object \n\tEXEC sp_MSdependencies N'Sales.Customer', NULL, 266751 -- Change Table Name \n<\/pre>\n<p class=\"caption\">Listing 34: using sp_msdependencies to view objects that depend on a table (all  levels)<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img13E.jpg\" alt=\"2066-img13E.jpg\" \/><\/p>\n<p>Similarly, in SSMS, we can see the objects on which a given table depends.<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img13F.jpg\" alt=\"2066-img13F.jpg\" \/><\/p>\n<p>The following <code> msdependencies<\/code> report would  provide similar information.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- Objects that the specified object is dependent on \n\tEXEC sp_MSdependencies N'Sales.Customer', null, 1053183 -- Change Table \n<\/pre>\n<p class=\"caption\">Listing 35: Using sp_msdependencies to view objects on which a table depends<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2066-img141.jpg\" alt=\"2066-img141.jpg\" \/><\/p>\n<p>If you want a list of just table dependencies you could use a temp table to  filter the dependency types.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tCREATE TABLE #TempTable1\n\t&#160;&#160;&#160;  (\n\t&#160;&#160;&#160;&#160;&#160; Type INT ,\n\t&#160;&#160;&#160;&#160;&#160; ObjName VARCHAR(256) ,\n\t&#160;&#160;&#160;&#160;&#160; Owner VARCHAR(25) ,\n\t&#160;&#160;&#160;&#160;&#160; Sequence INT\n\t&#160;&#160;&#160; ); \n\tINSERT&#160; INTO #TempTable1\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; EXEC sp_MSdependencies NULL \n\tSELECT&#160; *\n\tFROM&#160;&#160;&#160; #TempTable1\n\tWHERE&#160;&#160; Type = 8 --Tables \n\tORDER BY Sequence ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ObjName \n\tDROP TABLE #TempTable1; \n<\/pre>\n<p class=\"caption\">Listing 36: Using sp_msdependencies to view only table dependencies<\/p>\n<h4 id=\"thirtyfourth\">Query the system catalog views<\/h4>\n<p>The second method to reverse engineer your database dependencies is to query the  foreign key relationships system tables.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t--Independent tables\n\tSELECT&#160; Name AS InDependentTables\n\tFROM&#160;&#160;&#160; sys.tables\n\tWHERE&#160;&#160; object_id NOT IN ( SELECT referenced_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160; sys.foreign_key_columns )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;-- Check for parents\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND object_id NOT IN ( SELECT parent_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160; sys.foreign_key_columns )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;-- Check for Dependents\n\tORDER BY Name\n\t&#160;\n\t-- Tables with dependencies.\n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(referenced_object_id) AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(parent_object_id) AS DependentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(constraint_object_id) AS ForeignKeyName\n\tFROM&#160;&#160;&#160; sys.foreign_key_columns\n\tORDER BY ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DependentTable\n\t&#160;\n\t-- Top level of the pyramid tables. Tables with no parents.\n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(referenced_object_id) AS TablesWithNoParent\n\tFROM&#160;&#160;&#160; sys.foreign_key_columns\n\tWHERE&#160;&#160; referenced_object_id NOT IN ( SELECT&#160; parent_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM&#160;&#160;&#160; sys.foreign_key_columns )\n\tORDER BY 1\n\t&#160;\n\t-- Bottom level of the pyramid tables. \n\t-- Tables with no dependents. (These are the leaves on a tree.)\n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(parent_object_id) AS TablesWithNoDependents\n\tFROM&#160;&#160;&#160; sys.foreign_key_columns\n\tWHERE&#160;&#160; parent_object_id NOT IN ( SELECT&#160; referenced_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; sys.foreign_key_columns )\n\tORDER BY 1\n\t&#160;\n\t-- Tables with both parents and dependents. \n\t-- Tables in the middle of the hierarchy\n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(referenced_object_id) AS MiddleTables\n\tFROM&#160;&#160;&#160; sys.foreign_key_columns\n\tWHERE&#160;&#160; referenced_object_id IN ( SELECT&#160; parent_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; sys.foreign_key_columns )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND parent_object_id NOT IN ( SELECT&#160; referenced_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; sys.foreign_key_columns )\n\tORDER BY 1;\n\t&#160;\n\t-- in rare cases, you might find a self-referencing dependent table.\n\t-- Recursive (self) referencing table dependencies. \n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(referenced_object_id) AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(parent_object_id) AS ChildTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(constraint_object_id) AS ForeignKeyName\n\tFROM&#160;&#160;&#160; sys.foreign_key_columns\n\tWHERE&#160;&#160; referenced_object_id = parent_object_id\n\tORDER BY 1 ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; 2;\n<\/pre>\n<p class=\"caption\">Listing 37: Using catalog views to view dependencies<\/p>\n<h4 id=\"thirtyfifth\">Using a Common Table Expression (CTE)<\/h4>\n<p>The third method to reverse engineer your database hierarchical dependencies is  to solve a recursive query using a Common Table Expression (CTE).<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t-- How to find the hierarchical dependencies\n\t-- Solve recursive queries using Common Table Expressions (CTE)\n\tWITH&#160;&#160;&#160; TableHierarchy ( ParentTable, DependentTable, Level )\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AS (\n\t-- Anchor member definition (First level group to start the process)\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CAST(NULL AS INT) AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; e.referenced_object_id AS DependentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 0 AS Level\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160;&#160; sys.foreign_key_columns AS e\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160;&#160; e.referenced_object_id NOT IN (\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; parent_object_id\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; sys.foreign_key_columns )\n\t-- Add filter dependents of only one parent table\n\t-- AND Object_Name(e.referenced_object_id) = 'User'\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UNION ALL\n\t-- Recursive member definition (Find all the layers of dependents)\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT --Distinct\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; e.referenced_object_id AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;e.parent_object_id AS DependentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Level + 1\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160;&#160; sys.foreign_key_columns AS e\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN TableHierarchy AS d\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;ON ( e.referenced_object_id ) = \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; d.DependentTable\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n\t&#160;&#160;&#160; -- Statement that executes the CTE\n\tSELECT DISTINCT\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(ParentTable) AS ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; OBJECT_NAME(DependentTable) AS DependentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; Level\n\tFROM&#160;&#160;&#160; TableHierarchy\n\tORDER BY Level ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; ParentTable ,\n\t&#160;&#160;&#160;&#160;&#160;&#160;&#160; DependentTable;\n<\/pre>\n<p class=\"caption\">Listing 38: Using catalog views and a CTE to view dependencies<\/p>\n<h2 id=\"thirtysixth\">Summary<\/h2>\n<p>Within an hour or two, I can usually gain a good understanding of any database  design, using the &#8216;reverse engineering&#8217; methods described in this article. <\/p>\n<p>My intention has been to provide a set of sample scripts that you can run  immediately on the server and databases you are currently supporting. Microsoft Excel is a great tool to use to help  analyze and document your database data. I recommend copying some of the following tables and column query results to  excel, so you can filter and sort in a variety of ways. It also is a great way to share the results of your reverse  engineering analysis with your managers and peers.<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Most DBAs hoard their own favourite T-SQL scripts to help them with their work, often on a USB &#8216;thumbdrive&#8217;, but it is rare that one of them offers us a glimpse of the sort of scripts that they find useful. It is our good fortune that Scott Swanberg shows us those scripts he uses for discovering more about database objects.&hellip;<\/p>\n","protected":false},"author":189815,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4168,4170,4150,4151,4183],"coauthors":[],"class_list":["post-1881","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-database","tag-database-administration","tag-sql","tag-sql-server","tag-t-sql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1881","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/189815"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1881"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1881\/revisions"}],"predecessor-version":[{"id":92222,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1881\/revisions\/92222"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1881"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1881"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1881"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1881"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}