{"id":1852,"date":"2014-08-12T00:00:00","date_gmt":"2014-08-12T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/quickly-investigating-whats-in-the-tables-of-sql-server-databases\/"},"modified":"2021-09-29T16:21:34","modified_gmt":"2021-09-29T16:21:34","slug":"quickly-investigating-whats-in-the-tables-of-sql-server-databases","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/quickly-investigating-whats-in-the-tables-of-sql-server-databases\/","title":{"rendered":"Quickly Investigating What&#8217;s in the Tables of  SQL Server Databases"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">It is very handy to get an impression of a database, or a schema within a database, by being able to see what&#8217;s in the first few rows of all the tables. I like to use a browser to do this so as I get the search and zoom facilities for free. It also means I can keep a whole lot of databases or schemas loaded up into browser tabs if necessary.<\/p>\n<p>Every SQL developer has a whole lot of routines that they use in order to more quickly make sense of the databases they work on. Almost everyone I know who is working in the same business has their own different collection of code, often stored on a thumbdrive, that evolves over time. What gets into that &#8216;toolbox&#8217; is very much an idiosyncratic choice. There are gems that everyone seems to have such as Kimberly Tripp&#8217;s <a href=\"http:\/\/www.sqlskills.com\/blogs\/kimberly\/category\/sp_helpindex-rewrites\/\">routine for finding duplicate indexes<\/a>, and Adam Machanic&#8217;s <code>sp_whoisactive<\/code>: However, most of the cherished routines in the average thumbdrive seem to be unfinished hacks that just come in handy. Because they&#8217;re unfinished and a bit untidy you tend not to see them. I have quite a few that I use regularly to investigate a database. One routine I find particularly useful is a simple thing that simply prints out the first three lines of a collection of tables that I specify. This is a curiously useful way of getting a good overview of what a database, or more usually a schema within a database, is really doing and how it is designed. It actually produces an HTML document that produces an index at the top that can be used to navigate to the table you want to examine. You can either email this from the database or export it as a file to view in a browser. In PowerShell, you can execute it as a query, save the HTML as a local file, and invoke the browser to display the results.<\/p>\n<p>Although the routine is pretty simple, there are a few obvious problems. You can&#8217;t really display the big values such as BLOBs and long strings. I just display the first 100 characters. NULLs can cause problems until you know the right XML spell, if a cell contains HTML it has to be cleaned up to avoid rendering problems. On a large database, you will only want to display the contents of a single schema, or a range of tables. (a 1000-table database took six minutes to do on test!)<\/p>\n<p>I find it just plain useful although some may rub their eyes at such a humble utility. I offer it in all its unkempt charm in the hope that someone else will find it a good way of getting to know a database.<\/p>\n<h1>The Beef<\/h1>\n<p>\u00a0Here is the old <code>PUBS<\/code> database. It is just enough to show you what the routine will do. If you have a realistic database, you&#8217;ll have a lot of tables so the catalog or contents page in the beginning which you click on to get to the table you wish to view becomes more useful!<\/p>\n<div class=\"thetables\">\n<div class=\"columnar\">\n<ol>\n<li><a href=\"#table2\">[dbo].[authors]<\/a><\/li>\n<li><a href=\"#table3\">[dbo].[discounts]<\/a><\/li>\n<li><a href=\"#table4\">[dbo].[employee]<\/a><\/li>\n<li><a href=\"#table5\">[dbo].[jobs]<\/a><\/li>\n<li><a href=\"#table6\">[dbo].[pub_info]<\/a><\/li>\n<li><a href=\"#table7\">[dbo].[publishers]<\/a><\/li>\n<li><a href=\"#table8\">[dbo].[roysched]<\/a><\/li>\n<li><a href=\"#table9\">[dbo].[sales]<\/a><\/li>\n<li><a href=\"#table10\">[dbo].[stores]<\/a><\/li>\n<li><a href=\"#table11\">[dbo].[titleauthor]<\/a><\/li>\n<li><a href=\"#table12\">[dbo].[titles]<\/a><\/li>\n<\/ol>\n<\/div>\n<table id=\"table\" class=\"tablecontents\">\n<thead>\n<tr>\n<th>au_id<\/th>\n<th>au_lname<\/th>\n<th>au_fname<\/th>\n<th>phone<\/th>\n<th>address<\/th>\n<th>city<\/th>\n<th>state<\/th>\n<th>zip<\/th>\n<th>contract<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>172-32-1176<\/td>\n<td>White<\/td>\n<td>Johnson<\/td>\n<td>408 496-7223<\/td>\n<td>10932 Bigge Rd.<\/td>\n<td>Menlo Park<\/td>\n<td>CA<\/td>\n<td>94025<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>213-46-8915<\/td>\n<td>Green<\/td>\n<td>Marjorie<\/td>\n<td>415 986-7020<\/td>\n<td>309 63rd St. #411<\/td>\n<td>Oakland<\/td>\n<td>CA<\/td>\n<td>94618<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>238-95-7766<\/td>\n<td>Carson<\/td>\n<td>Cheryl<\/td>\n<td>415 548-7723<\/td>\n<td>589 Darwin Ln.<\/td>\n<td>Berkeley<\/td>\n<td>CA<\/td>\n<td>94705<\/td>\n<td>1<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>discounttype<\/th>\n<th>stor_id<\/th>\n<th>lowqty<\/th>\n<th>highqty<\/th>\n<th>discount<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Initial Customer<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>10.50<\/td>\n<\/tr>\n<tr>\n<td>Volume Discount<\/td>\n<td>NULL<\/td>\n<td>100<\/td>\n<td>1000<\/td>\n<td>6.70<\/td>\n<\/tr>\n<tr>\n<td>Customer Discount<\/td>\n<td>8042<\/td>\n<td>NULL<\/td>\n<td>NULL<\/td>\n<td>5.00<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>emp_id<\/th>\n<th>fname<\/th>\n<th>minit<\/th>\n<th>lname<\/th>\n<th>job_id<\/th>\n<th>job_lvl<\/th>\n<th>pub_id<\/th>\n<th>hire_date<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>PMA42628M<\/td>\n<td>Paolo<\/td>\n<td>M<\/td>\n<td>Accorti<\/td>\n<td>13<\/td>\n<td>35<\/td>\n<td>0877<\/td>\n<td>1992-08-27T00:00:00<\/td>\n<\/tr>\n<tr>\n<td>PSA89086M<\/td>\n<td>Pedro<\/td>\n<td>S<\/td>\n<td>Afonso<\/td>\n<td>14<\/td>\n<td>89<\/td>\n<td>1389<\/td>\n<td>1990-12-24T00:00:00<\/td>\n<\/tr>\n<tr>\n<td>VPA30890F<\/td>\n<td>Victoria<\/td>\n<td>P<\/td>\n<td>Ashworth<\/td>\n<td>6<\/td>\n<td>140<\/td>\n<td>0877<\/td>\n<td>1990-09-13T00:00:00<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>job_id<\/th>\n<th>job_desc<\/th>\n<th>min_lvl<\/th>\n<th>max_lvl<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1<\/td>\n<td>New Hire &#8211; Job not specified<\/td>\n<td>10<\/td>\n<td>10<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>Chief Executive Officer<\/td>\n<td>200<\/td>\n<td>250<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>Business Operations Manager<\/td>\n<td>175<\/td>\n<td>225<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>pub_id<\/th>\n<th>logo<\/th>\n<th>pr_info<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>0736<\/td>\n<td>R0lGODlh0wAfALMPAAAAAIAAAACAAICAAAAAgIAAg<\/td>\n<td>This is sample text data for New Moon Books, publisher 0736 in the pubs database. New Moon Books is<\/td>\n<\/tr>\n<tr>\n<td>0877<\/td>\n<td>R0lGODlhiwAvALMPAAAAAIAAAACAAICAAAAAgIAAg<\/td>\n<td>This is sample text data for Binnet &amp; Hardley, publisher 0877 in the pubs database. Binnet &amp; Hardley<\/td>\n<\/tr>\n<tr>\n<td>1389<\/td>\n<td>R0lGODlhwgAdALMPAAAAAIAAAACAAICAAAAAg<\/td>\n<td>This is sample text data for Algodata Infosystems, publisher 1389 in the pubs database. Algodata Inf<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>pub_id<\/th>\n<th>pub_name<\/th>\n<th>city<\/th>\n<th>state<\/th>\n<th>country<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>0736<\/td>\n<td>New Moon Books<\/td>\n<td>Boston<\/td>\n<td>MA<\/td>\n<td>USA<\/td>\n<\/tr>\n<tr>\n<td>0877<\/td>\n<td>Binnet &amp; Hardley<\/td>\n<td>Washington<\/td>\n<td>DC<\/td>\n<td>USA<\/td>\n<\/tr>\n<tr>\n<td>1389<\/td>\n<td>Algodata Infosystems<\/td>\n<td>Berkeley<\/td>\n<td>CA<\/td>\n<td>USA<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>title_id<\/th>\n<th>lorange<\/th>\n<th>hirange<\/th>\n<th>royalty<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>BU1032<\/td>\n<td>0<\/td>\n<td>5000<\/td>\n<td>10<\/td>\n<\/tr>\n<tr>\n<td>BU1032<\/td>\n<td>5001<\/td>\n<td>50000<\/td>\n<td>12<\/td>\n<\/tr>\n<tr>\n<td>PC1035<\/td>\n<td>0<\/td>\n<td>2000<\/td>\n<td>10<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>stor_id<\/th>\n<th>ord_num<\/th>\n<th>ord_date<\/th>\n<th>qty<\/th>\n<th>payterms<\/th>\n<th>title_id<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>6380<\/td>\n<td>6871<\/td>\n<td>1994-09-14T00:00:00<\/td>\n<td>5<\/td>\n<td>Net 60<\/td>\n<td>BU1032<\/td>\n<\/tr>\n<tr>\n<td>6380<\/td>\n<td>722a<\/td>\n<td>1994-09-13T00:00:00<\/td>\n<td>3<\/td>\n<td>Net 60<\/td>\n<td>PS2091<\/td>\n<\/tr>\n<tr>\n<td>7066<\/td>\n<td>A2976<\/td>\n<td>1993-05-24T00:00:00<\/td>\n<td>50<\/td>\n<td>Net 30<\/td>\n<td>PC8888<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>stor_id<\/th>\n<th>stor_name<\/th>\n<th>stor_address<\/th>\n<th>city<\/th>\n<th>state<\/th>\n<th>zip<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>6380<\/td>\n<td>Eric the Read Books<\/td>\n<td>788 Catamaugus Ave.<\/td>\n<td>Seattle<\/td>\n<td>WA<\/td>\n<td>98056<\/td>\n<\/tr>\n<tr>\n<td>7066<\/td>\n<td>Barnum&#8217;s<\/td>\n<td>567 Pasadena Ave.<\/td>\n<td>Tustin<\/td>\n<td>CA<\/td>\n<td>92789<\/td>\n<\/tr>\n<tr>\n<td>7067<\/td>\n<td>News &amp; Brews<\/td>\n<td>577 First St.<\/td>\n<td>Los Gatos<\/td>\n<td>CA<\/td>\n<td>96745<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>au_id<\/th>\n<th>title_id<\/th>\n<th>au_ord<\/th>\n<th>royaltyper<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>172-32-1176<\/td>\n<td>PS3333<\/td>\n<td>1<\/td>\n<td>100<\/td>\n<\/tr>\n<tr>\n<td>213-46-8915<\/td>\n<td>BU1032<\/td>\n<td>2<\/td>\n<td>40<\/td>\n<\/tr>\n<tr>\n<td>213-46-8915<\/td>\n<td>BU2075<\/td>\n<td>1<\/td>\n<td>100<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"tablecontents\">\n<thead>\n<tr>\n<th>title_id<\/th>\n<th>title<\/th>\n<th>type<\/th>\n<th>pub_id<\/th>\n<th>price<\/th>\n<th>advance<\/th>\n<th>royalty<\/th>\n<th>ytd_sales<\/th>\n<th>notes<\/th>\n<th>pubdate<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>BU1032<\/td>\n<td>The Busy Executive&#8217;s Database Guide<\/td>\n<td>business<\/td>\n<td>1389<\/td>\n<td>19.9900<\/td>\n<td>5000.0000<\/td>\n<td>10<\/td>\n<td>4095<\/td>\n<td>An overview of available database systems with emphasis on common business applications. Illustrated<\/td>\n<td>1991-06-12T00:00:00<\/td>\n<\/tr>\n<tr>\n<td>BU1111<\/td>\n<td>Cooking with Computers: Surreptitious Balance Sheets<\/td>\n<td>business<\/td>\n<td>1389<\/td>\n<td>11.9500<\/td>\n<td>5000.0000<\/td>\n<td>10<\/td>\n<td>3876<\/td>\n<td>Helpful hints on how to use your electronic resources to the best advantage.<\/td>\n<td>1991-06-09T00:00:00<\/td>\n<\/tr>\n<tr>\n<td>BU2075<\/td>\n<td>You Can Combat Computer Stress!<\/td>\n<td>business<\/td>\n<td>0736<\/td>\n<td>2.9900<\/td>\n<td>10125.0000<\/td>\n<td>24<\/td>\n<td>18722<\/td>\n<td>The latest medical and psychological techniques for living with the electronic office. Easy-to-under<\/td>\n<td>1991-06-30T00:00:00<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Here, in a separate HTML page is <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2033-PhilFactor-AdventureWorks2012.html\">AdventureWorks2012<\/a>, done the same way.<\/p>\n<p>The start of the page looks a bit like this (seen from a distance)<\/p>\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2033-PhilFactor-AdventureWorks2012.html\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2033-Adventureworks.jpg\" alt=\"2033-Adventureworks.jpg\" \/><\/a><\/p>\n<p class=\"caption\">The tables from adventureWorks, seen from a browser (thumbnail)<\/p>\n<p>When I&#8217;m using this, I use a SQL Script executed in PowerShell for a development group of servers in my registered server collection in SSMS. I set a task going on the scheduler to create HTML files on my local workstation, one for each database or schema that I can then browse when I need to. However, I&#8217;ll give enough information so you can use it in a number of different ways that suit you.<\/p>\n<h1>Selecting tables<\/h1>\n<p>There is a task here to select a list of tables. I like to specify tables using wildcards so that &#8216;p%.p%&#8217; would mean all tables whose names beginning with the letter &#8216;P&#8217;, from a schema beginning with the letter &#8216;P&#8217; (seventeen in AdventureWorks for 2008), or &#8216;%.%&#8217; for all tables from all schemas. You could do things like &#8216;%.%log&#8217; for all log tables, but beware of using square brackets in LIKE expressions such as %[^A-Z0-9]%. These get trashed by the PARSENAME function which tends to return a <code>NULL<\/code>!<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DECLARE @WildCardName VARCHAR(150) --the wildcard to represent the tables you want\r\nDECLARE @tablesToDo TABLE(TheOrder INT IDENTITY,TheTable VARCHAR(2000))\r\n\u00a0\u00a0\u00a0 --this is the table variable containing our list of tables\r\n\u00a0\r\nSELECT @WildCardName ='%.%';\r\n\u00a0\r\n\u00a0\u00a0\u00a0 --if no Schema was specified, we'll assume all schemas are intended\r\nIF PARSENAME(@WildCardName,2) IS NULL \r\n\u00a0\u00a0\u00a0 SELECT @WildCardName='%.'+@WildCardName;\r\n\u00a0\u00a0\u00a0 --now we fetch all the table names into the table\r\nINSERT INTO @tablesToDo (TheTable) --insert the names in order into the table\r\n\u00a0\u00a0\u00a0 SELECT QUOTENAME(schema_name([schema_ID]))+'.'+QUOTENAME(name) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 FROM sys.tables\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE name LIKE PARSENAME(@WildCardName,1)--the table name\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND schema_name([schema_ID]) LIKE PARSENAME(@WildCardName,2)--the schema\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY schema_name([schema_ID]),name; --order by schema, followed by name \r\n\u00a0\u00a0\u00a0 --nothing found? We warn the user and abort.\r\nIF @@rowcount=0 --if we found nothing\r\n\u00a0\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0 RAISERROR\u00a0 ('&lt;p&gt;No such table like ''%s'' in this database&lt;\/p&gt;',16,1,@WildCardName);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --return 1\r\n\u00a0\u00a0\u00a0 END\r\n<\/pre>\n<p>Once you have your list of tables, then it is pretty-well plain sailing. I tend to use something like this for any sort of exploration of tables, such as for producing pretty<code> CREATE <\/code>statements for tables.<\/p>\n<h2>Displaying the list of tables.<\/h2>\n<p>In HTML, I like to have multi-column lists of tables just to make it a bit easier to scan through. For these, the HTML is just a straightforward list.\u00a0 The formatting is all in the CSS! This is the easiest way of doing it, using the table variable we already have; though I tend to do the contents list in step with each table.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Declare @Contents Varchar(MAX)\r\nSelect @Contents=coalesce(@Contents,'')+'&lt;li&gt;&lt;a href=\"#table'+convert(Varchar(5),TheOrder)+'\"&gt;'+TheTable+'&lt;\/a&gt;&lt;\/li&gt;\r\n'\u00a0\u00a0 FROM @tablesToDo\r\nSelect @contents='&lt;ol&gt;'+@contents+'&lt;\/ol&gt;' \r\n<\/pre>\n<h2>Displaying the contents of the first three rows<\/h2>\n<p>You just need to execute\u00a0 SQL Queries to get the first three rows of each table to return as XML, and then use this XML document to produce an HTML table. There are a couple of problems to using <code>SELECT * from &lt;tablename&gt;\u00a0 FOR XML PATH<\/code><\/p>\n<h3>Illegal column names<\/h3>\n<p>If it comes across a column name that is illegal in XML terms, it throws its hands up and errors out.<\/p>\n<p>Try, for example&#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Select * from Adventureworks.dbo.AWBuildVersion for xml path\r\n<\/pre>\n<p>And you get&#8230;<\/p>\n<pre class=\"\">Msg 6850, Level 16, State 1, Line 1 \r\nColumn name 'Database Version' contains an invalid XML identifier as required by FOR XML; ' '(0x0020) is the first character at fault.\r\n<\/pre>\n<p>You actually have to specify the columns, with XML-legal aliases if necessary, which takes some of the advantages away from using the XML trick. E.g.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Select SystemInformationID, [Database Version] as DatabaseVersion, VersionDate, ModifiedDate \r\nfrom Adventureworks.dbo.AWBuildVersion for xml path\r\n<\/pre>\n<p>However, you still get automatic truncation and conversion of the values, which is handy.<\/p>\n<p>So doing this &#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DECLARE @XML XML;\r\nSelect @XML=(Select SystemInformationID, [Database Version] as DatabaseVersion, VersionDate, ModifiedDate \r\nfrom Adventureworks.dbo.AWBuildVersion for xml path, ELEMENTS XSINIL, root);\r\nSELECT\u00a0 [x].value('local-name(.)', 'varchar(100)'),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [x].value('text()[1]','varchar(100)')\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 FROM @XML.nodes('root\/row[1]\/*')\u00a0 as a(x);\r\n<\/pre>\n<p>&#8230; will give you this:<\/p>\n<table class=\"MsoTableGrid\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p><strong>Column_Name<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>The_Value<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p>SystemInformationID<\/p>\n<\/td>\n<td valign=\"top\">\n<p>1<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p>DatabaseVersion<\/p>\n<\/td>\n<td valign=\"top\">\n<p>10.00.80404.00<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p>VersionDate<\/p>\n<\/td>\n<td valign=\"top\">\n<p>2008-04-04T00:00:00<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p>ModifiedDate<\/p>\n<\/td>\n<td valign=\"top\">\n<p>2008-04-04T00:00:00<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&#8230; and so it is just a matter of getting the next two row values and formatting it all up in an HTML table.<\/p>\n<h3>CLR Datatypes<\/h3>\n<p>One other problem you&#8217;ll hit is that The<code> FOR XML <\/code>phrase will cause<code> SELECT * FROM \t<\/code>to crash if there is a CLR data type in the table. Try, for example&#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSelect * from person.address for XML path<\/pre>\n<p>and you get &#8230;.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Msg 6865, Level 16, State 1, Line 2\r\n \tFOR XML does not support CLR types - cast CLR types explicitly into one of the supported types in FOR XML queries.<\/pre>\n<p>so instead, you need to\u00a0 do &#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Select AddressID, AddressLine1, AddressLine2, City, StateProvinceID, PostalCode, convert(varchar(100),SpatialLocation), rowguid, ModifiedDate from person.address for XML path<\/pre>\n<p>..and by doing an explicit conversion, everything works happily.<\/p>\n<h3>NULLs<\/h3>\n<p>You&#8217;ll notice that we&#8217;ve specified ELEMENTS XSINIL, which gives us <code>NULL<\/code> values. Unless you do this, XML assumes that the column doesn&#8217;t exist for that row, which is pretty reasonable\u00a0 for a data document. However, it isn&#8217;t correct for a SQL table.<\/p>\n<h2>Putting it all together<\/h2>\n<p>The devil is in the detail, as any SQL Developer will tell you.<\/p>\n<p>Here is the batch. This would normally be made into a stored procedure, but I tend to use such things from SSMS query window or from PowerShell, and for the latter I don&#8217;t like to rely on anything in particular being installed. I&#8217;m saving the HTML in a file that the browser can display. I like to have a separate file for each database, so I just specify the path to the directory where the file should be and leave it to the routine to name the file, using the database and server name automatically. In this version of the batch, I use <a href=\"https:\/\/www.simple-talk.com\/blogs\/2007\/07\/13\/using-bcp-to-export-the-contents-of-max-datatypes-to-a-file\/\">an old routine of mine<\/a>, <a href=\"https:\/\/www.simple-talk.com\/sql\/t-sql-programming\/the-tsql-of-text-files\/\">published elsewhere<\/a>, called <a href=\"https:\/\/www.simple-talk.com\/content\/file.ashx?file=6822\"> spSaveTextToFile<\/a> to save the text file to disk, but you can execute the batch from PowerShell to save the file locally instead, and avoid the need to have <code>xp_cmdshell <\/code>enabled on the server.<\/p>\n<p>I would feel flattered if you had a PhilFactor database on your server as I have, as you&#8217;ll see in the reference philfactor.dbo.spSaveTextToFile, but you&#8217;ll probably have to install it in your utility directory and reference it from there. Also, I&#8217;ve set the file path in the variable <code>@directory<\/code> to <code>'d:\\files\\' <\/code>(note the trailing backslash. whereas you will probably need to set it to a suitable directory on the server. As mentioned before, you may wish just to select certain tables in certain schemas so you&#8217;ll need to set the value of <code>@WildCardName<\/code> to something suitable!<\/p>\n<p>The CSS is written using the principles and some of the CSS, from this article <a href=\"https:\/\/www.simple-talk.com\/dotnet\/.net-framework\/making-html-tables-easier-on-the-eye--css-structural-pseudo-classes\/\"> Making HTML tables easier on the eye- CSS Structural Pseudo-classes<\/a>.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSet nocount on \r\n\tSET ARITHABORT ON\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\tDECLARE @WildCardName VARCHAR(150) --the wildcard to represent the tables you want\r\n\tDECLARE @Directory VARCHAR(255)--the full path of the directory where you want to store the file \r\n\t\u00a0\u00a0\u00a0 --this is the table variable containing our list of tables\r\n\tSELECT @WildCardName ='%.%', @Directory='d:\\files\\'\r\n\tDECLARE @FileNameAndPath VARCHAR(255) --the path and the file \r\n\tDECLARE @ColumnList NVARCHAR(MAX) --comma delimited list of columns\r\n\tDECLARE @x XML, @HTML VARCHAR(MAX), @Row VARCHAR(MAX), @CrLf CHAR(2)\r\n\tDECLARE @Contents NVARCHAR(MAX)\r\n\tDECLARE @Errors NVARCHAR(MAX)\r\n\tDECLARE @SQL NVARCHAR(MAX) --the dynamic SQL that we create\r\n\tDECLARE @ii INT ,@iiMax INT --the counters for our loop\r\n\tDECLARE @TheTable VARCHAR(2000) --the name of the table being documented\r\n\tDECLARE @tablesToDo TABLE(TheOrder INT IDENTITY,TheTable VARCHAR(2000))\r\n\t\u00a0\u00a0\u00a0 --if no Schema was specified, we'll assume all schemas are intended\r\n\tDECLARE @columnsToDo TABLE (FirstBadCharacter int ,name varchar(255), RedactedName varchar(255), column_ID int, Is_Assembly_Type bit )\r\n\t\r\n\tIF PARSENAME(@WildCardName,2) IS NULL \r\n\t\u00a0\u00a0\u00a0 SELECT @WildCardName='%.'+@WildCardName;\r\n\t\u00a0\u00a0\u00a0 --now we fetch all the table names into the table\r\n\tINSERT INTO @tablesToDo (TheTable) --insert the names in order into the table\r\n\t\u00a0\u00a0\u00a0 SELECT QUOTENAME(schema_name([schema_ID]))+'.'+QUOTENAME(name) \r\n\t\u00a0\u00a0\u00a0 FROM sys.tables\r\n\t\u00a0\u00a0\u00a0 WHERE name LIKE PARSENAME(@WildCardName,1)--the table name\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND schema_name([schema_ID]) LIKE PARSENAME(@WildCardName,2)--the schema\r\n\t\u00a0\u00a0\u00a0 ORDER BY schema_name([schema_ID]),name; --order by schema, followed by name\u00a0\u00a0 \r\n\t\u00a0\u00a0\u00a0 --nothing found? We warn the user and abort.\r\n\tIF @@rowcount=0 --if we found nothing\r\n\t\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0 Select @errors='&lt;H4&gt;No such table like '''+@WildCardName+''' in this database&lt;\/H4&gt;';\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --return 1\r\n\t\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 --and we loop through each table, creating the HTML table that shows the first three rows.\r\n\tSELECT\u00a0 @CrLf=CHAR(13)+CHAR(10), @Contents='', @HTML='', @ii=1,@iiMax=MAX(TheOrder) \r\n\t\u00a0\u00a0\u00a0 FROM @tablesToDo;\r\n\tWHILE @ii&lt;=@iiMax\r\n\t\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the name and schema of the next table to do\r\n\t\u00a0\u00a0\u00a0 SELECT @TheTable=TheTable,@ii=@ii+1 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @tablesToDo \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE TheOrder=@ii;\r\n\t\u00a0\u00a0\u00a0 SELECT @Contents=@Contents+'&lt;li&gt;&lt;a href=\"#table'+CONVERT(VARCHAR(5),@ii)+'\"&gt;'+@TheTable+'&lt;\/a&gt;&lt;\/li&gt;'+@CrLf;\r\n\t\u00a0\u00a0\u00a0 \/* get the name of the column and take out any problem characters for XML and HTML *\/\r\n\t\u00a0\u00a0\u00a0 Delete from @columnsToDo\u00a0 \r\n\t\u00a0\u00a0\u00a0 insert into @columnsToDo (FirstBadCharacter,name,RedactedName,Column_ID,is_assembly_type)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sELECT Patindex('%[^a-zA-Z_0-9]%',sys.columns.NAME COLLATE Latin1_General_CI_AI),sys.columns.Name,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sys.columns.Name,Column_ID,is_assembly_type\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 FROM sys.columns \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inner join sys.types \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 on sys.columns.user_type_id =sys.types.user_type_id\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE OBJECT_NAME([object_id]) LIKE PARSENAME(@TheTable,1)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND object_schema_name([object_ID]) LIKE PARSENAME(@TheTable,2)\r\n\t\u00a0\u00a0\u00a0 while exists (Select * from @columnsToDo where FirstBadCharacter&gt;0)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Begin\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 update @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Set RedactedName= stuff(RedactedName,FirstBadCharacter,1,'_') where FirstBadCharacter&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 update @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Set FirstBadCharacter=Patindex('%[^a-zA-Z_0-9]%',RedactedName COLLATE Latin1_General_CI_AI)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 where FirstBadCharacter&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 end\r\n\t\u00a0\u00a0\u00a0 SELECT @ColumnList=STUFF((\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT ','+case when is_assembly_type=1 then 'CONVERT(VARCHAR(2000),['+name+']) AS ['+RedactedName+'] '\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else '['+name+'] AS ['+RedactedName+'] ' end\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY column_ID\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH (''), TYPE).value('.', 'varchar(max)') ,1,1,'');\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the top three rows (meaningless as we haven't specified the order) as XML\r\n\t\u00a0\u00a0\u00a0 SELECT @SQL=N'Select @TheXML=((Select top 3 '+@columnList+' from '+@TheTable\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 +' for XML path, ELEMENTS XSINIL, root))'\r\n\t\u00a0\u00a0\u00a0 EXECUTE sp_ExecuteSQL\u00a0 @statement =\u00a0 @SQL,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @params = N'@TheXML XML\u00a0 OUTPUT',\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @TheXML = @x output\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we do the TABLE tag in HTML with the name of the table and the caption\r\n\t\u00a0\u00a0\u00a0 SELECT @HTML=@HTML+'&lt;table id=\"table\"\"'+CONVERT(VARCHAR(5),@ii)+'\" class=\"tablecontents\" border=\"1\" \r\n\tsummary=\"first three rows in table'''+@TheTable+'''\"&gt;\r\n\t&lt;caption&gt;'+@TheTable+'&lt;\/caption&gt;\r\n\t&lt;thead&gt;\r\n\t&lt;tr&gt;\r\n\t',@Row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --if no data there then we just give the column names taken from the system tables.\r\n\t\u00a0\u00a0\u00a0 IF @X IS NULL --no XML output from the executed batch\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN --just adding in all the column names in the header\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @HTML=@html+'&lt;th&gt;'+name+'&lt;\/th&gt;'+@CrLf \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY column_ID\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @HTML=@HTML+'&lt;\/tr&gt;&lt;\/thead&gt;'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 ELSE--it was valid XML result so there was data.\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the heading line for the column names\u00a0 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 @HTML=@HTML+'&lt;th&gt;'+[x].value('local-name(.)', 'varchar(100)')+'&lt;\/th&gt;',--+@CrLf,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @Row=@row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'--+@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[1]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --and add it to the table\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;\/tr&gt;&lt;\/thead&gt;'+@CrLf+'&lt;tbody&gt;&lt;tr&gt;'+@Row+'&lt;\/tr&gt;'+@CrLf, @row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we collect the data from\u00a0 any second row\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @Row=@Row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'--+@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[2]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --if there was a second row we add it.\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @@Rowcount&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;tr&gt;'+@Row+'&lt;\/tr&gt;'+@CrLf, @row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we get the third row if there is one\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @Row=@Row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'-- +@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[3]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @@Rowcount&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+@CrLf+'&lt;tr&gt;'+@Row+'&lt;\/tr&gt;',@row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;\/tbody&gt;&lt;\/table&gt;'\r\n\t\u00a0\u00a0\u00a0 END\r\n\t\/* now we have all the data we need, we turn it into an HTML page merely by adding the CSS and the basic page elements *\/\u00a0\u00a0\u00a0 \r\n\tSELECT @HTML='&lt;!DOCTYPE html&gt;\r\n\t&lt;html&gt;\r\n\t&lt;head&gt;\r\n\t&lt;title&gt;'+@@Servername+'-' +DB_NAME()+'&lt;\/title&gt;\r\n\t&lt;\/head&gt;\r\n\t&lt;style&gt;\r\n\t&lt;!--\r\n\t\r\n\t.columnar { columns: 4; -moz-column-width: 15em; -webkit-column-width:15em; column-width: 15em; }\r\n\t\r\n\t.thetables { }\r\n\t\r\n\t\/* do the basic style for the entire table *\/\r\n\t.thetables table {\r\n\t\u00a0\u00a0 border-collapse: collapse;\r\n\t\u00a0\u00a0 border: none ;\r\n\t\u00a0\u00a0 font: 11px Verdana, Geneva, Arial, Helvetica, sans-serif;\r\n\t\u00a0\u00a0 color: black;\r\n\t\u00a0\u00a0 margin-left:20px;\r\n\t\u00a0\u00a0 margin-top: 20px;\r\n\t\r\n\t\u00a0\u00a0\u00a0 }\r\n\t\/*attach the styles to the caption of the table *\/\r\n\t.thetables table caption {\r\n\t\u00a0 font-weight: bold;\r\n\t\u00a0 text-align:left;\r\n\t\u00a0 padding-left:5px;\r\n\t\u00a0 background-color: #f3f3f3;\r\n\t}\r\n\t\u00a0\u00a0 \r\n\t\/*give every cell the same style of border *\/\r\n\t.thetables table td, .thetables table th, .thetables table caption { border: 1px solid #bbbde1\u00a0 ;\u00a0 vertical-align: top;\u00a0 }\r\n\t\/* apply styles to the odd headers *\/\r\n\t.thetables table th:nth-child(odd) { background-color: #cedfe2; }\r\n\t\/* apply styles to the even headers *\/\r\n\t.thetables table tr th:nth-child(even) { background-color: #dfebee; }\r\n\t\u00a0\/* apply styles to the even rows *\/\r\n\t.thetables table td {background-color: #f0f7f9;}\r\n\t\r\n\t.thetables table tr:nth-child(even) td:nth-child(odd){background-color: #f7fafb;\u00a0 }\r\n\t\u00a0\/* apply styles to the even colums of odd rows *\/\r\n\t.thetables table tr:nth-child(odd) td:nth-child(even){ background-color: #f7fafb; }\r\n\t\r\n\th1, ol { color: #000000; text-align: left; font: normal 11px Verdana, Geneva, Arial, Helvetica, sans-serif; }\r\n\t\r\n\th1 { font-size: 16px; font-weight: bold; color: #000000; text-align: left; }\r\n\t--&gt;\r\n\t&lt;\/style&gt;\r\n\t\r\n\t&lt;body&gt;\r\n\t&lt;h1&gt;Sample of contents of tables in '+@@Servername+'-'+DB_NAME()+'&lt;\/h1&gt;\r\n\t&lt;div class=\"columnar\"&gt;&lt;ol&gt;'+@Contents+'&lt;\/ol&gt;&lt;\/div&gt;\r\n\t&lt;div class=\"thetables\"&gt;\u00a0\u00a0\u00a0 \r\n\t'\u00a0\u00a0\u00a0 +Coalesce(@HTML,'')+coalesce(@errors,'')+'\r\n\t&lt;\/div&gt;\r\n\t&lt;\/body&gt;\r\n\t&lt;\/html&gt;'\r\n\t\/* and now we write out the result *\/\r\n\tSELECT @FileNameAndPath=@Directory+REPLACE(REPLACE(REPLACE(@@Servername+'-'+DB_NAME(),'\/',''),'\\',''),':','')+'.html'\r\n\tEXECUTE philfactor.dbo.spSaveTextToFile @html,@FileNameAndPath\u00a0 \r\n\t\u00a0\r\n<\/pre>\n<h2>Putting It In PowerShell<\/h2>\n<p>I us a PowerShell routine that uses the function <code>'Foreach-DatabaseInServers<\/code>&#8216; that I published a couple of years ago here &#8216;<a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/powershell-smo-just-writing-things-once\/\">PowerShell SMO: Just Writing Things Once&#8217;<\/a>. This routine is very close to what I&#8217;ve done in the previous section except for the method of collecting the HTML file and the name of the file to save it in.<\/p>\n<p>This routine allows me to specify what databases I&#8217;d like processed on these servers and which of the development servers I want to access.<\/p>\n<p>To run this, the function must be on a file in the same directory as the PowerShell file and if you are running it in the IDE, then the IDE must be on the same directory as well.<\/p>\n<p>There isn&#8217;t much PowerShell in this routine . It is mostly the same SQL you&#8217;ve just been staring at in admiration (gulp) in the beginning of the article. I&#8217;ve done this to show how easy it is to execute SQL on a number of databases in a number of servers, and get the results back.<\/p>\n<h2>Getting it running<\/h2>\n<p>Now, before you run this, makes sure that,&#8217; unless you happen to have servers called &#8216;Shem&#8217;, &#8216;Ham&#8217; and &#8216;Japeth&#8217;, you will need to change the line<\/p>\n<pre>$servers=@('Shem','Ham','Japeth')#Specify the list of servers you want to use<\/pre>\n<p>to something else.<\/p>\n<p>I always use my <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb934126.aspx\">Central Management Server groups<\/a>, rather than do lots of handmade lists of my servers (fortunately they are all within the domain so I can use Windows Security)<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">Import-Module sqlps -DisableNameChecking #load the SQLPS functionality for getting the registered servers\r\n#get a list of the servers we want to scan\r\n$servers= dir 'SQLSERVER:\\sqlregistration\\Database Engine Server Group' | foreach-object{$_.name}\r\n\r\n<\/pre>\n<p>You will also need to change the &#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">$LocalDirectory='E:\\MyDatabaseTables\\'#the path to the local directory where you want to store it<\/pre>\n<p>&#8230; to point to a valid directory. You might\u00a0 also want to change or remove the database filter &#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">-TheDatabaseFilter\u00a0 {param($x); if ($x.name -like'*'){$x}}<\/pre>\n<p>&#8230; If\u00a0 you only want to list out Adventureworks databases it would be &#8216;ADV*&#8217;. ( you can use this to exclude databases)<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">\t. \".\\ForAllDatabaseServers.ps1\" # pull in our pipeline function 'Foreach-DatabaseInServers'\r\n\t$servers=@('Shem','Ham','Japeth') #Specify the list of servers you want to use\r\n\t$TableWildcard='%.%'#Which schema and tables do you want to do (%.% means all tables from all schema\r\n\t$LocalDirectory='E:\\Tables\\'#the path to the local directory where you want to store it\r\n\t$SQL=@\"\r\n\tSet nocount on\r\n\tSET ARITHABORT ON\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\tDECLARE @WildCardName VARCHAR(150) --the wildcard to represent the tables you want\r\n\tDECLARE @Directory VARCHAR(255)--the full path of the directory where you want to store the file \r\n\t\u00a0\u00a0\u00a0 --this is the table variable containing our list of tables\r\n\tSELECT @WildCardName ='$TableWildcard', @Directory='$LocalDirectory'\r\n\tDECLARE @FileNameAndPath VARCHAR(255) --the path and the file \r\n\tDECLARE @ColumnList NVARCHAR(MAX) --comma delimited list of columns\r\n\tDECLARE @x XML, @HTML VARCHAR(MAX), @Row VARCHAR(MAX), @CrLf CHAR(2)\r\n\tDECLARE @Contents NVARCHAR(MAX)\r\n\tDECLARE @Errors NVARCHAR(MAX)\r\n\tDECLARE @SQL NVARCHAR(MAX) --the dynamic SQL that we create\r\n\tDECLARE @ii INT ,@iiMax INT --the counters for our loop\r\n\tDECLARE @TheTable VARCHAR(2000) --the name of the table being documented\r\n\tDECLARE @tablesToDo TABLE(TheOrder INT IDENTITY,TheTable VARCHAR(2000))\r\n\t\u00a0\u00a0\u00a0 --if no Schema was specified, we'll assume all schemas are intended\r\n\tDECLARE @columnsToDo TABLE (FirstBadCharacter int ,name varchar(255), RedactedName varchar(255), column_ID int, Is_Assembly_Type bit )\r\n\t\r\n\tIF PARSENAME(@WildCardName,2) IS NULL \r\n\t\u00a0\u00a0\u00a0 SELECT @WildCardName='%.'+@WildCardName;\r\n\t\u00a0\u00a0\u00a0 --now we fetch all the table names into the table\r\n\tINSERT INTO @tablesToDo (TheTable) --insert the names in order into the table\r\n\t\u00a0\u00a0\u00a0 SELECT QUOTENAME(schema_name([schema_ID]))+'.'+QUOTENAME(name) \r\n\t\u00a0\u00a0\u00a0 FROM sys.tables\r\n\t\u00a0\u00a0\u00a0 WHERE name LIKE PARSENAME(@WildCardName,1)--the table name\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND schema_name([schema_ID]) LIKE PARSENAME(@WildCardName,2)--the schema\r\n\t\u00a0\u00a0\u00a0 ORDER BY schema_name([schema_ID]),name; --order by schema, followed by name\u00a0\u00a0 \r\n\t\u00a0\u00a0\u00a0 --nothing found? We warn the user and abort.\r\n\tIF @@rowcount=0 --if we found nothing\r\n\t\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0 Select @errors='&lt;H4&gt;No such table like '''+@WildCardName+''' in this database&lt;\/H4&gt;';\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --return 1\r\n\t\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 --and we loop through each table, creating the HTML table that shows the first three rows.\r\n\tSELECT\u00a0 @CrLf=CHAR(13)+CHAR(10), @Contents='', @HTML='', @ii=1,@iiMax=MAX(TheOrder) \r\n\t\u00a0\u00a0\u00a0 FROM @tablesToDo;\r\n\tWHILE @ii&lt;=@iiMax\r\n\t\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the name and schema of the next table to do\r\n\t\u00a0\u00a0\u00a0 SELECT @TheTable=TheTable,@ii=@ii+1 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @tablesToDo \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE TheOrder=@ii;\r\n\t\u00a0\u00a0\u00a0 SELECT @Contents=@Contents+'&lt;li&gt;&lt;a href=\"#table'+CONVERT(VARCHAR(5),@ii)+'\"&gt;'+@TheTable+'&lt;\/a&gt;&lt;\/li&gt;'+@CrLf;\r\n\t\u00a0\u00a0\u00a0 \/* get the name of the column and take out any problem characters for XML and HTML *\/\r\n\t\u00a0\u00a0\u00a0 Delete from @columnsToDo\u00a0 \r\n\t\u00a0\u00a0\u00a0 insert into @columnsToDo (FirstBadCharacter,name,RedactedName,Column_ID,is_assembly_type)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sELECT Patindex('%[^a-zA-Z_0-9]%',sys.columns.NAME COLLATE Latin1_General_CI_AI),sys.columns.Name,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sys.columns.Name,Column_ID,is_assembly_type\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 FROM sys.columns \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inner join sys.types \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 on sys.columns.user_type_id =sys.types.user_type_id\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE OBJECT_NAME([object_id]) LIKE PARSENAME(@TheTable,1)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND object_schema_name([object_ID]) LIKE PARSENAME(@TheTable,2)\r\n\t\u00a0\u00a0\u00a0 while exists (Select * from @columnsToDo where FirstBadCharacter&gt;0)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Begin\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 update @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Set RedactedName= stuff(RedactedName,FirstBadCharacter,1,'_') where FirstBadCharacter&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 update @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Set FirstBadCharacter=Patindex('%[^a-zA-Z_0-9]%',RedactedName COLLATE Latin1_General_CI_AI)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 where FirstBadCharacter&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 end\r\n\t\u00a0\u00a0\u00a0 SELECT @ColumnList=STUFF((\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT ','+case when is_assembly_type=1 then 'CONVERT(VARCHAR(2000),['+name+']) AS ['+RedactedName+'] '\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else '['+name+'] AS ['+RedactedName+'] ' end\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY column_ID\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH (''), TYPE).value('.', 'varchar(max)') ,1,1,'');\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the top three rows (meaningless as we haven't specified the order) as XML\r\n\t\u00a0\u00a0\u00a0 SELECT @SQL=N'Select @TheXML=((Select top 3 '+@columnList+' from '+@TheTable\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 +' for XML path, ELEMENTS XSINIL, root))'\r\n\t\u00a0\u00a0\u00a0 EXECUTE sp_ExecuteSQL\u00a0 @statement =\u00a0 @SQL,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @params = N'@TheXML XML\u00a0 OUTPUT',\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @TheXML = @x output\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we do the TABLE tag in HTML with the name of the table and the caption\r\n\t\u00a0\u00a0\u00a0 SELECT @HTML=@HTML+'&lt;table id=\"table\"\"'+CONVERT(VARCHAR(5),@ii)+'\" class=\"tablecontents\" border=\"1\" \r\n\tsummary=\"first three rows in table'''+@TheTable+'''\"&gt;\r\n\t&lt;caption&gt;'+@TheTable+'&lt;\/caption&gt;\r\n\t&lt;thead&gt;\r\n\t&lt;tr&gt;\r\n\t',@Row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --if no data there then we just give the column names taken from the system tables.\r\n\t\u00a0\u00a0\u00a0 IF @X IS NULL --no XML output from the executed batch\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN --just adding in all the column names in the header\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @HTML=@html+'&lt;th&gt;'+name+'&lt;\/th&gt;'+@CrLf \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @columnsToDo\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY column_ID\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @HTML=@HTML+'&lt;\/tr&gt;&lt;\/thead&gt;'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 ELSE--it was valid XML result so there was data.\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --get the heading line for the column names\u00a0 \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 @HTML=@HTML+'&lt;th&gt;'+[x].value('local-name(.)', 'varchar(100)')+'&lt;\/th&gt;',--+@CrLf,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @Row=@row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'--+@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[1]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --and add it to the table\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;\/tr&gt;&lt;\/thead&gt;'+@CrLf+'&lt;tbody&gt;&lt;tr&gt;'+@Row+'&lt;\/tr&gt;'+@CrLf, @row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we collect the data from\u00a0 any second row\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @Row=@Row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'--+@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[2]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --if there was a second row we add it.\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @@Rowcount&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;tr&gt;'+@Row+'&lt;\/tr&gt;'+@CrLf, @row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 --now we get the third row if there is one\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @Row=@Row+'&lt;td&gt;'+REPLACE(REPLACE(COALESCE([x].value('text()[1]','varchar(100)'),'NULL'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')+'&lt;\/td&gt;'-- +@CrLf\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM @x.nodes('root\/row[3]\/*')\u00a0 AS a(x)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @@Rowcount&gt;0\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @html=@HTML+@CrLf+'&lt;tr&gt;'+@Row+'&lt;\/tr&gt;',@row=''\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\t\u00a0\u00a0\u00a0 SELECT @html=@HTML+'&lt;\/tbody&gt;&lt;\/table&gt;'\r\n\t\u00a0\u00a0\u00a0 END\r\n\t\/* now we have all the data we need, we turn it into an HTML page merely by adding the CSS and the basic page elements *\/\u00a0\u00a0\u00a0 \r\n\tSELECT @HTML='&lt;!DOCTYPE html&gt;\r\n\t&lt;html&gt;\r\n\t&lt;head&gt;\r\n\t&lt;title&gt;'+@@Servername+'-' +DB_NAME()+'&lt;\/title&gt;\r\n\t&lt;\/head&gt;\r\n\t&lt;style&gt;\r\n\t&lt;!--\r\n\t\r\n\t.columnar { columns: 4; -moz-column-width: 15em; -webkit-column-width:15em; column-width: 15em; }\r\n\t\r\n\t.thetables { }\r\n\t\r\n\t\/* do the basic style for the entire table *\/\r\n\t.thetables table {\r\n\t\u00a0 \u00a0border-collapse: collapse;\r\n\t\u00a0\u00a0 border: none ;\r\n\t\u00a0\u00a0 font: 11px Verdana, Geneva, Arial, Helvetica, sans-serif;\r\n\t\u00a0\u00a0 color: black;\r\n\t\u00a0\u00a0 margin-left:20px;\r\n\t\u00a0\u00a0 margin-top: 20px;\r\n\t\r\n\t\u00a0\u00a0\u00a0 }\r\n\t\/*attach the styles to the caption of the table *\/\r\n\t.thetables table caption {\r\n\t\u00a0 font-weight: bold;\r\n\t\u00a0 text-align:left;\r\n\t\u00a0 padding-left:5px;\r\n\t\u00a0 background-color: #f3f3f3;\r\n\t}\r\n\t\u00a0\u00a0 \r\n\t\/*give every cell the same style of border *\/\r\n\t.thetables table td, .thetables table th, .thetables table caption { border: 1px solid #bbbde1\u00a0 ;\u00a0 vertical-align: top;\u00a0 }\r\n\t\/* apply styles to the odd headers *\/\r\n\t.thetables table th:nth-child(odd) { background-color: #cedfe2; }\r\n\t\/* apply styles to the even headers *\/\r\n\t.thetables table tr th:nth-child(even) { background-color: #dfebee; }\r\n\t\u00a0\/* apply styles to the even rows *\/\r\n\t.thetables table td {background-color: #f0f7f9;}\r\n\t\r\n\t.thetables table tr:nth-child(even) td:nth-child(odd){background-color: #f7fafb;\u00a0 }\r\n\t\u00a0\/* apply styles to the even colums of odd rows *\/\r\n\t.thetables table tr:nth-child(odd) td:nth-child(even){ background-color: #f7fafb; }\r\n\t\r\n\th1, ol { color: #000000; text-align: left; font: normal 11px Verdana, Geneva, Arial, Helvetica, sans-serif; }\r\n\t\r\n\th1 { font-size: 16px; font-weight: bold; color: #000000; text-align: left; }\r\n\t--&gt;\r\n\t&lt;\/style&gt;\r\n\t\r\n\t&lt;body&gt;\r\n\t&lt;h1&gt;Sample of contents of tables in '+@@Servername+'-'+DB_NAME()+'&lt;\/h1&gt;\r\n\t&lt;div class=\"columnar\"&gt;&lt;ol&gt;'+@Contents+'&lt;\/ol&gt;&lt;\/div&gt;\r\n\t&lt;div class=\"thetables\"&gt;\u00a0\u00a0\u00a0 \r\n\t'\u00a0\u00a0\u00a0 +Coalesce(@HTML,'')+coalesce(@errors,'')+'\r\n\t&lt;\/div&gt;\r\n\t&lt;\/body&gt;\r\n\t&lt;\/html&gt;'\r\n\t\/* and now we write out the result *\/\r\n\t\r\n\t--SELECT @FileNameAndPath=@Directory+REPLACE(REPLACE(REPLACE(@@Servername+'-'+DB_NAME(),'\/',''),'\\',''),':','')+'.html'\r\n\t--EXECUTE philfactor.dbo.spSaveTextToFile @html,@FileNameAndPath\u00a0 \r\n\tSelect @html as TheHTML, @Directory+REPLACE(REPLACE(REPLACE(@@Servername+'-'+DB_NAME(),'\/',''),'\\',''),':','')+'.html' as filename\r\n\t\r\n\t\"@\r\n\t\r\n\t&lt;#So, we can make it call some sql and get back a result. In this case we are only looking at the various AdventureWorks databases in all the servers, just to illustrate the different filters you can specify.#&gt;\r\n\tForeach-DatabaseInServers $servers -verbose -TheDatabaseFilter\u00a0 { param($x); if ($x.name -like '*'){$x}} -jobToDo {\r\n\t\u00a0\u00a0 param($database)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $result=$database.ExecuteWithResults($SQL) #execute the SQL\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $result.Tables[0]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } |\u00a0 select-object ('TheHTML','filename') | &amp; {PROCESS{ $_.TheHTML\u00a0 &gt;$_.filename }}\u00a0 \r\n<\/pre>\n<h2>Conclusions<\/h2>\n<p>This is a demonstration of the type of routine that some DBAs or Database Developers will find very handy, though it will probably not be universal enough to embed in a commercial tool. It is the sort of script-based tool that most of us working on SQL Server carry around on thumbdrives and which generally exist in slightly tatty states. PowerShell has made it so much easier to use custom scripts like this, and has freed us from much of the tedium of having to use .NET languages like C#, VB or Python to do the automation side.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>From SQL Server Management Studio it is difficult to look through the first few rows of a whole lot of tables in a database. This is odd, since it is a great way to get quickly familiar with a database. Phil was persuaded to tidy up a SQL routine he uses to investigate databases quickly in a browser. He explains how to use it, how it works, and how to use it from PowerShell.&hellip;<\/p>\n","protected":false},"author":154613,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4168,4635,4150,4151,4252],"coauthors":[6813],"class_list":["post-1852","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-database","tag-powershell","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1852","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\/154613"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1852"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1852\/revisions"}],"predecessor-version":[{"id":71941,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1852\/revisions\/71941"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1852"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1852"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1852"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1852"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}