<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Dealing with the Microsoft Data Platform</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/johannh/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/johannh/feed.xml</link>
    <description>Some notes from the field as consultant in Data Insights</description>
    <lastBuildDate>Tue, 16 Apr 2019 06:09:28 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Reverse string in Excel using array formula and without VBA</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/reverse-string-in-excel-using-array-formula-and-without-vba</link>
      <pubDate>Thu, 22 Feb 2018 19:13:33 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=385</guid>
      <description><![CDATA[Being in the middle of something, suddenly the need to reverse a string in Excel arose.
Using the...]]></description>
      <content:encoded><![CDATA[Being in the middle of something, suddenly the need to reverse a string in Excel arose.
Using the reversed string, it is easy to get the position of a comma before another string.
This time, I wanted to reverse the string without using VBA and its StrReverse-Function.
<h1>How it looks like: array formula</h1>
Reverse content of cell A2 with array formula (&lt;ctrl&gt;+&lt;shift&gt;+&lt;enter&gt; instead of &lt;enter&gt; after entering the formula).

German Syntax:
<pre>=WENN(A2="";"";TEXTKETTE(TEIL(A2;1 + LÄNGE(A2)-ZEILE(INDIREKT("1:" &amp; LÄNGE(A2)));1)))</pre>
English Syntax:
<pre>=IF(A2="";"";CONCAT(MID(A2;1 + LEN(A2)-ROW(INDIRECT("1:" &amp; LEN(A2)));1)))</pre>
<h1>How it works</h1>
The formula concatenates all characters of the string, starting at the last one till the first one.
<h2>Basic Structure</h2>
<pre>=IF(A2="";"";&lt;Do the work...&gt;)</pre>
If the string is empty, no need to reverse something.
<h2>Do the actual work</h2>
Details of the non-empty part:
<pre>CONCAT(
 MID(A2;1 + LEN(A2) - ROW(INDIRECT("1:" &amp; LEN(A2))); 1)
 )</pre>
Steps:
<ol>
 	<li>Get length of string</li>
 	<li>Create counter using a range "1:x", whereas "x" contains the length of the string</li>
 	<li>Get each single character, starting at the last one at position
<ul>
 	<li>character at LEN(A2), which is the last character: MID(A2; 1 + [LEN(A2)] - 1; 1) =&gt; MID(A2; [LEN(A2)]; 1)</li>
 	<li>character at LEN(A2) - 1: MID(A2; 1 + [LEN(A2)] - 2; 1)</li>
 	<li>...</li>
 	<li>first character of A2: MID(A2; 1 + [LEN(A2)] - [LEN(A2)]; 1) =&gt; MID(A2; 1, 1)</li>
</ul>
</li>
 	<li>Concatenate the characters again</li>
</ol>
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://support.office.com/en-us/article/create-an-array-formula-e43e12e0-afc6-4a12-bc7f-48361075954d">Create an array formula</a></li>
 	<li><a href="https://support.office.com/en-us/article/concat-function-9b1a9a3f-94ff-41af-9736-694cbd6b4ca2?ui=en-US&amp;rs=en-US&amp;ad=US">Excel CONCAT function</a></li>
 	<li><a href="https://support.office.com/en-us/article/mid-midb-functions-d5f9e25c-d7d6-472e-b568-4ecb12433028">Excel MID function</a></li>
 	<li><a href="https://support.office.com/en-us/article/len-lenb-functions-29236f94-cedc-429d-affd-b5e33d2c67cb">Excel LEN function</a></li>
 	<li><a href="https://support.office.com/en-us/article/row-function-3a63b74a-c4d0-4093-b49a-e76eb49a6d8d">Excel ROW function</a></li>
 	<li><a href="https://support.office.com/en-us/article/indirect-function-474b3a3a-8a26-4f44-b491-92b6306fa261">Excel INDIRECT function</a></li>
 	<li><a href="https://support.office.com/en-us/article/if-function-69aed7c9-4e8a-4755-a9bc-aa8bbff73be2">Excel IF function</a></li>
 	<li><a href="https://msdn.microsoft.com/en-us/vba/language-reference-vba/articles/strreverse-function">VBA: StrReverse-Function</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>SQL Server: Compare row count local and remote for Oracle Linked-Server-Connection</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/sql-server-compare-row-count-local-and-remote-for-oracle-linked-server-connection</link>
      <pubDate>Fri, 06 Oct 2017 13:47:52 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=375</guid>
      <description><![CDATA[As part of an Oracle2SQL-Project, it was required to compare the row count of the destination tables...]]></description>
      <content:encoded><![CDATA[As part of an Oracle2SQL-Project, it was required to compare the row count of the destination tables stored in SQL Server and the source tables stored in Oracle. Using transactional replication, the data is continuously replicated from Oracle to SQL Server. Doing so, it is possible to implement a step-by-step-migration, migrating the read-only consumers to SQL Server first.
<h1>Result</h1>
Statistic about the tables in the current database with different row count compared to an Oracle Database, accessible using Linked Server:
<ul>
 	<li>ObjectId =&gt; for the interested reader, to query some other metadata afterwards</li>
 	<li>TableName =&gt; Table- and Schema-Name: [Schema].[Table]</li>
 	<li>RowCountMS =&gt; Number of rows in SQL Server</li>
 	<li>RowCountOracle =&gt; Number of rows in Oracle, or -1 if not found</li>
</ul>
<h1>T-SQL</h1>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em">
<pre style="margin: 0;line-height: 125%"><span style="color: #888888">-- ### Replace "MY.REMOTE.ORACLE.DATABASE" with the actual linked server name</span>

<span style="color: #888888">-- ### Prepare stats</span>
IF OBJECT_ID(<span style="background-color: #fff0f0">'tempdb..#TableStats'</span>) <span style="color: #008800;font-weight: bold">IS</span> <span style="color: #008800;font-weight: bold">NOT</span> <span style="color: #008800;font-weight: bold">NULL</span> <span style="color: #008800;font-weight: bold">DROP</span> <span style="color: #008800;font-weight: bold">TABLE</span> <span style="color: #333333">#</span>TableStats;

<span style="color: #008800;font-weight: bold">CREATE</span> <span style="color: #008800;font-weight: bold">TABLE</span> <span style="color: #333333">#</span>TableStats (
	ObjectId <span style="color: #007020">int</span>,
	TableName <span style="color: #007020">varchar</span>(<span style="color: #0000dd;font-weight: bold">100</span>),
	RowCountMS <span style="color: #007020">int</span>,
	RowCountOracle <span style="color: #007020">int</span>,
);

<span style="color: #888888">-- ### Gather stats</span>
PRINT <span style="background-color: #fff0f0">'Gathering table stats...'</span>
<span style="color: #008800;font-weight: bold">DECLARE</span> <span style="color: #333333">@</span>SQLCommand nvarchar(<span style="color: #0000dd;font-weight: bold">2000</span>) <span style="color: #333333">=</span> <span style="background-color: #fff0f0">'</span>
<span style="background-color: #fff0f0">	DECLARE @Pos AS int = CHARINDEX(''.'', ''?'');</span>
<span style="background-color: #fff0f0">	DECLARE @Schema sysname = SUBSTRING(''?'', 2, @Pos - 3);</span>
<span style="background-color: #fff0f0">	DECLARE @Table sysname = SUBSTRING(''?'', @Pos + 2, LEN(''?'') - @Pos - 2);</span>
<span style="background-color: #fff0f0">	DECLARE @SQLOpenQuery nvarchar(1000) = </span>
<span style="background-color: #fff0f0">		N''DECLARE @x AS int; SELECT @x=1 FROM OPENQUERY([MY.REMOTE.ORACLE.DATABASE], ''''SELECT 1 FROM all_objects WHERE OWNER = '''''''''' + @Schema + '''''''''' AND OBJECT_NAME = '''''''''' + @Table + '''''''''''''')''</span>
<span style="background-color: #fff0f0">	DECLARE @SQLUpdate nvarchar(1000) = </span>
<span style="background-color: #fff0f0">		N''UPDATE #TableStats SET RowCountOracle = (SELECT vOracle.RowCountOracle FROM OPENQUERY([MY.REMOTE.ORACLE.DATABASE], ''''SELECT COUNT(*) AS RowCountOracle FROM '' + @Schema + ''.'' + @Table + '''''') AS vOracle) WHERE TableName = ''''?'''''';</span>
<span style="background-color: #fff0f0">	-- PRINT @SQLUpdate</span>
<span style="background-color: #fff0f0">	PRINT ''?...''</span>

<span style="background-color: #fff0f0">	INSERT INTO #TableStats (ObjectId, TableName, RowCountMS, RowCountOracle)</span>
<span style="background-color: #fff0f0">	SELECT OBJECT_ID(''?''), ''?'', (SELECT COUNT(*) FROM ?), -1;</span>

<span style="background-color: #fff0f0">	EXEC sp_executeSQL @SQLOpenQuery;</span>
<span style="background-color: #fff0f0">	IF @@RowCount &gt; 0 </span>
<span style="background-color: #fff0f0">	BEGIN</span>
<span style="background-color: #fff0f0">		EXEC sp_executeSQL @SQLUpdate;</span>
<span style="background-color: #fff0f0">	END			</span>
<span style="background-color: #fff0f0">'</span>
PRINT <span style="background-color: #fff0f0">'EXEC sp_MSforeachtable using the following command:'</span>
PRINT <span style="color: #333333">@</span>SQLCommand;
<span style="color: #008800;font-weight: bold">EXEC</span> sp_MSforeachtable <span style="color: #333333">@</span>SQLCommand;
PRINT <span style="background-color: #fff0f0">'Finished!'</span>

<span style="color: #888888">-- ### Print stats, RowCountOracle = -1 =&gt; Table doesn't exist in Oracle</span>
<span style="color: #008800;font-weight: bold">SELECT</span> <span style="color: #333333">*</span> 
<span style="color: #008800;font-weight: bold">FROM</span> <span style="color: #333333">#</span>TableStats <span style="color: #008800;font-weight: bold">AS</span> s
<span style="color: #008800;font-weight: bold">WHERE</span> 
<span style="color: #888888">-- No SSMA-Objects</span>
s.TableName <span style="color: #008800;font-weight: bold">NOT</span> <span style="color: #008800;font-weight: bold">LIKE</span> <span style="background-color: #fff0f0">'%ssma_oracle%'</span>
<span style="color: #888888">-- only tables with primary keys</span>
<span style="color: #008800;font-weight: bold">AND</span> <span style="color: #008800;font-weight: bold">EXISTS</span> (
	<span style="color: #008800;font-weight: bold">SELECT</span> <span style="color: #333333">*</span>
	<span style="color: #008800;font-weight: bold">FROM</span> sys.indexes <span style="color: #008800;font-weight: bold">AS</span> idxPK
	<span style="color: #008800;font-weight: bold">WHERE</span> idxPK.object_id <span style="color: #333333">=</span> s.ObjectId
	<span style="color: #008800;font-weight: bold">AND</span> idxPK.is_primary_key <span style="color: #333333">=</span> <span style="color: #0000dd;font-weight: bold">1</span>
)
<span style="color: #888888">-- only differences</span>
<span style="color: #008800;font-weight: bold">AND</span> s.RowCountMS <span style="color: #333333">&lt;&gt;</span> s.RowCountOracle
<span style="color: #008800;font-weight: bold">ORDER</span> <span style="color: #008800;font-weight: bold">BY</span> s.TableName;
</pre>
</div>
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/linked-servers/create-linked-servers-sql-server-database-engine">SQL Server: Create Linked Servers</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql">SQL Server: OPENQUERY (T-SQL)</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/replication/sql-server-replication">SQL Server: Replication</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/ssma/sql-server-migration-assistant">SQL Server Migration Assistant</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>SQL Server Database Objects and Dependencies Report</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/sql-server-database-objects-and-dependencies-report</link>
      <pubDate>Sat, 09 Sep 2017 15:29:53 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=325</guid>
      <description><![CDATA[SQL Server exposes very much information using Dynamic Management Views and System Catalog Views. I...]]></description>
      <content:encoded><![CDATA[SQL Server exposes very much information using Dynamic Management Views and System Catalog Views. I like the idea of self-documenting systems. Manually maintained documentation tends to be outdated very fast and therefore is obsolete in many cases. What I've missed, is a report visualizing the Database Objects and Dependencies at a glance. Here we go! The result may be used in addition to the <a target="_blank" href="https://blogs.technet.microsoft.com/johannh/2017/08/25/sql-server-reporting-services-inventory-report-with-parameters-dependencies-and-permissions/" rel="noopener noreferrer">self-documenting Report Environment</a>.

The idea is, to see the objects of the current database with their dependencies and where they are referenced by others.

Additionally, I'd like to control the type of dependency: all, only cross-schema, only cross-database or only cross-server.

The result should also be neat and handy to be usable directly within SQL Server Management Studio as Custom Report with no further requirements. And on top, it should work for a current SQL Server Reporting Services Instance (2016) and for SSRS 2012.
<h1>How the result should look like</h1>
After a parameter pane, an overview with the dependencies and detailed objects and dependencies are listed:

<img width="1860" height="1046" class="alignnone size-full wp-image-345" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/Report-ResultWithoutDocumentMap.png" />

&nbsp;
<h1>Report Design</h1>
<h2>Basic Design</h2>
The basic design for the report consists of:
<ul>
 	<li>Headline</li>
 	<li>Parameter Pane, showing the three report parameters in the report result, and usable as toggle-items to refresh the report with a different parameter set</li>
 	<li>Dependencies Overview, a matrix of the result, filtered for rows containing dependencies</li>
 	<li>Details, starting with Column-Toggle-Items (Table bound to Dummy-Dataset) and the actual Detail-Table</li>
</ul>
Design View:

<img width="2461" height="733" class="alignnone size-full wp-image-346" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/Report-ReportDesign.png" />
<h2>Parameter Pane</h2>
The parameter pane is based on the Dummy-Dataset, rendering all rows as header-rows.

Cells containing the currently selected parameter are not rendered as links using a dynamic report action, e.g. "all"-Cell for ShowDependencies:
<blockquote>
<pre>=IIf(Parameters!ShowDependencies.Value = "all", Nothing, Parameters!ReportName.Value)</pre>
</blockquote>
The background color for cells containing the currently selected parameter is set to grey, e.g. cells for ShowDependencies:
<blockquote>
<pre>=IIf(Parameters!ShowDependencies.Label = me.Value, "LightGrey", "White")</pre>
</blockquote>
<h2>Overview</h2>
The overview is a matrix for all rows containing references, using the following filter condition:
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em">
<pre style="margin: 0;line-height: 125%"><span style="color: #007700">&lt;Filters&gt;</span>
	<span style="color: #007700">&lt;Filter&gt;</span>
	  <span style="color: #007700">&lt;FilterExpression&gt;</span>=Fields!ReferencedObjectName.Value<span style="color: #007700">&lt;/FilterExpression&gt;</span>
	  <span style="color: #007700">&lt;Operator&gt;</span>NotEqual<span style="color: #007700">&lt;/Operator&gt;</span>
	  <span style="color: #007700">&lt;FilterValues&gt;</span>
		<span style="color: #007700">&lt;FilterValue&gt;</span>=Nothing<span style="color: #007700">&lt;/FilterValue&gt;</span>
	  <span style="color: #007700">&lt;/FilterValues&gt;</span>
	<span style="color: #007700">&lt;/Filter&gt;</span>
<span style="color: #007700">&lt;/Filters&gt;</span></pre>
</div>
<h2>Details</h2>
The details are listed as grouped table containing object details and further columns for References and Referenced By:
<ul>
 	<li>Column-Visibility could be toggled with textboxes of separate table.</li>
 	<li>2nd Header-Row of main-table also contains a sub-table for each column with the corresponding row-headers.</li>
 	<li>Bookmark-Actions for objects mentioned as reference or referenced-by to enable direct navigation within report.</li>
</ul>
Toggle-Items:
<ul>
 	<li>Initially, the rows for all non-dbo-Schema of the current database are shown, each other rows are collapsed by default.</li>
 	<li>The reference-column is visible by default.</li>
 	<li>The referencedBy-column isn't visible by default.</li>
</ul>
<h1>Report Data Elements</h1>
The report is based on a single Data Source and some Report Parameter and Datasets.
<h2>Overview</h2>
<img width="350" height="778" class="alignnone wp-image-355" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/Report-ReportData.png" />
<h2>Report Parameter</h2>
<ol>
 	<li>ShowDependencies, filters dependencies: all / Cross-Schema / Cross-Database / Cross-Server</li>
 	<li>FilterReferencing: all / with / without</li>
 	<li>FilterReferencedBy: all / with / without</li>
 	<li>ReportName: hidden, used for report actions, executing the current report with a different parameter set.
When executed as SSMS Custom Report, the value for Globals!ReportName is not set. Therefore, this parameter is used as single point of calculation for all report actions:</li>
</ol>
<blockquote>
<pre>=IIf(Globals!ReportName = "", "SSRS-DatabaseObjectsAndDependencies", Globals!ReportName)</pre>
</blockquote>
<h1>Getting the data</h1>
I'd like to look at several aspects to gather the information for this report. The overall query is to big to be a reasonable part of this post - take a look at the report definition to get the full query.
<h2>Getting the details for foreign keys</h2>
The System Catalog View sys.foreign_keys contain the foreign keys of the current database:

<!-- HTML generated using hilite.me -->
<div style="background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em">
<pre style="margin: 0;line-height: 125%"><span style="color: #008800;font-weight: bold">SELECT</span> 
	<span style="color: #888888">-- source</span>
	<span style="color: #333333">@@</span>SERVERNAME <span style="color: #008800;font-weight: bold">AS</span> SourceServerName,
	DB_NAME() <span style="color: #008800;font-weight: bold">AS</span> SourceDatabaseName,
	OBJECT_SCHEMA_NAME(src.object_id) <span style="color: #008800;font-weight: bold">AS</span> SourceSchemaName,
	src.[<span style="color: #008800;font-weight: bold">type</span>] <span style="color: #008800;font-weight: bold">AS</span> SourceObjectType,
	src.type_desc <span style="color: #008800;font-weight: bold">AS</span> SourceObjectTypeDesc,
	src.name <span style="color: #008800;font-weight: bold">AS</span> SourceObjectName,

	<span style="color: #888888">-- destination</span>
	<span style="color: #333333">@@</span>SERVERNAME <span style="color: #008800;font-weight: bold">AS</span> DestinationServerName,
	DB_NAME() <span style="color: #008800;font-weight: bold">AS</span> DestinationDatabaseName,
	OBJECT_SCHEMA_NAME(dest.object_id) <span style="color: #008800;font-weight: bold">AS</span> DestinationSchemaName, 
	dest.[<span style="color: #008800;font-weight: bold">type</span>] <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectType,
	dest.type_desc <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectTypeDesc,
	dest.name <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectName
<span style="color: #008800;font-weight: bold">FROM</span> sys.foreign_keys <span style="color: #008800;font-weight: bold">AS</span> k
<span style="color: #888888">-- Foreign Keys are only within same database</span>
<span style="color: #008800;font-weight: bold">LEFT</span> <span style="color: #008800;font-weight: bold">JOIN</span> sys.objects <span style="color: #008800;font-weight: bold">AS</span> src <span style="color: #008800;font-weight: bold">ON</span> src.object_id <span style="color: #333333">=</span> k.parent_object_id
<span style="color: #008800;font-weight: bold">LEFT</span> <span style="color: #008800;font-weight: bold">JOIN</span> sys.objects <span style="color: #008800;font-weight: bold">AS</span> dest <span style="color: #008800;font-weight: bold">ON</span> dest.object_id <span style="color: #333333">=</span> k.referenced_object_id
<span style="color: #888888">-- Hide self-references</span>
<span style="color: #008800;font-weight: bold">WHERE</span> k.parent_object_id <span style="color: #333333">&lt;&gt;</span> k.referenced_object_id
</pre>
</div>
<h2>Getting the details for SQL references</h2>
SQL Server exposes the dependency information, e.g. of stored procedures or views in a System Catalog View sys.sql_expression_dependencies. References within the same database are there, but also references to other databases or even to remote objects. This may also contain references to obsolete objects, which no longer exist.

<!-- HTML generated using hilite.me -->
<div style="background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em">
<pre style="margin: 0;line-height: 125%"><span style="color: #008800;font-weight: bold">SELECT</span> 
	<span style="color: #888888">-- source</span>
	<span style="color: #333333">@@</span>SERVERNAME <span style="color: #008800;font-weight: bold">AS</span> SourceServerName,
	DB_NAME() <span style="color: #008800;font-weight: bold">AS</span> SourceDatabaseName,
	OBJECT_SCHEMA_NAME(src.object_id) <span style="color: #008800;font-weight: bold">AS</span> SourceSchemaName,
	src.[<span style="color: #008800;font-weight: bold">type</span>] <span style="color: #008800;font-weight: bold">AS</span> SourceObjectType,
	src.type_desc <span style="color: #008800;font-weight: bold">AS</span> SourceObjectTypeDesc,
	src.name <span style="color: #008800;font-weight: bold">AS</span> SourceObjectName,

	<span style="color: #888888">-- destination</span>
	<span style="color: #008800;font-weight: bold">ISNULL</span>(r.referenced_server_name, <span style="color: #333333">@@</span>SERVERNAME) <span style="color: #008800;font-weight: bold">AS</span> DestinationServerName, 
	<span style="color: #008800;font-weight: bold">ISNULL</span>(r.referenced_database_name, DB_NAME()) <span style="color: #008800;font-weight: bold">AS</span> DestinationDatabaseName,
	<span style="color: #008800;font-weight: bold">ISNULL</span>(r.referenced_schema_name, OBJECT_SCHEMA_NAME(r.referenced_id)) <span style="color: #008800;font-weight: bold">AS</span> DestinationSchemaName,
	dest.[<span style="color: #008800;font-weight: bold">type</span>] <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectType,
	dest.type_desc <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectTypeDesc,
	r.referenced_entity_name <span style="color: #008800;font-weight: bold">AS</span> DestinationObjectName
<span style="color: #008800;font-weight: bold">FROM</span> sys.objects <span style="color: #008800;font-weight: bold">AS</span> src
<span style="color: #008800;font-weight: bold">INNER</span> <span style="color: #008800;font-weight: bold">JOIN</span> sys.sql_expression_dependencies <span style="color: #008800;font-weight: bold">AS</span> r <span style="color: #008800;font-weight: bold">ON</span> r.referencing_id <span style="color: #333333">=</span> src.object_id
<span style="color: #008800;font-weight: bold">LEFT</span> <span style="color: #008800;font-weight: bold">JOIN</span> sys.objects <span style="color: #008800;font-weight: bold">AS</span> dest <span style="color: #008800;font-weight: bold">ON</span> dest.object_id <span style="color: #333333">=</span> r.referenced_id 
<span style="color: #008800;font-weight: bold">WHERE</span> 	
	<span style="color: #888888">-- Hide references to temporary objects within Stored Procedure</span>
	<span style="color: #008800;font-weight: bold">NOT</span> (src.type_desc <span style="color: #333333">=</span> <span style="background-color: #fff0f0">'SQL_STORED_PROCEDURE'</span> <span style="color: #008800;font-weight: bold">AND</span> r.referenced_id <span style="color: #008800;font-weight: bold">IS</span> <span style="color: #008800;font-weight: bold">NULL</span> <span style="color: #008800;font-weight: bold">AND</span> r.referenced_schema_name <span style="color: #008800;font-weight: bold">IS</span> <span style="color: #008800;font-weight: bold">NULL</span>)
</pre>
</div>
<h2>Getting the details for Objects</h2>
The objects of the current database are exposed in the System Catalog View sys.objects. The common name of table types may differ and therefore the one of sys.table_types is used.
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em">
<pre style="margin: 0;line-height: 125%"><span style="color: #008800;font-weight: bold">SELECT</span> 
	<span style="color: #333333">@@</span>SERVERNAME <span style="color: #008800;font-weight: bold">AS</span> ServerName,
	DB_NAME() <span style="color: #008800;font-weight: bold">AS</span> DatabaseName,
	OBJECT_SCHEMA_NAME(src.object_id) <span style="color: #008800;font-weight: bold">AS</span> SchemaName,
	src.[<span style="color: #008800;font-weight: bold">type</span>] <span style="color: #008800;font-weight: bold">AS</span> ObjectType,
	src.type_desc <span style="color: #008800;font-weight: bold">AS</span> ObjectTypeDesc,
	<span style="color: #008800;font-weight: bold">ISNULL</span>(srcTableType.name, src.name) <span style="color: #008800;font-weight: bold">AS</span> ObjectName
<span style="color: #008800;font-weight: bold">FROM</span> sys.objects <span style="color: #008800;font-weight: bold">AS</span> src
<span style="color: #888888">-- in case of Table Types, name in sys.objects differs from actual name in sy.table_types</span>
<span style="color: #008800;font-weight: bold">LEFT</span> <span style="color: #008800;font-weight: bold">JOIN</span> sys.table_types <span style="color: #008800;font-weight: bold">AS</span> srcTableType <span style="color: #008800;font-weight: bold">ON</span> srcTableType.type_table_object_id <span style="color: #333333">=</span> src.object_id
</pre>
</div>
<h2>Overall process</h2>
Steps to get the overall result:
<ol>
 	<li>The data of all known dependencies in the current database for foreign keys and SQL references are gathered.</li>
 	<li>The filtered result using parameter @ShowDependencies (all / Cross-Schema / Cross-Database / Cross-Server) is stored in a temporary table #AllReferences.</li>
 	<li>The object details for all objects of the current database are stored in a temporary table #AllObjects.</li>
 	<li>Objects only mentioned in dependencies, based on #AllReferences, are added to #AllObjects.</li>
 	<li>Objects of the following type are not listed in the result itself:
INTERNAL_TABLE, SYSTEM_TABLE, CHECK_CONSTRAINT, DEFAULT_CONSTRAINT, FOREIGN_KEY_CONSTRAINT, PRIMARY_KEY_CONSTRAINT, UNIQUE_CONSTRAINT
In case of constraints, they're in the result as dependency information for their tables without mentioning the name of the actual constraint: No "FK_xyz_abc" in the result, but a reference of table xyz to table abc and vice versa as referenced-by.</li>
 	<li>Building up the overall result, combining the data of #AllObjects and #AllReferences to a single table.</li>
</ol>
Columns of the overall result including object itself, dependencies as Referenced..., dependent objects as ReferencedBy...:
<ul>
 	<li>ServerName</li>
 	<li>DatabaseName</li>
 	<li>SchemaName</li>
 	<li>ObjectType</li>
 	<li>ObjectTypeDesc</li>
 	<li>ObjectName</li>
 	<li>ReferencedServerName</li>
 	<li>ReferencedDatabaseName</li>
 	<li>ReferencedSchemaName</li>
 	<li>ReferencedObjectType</li>
 	<li>ReferencedObjectTypeDesc</li>
 	<li>ReferencedObjectName</li>
 	<li>ReferencedByServerName</li>
 	<li>ReferencedByDatabaseName</li>
 	<li>ReferencedBySchemaName</li>
 	<li>ReferencedByObjectType</li>
 	<li>ReferencedByObjectTypeDesc</li>
 	<li>ReferencedByObjectName</li>
</ul>
<h1>Constraints</h1>
<h2>Play by the rules: Reports for SQL Server Management Studio (SSMS)</h2>
In order to get the report executable also directly in SSMS, it may not reference Shared DataSources, Shared DataSets and Subreport, also Parameter-Handling is constrained. Therefore, the report does all the work itself, not referencing anything else but a database, which objects and dependencies are analyzed. Also the parameter handling is done in the result using a custom parameter pane and report actions executing the current report with different parameter set.

<img width="512" height="400" class="alignnone wp-image-306" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SSMS-CustomReports.png" />
<h2>Differences between Report Versions</h2>
There are only very few differences between the Report Definitions for SSRS 2008R2 (including 2012 / 2014) and SSRS 2016:
<ol>
 	<li>Schema of RDL-File
<ul>
 	<li>2008R2: https://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition</li>
 	<li>2016: https://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition</li>
</ul>
</li>
 	<li>ReportParametersLayout
With SSRS 2016, the layout of the parameters is customizable. Within the RDL-File, this is stored as ReportParametersLayout-Element.</li>
</ol>
The SSRS 2016 Report may also work with newer versions - as long as the schema doesn't change.

The Query itself is based on System Catalog Views, available in SQL Server &gt;= 2008 and Azure SQL Database.
<h1>Conclusion</h1>
The result became very complex, but in the end, it fulfills the purpose. Knowing the parts and how they fit together, the overall picture should be complex, but no longer complicated. Using this report, you should be able to easily analyze and document database dependencies.

There may be further extensions to this report, not only to list objects and dependencies of the current database but of all databases of a server - e.g. using the very famous but also very shy and therefore officially undocumented stored procedure sp_MSforeachdb. Doing so may blow up the interoperability with Azure SQL Database. But there is also a way to work around this, resulting in a compatible version.

Do you feel challenged? It's up to you! Start going beyond and please share your insights! :-)
<h1>Download</h1>
<ul>
 	<li><a href="https://github.com/Johann-Hackl/SSRS-DatabaseObjectsAndDependencies">Reports at GitHub: SSRS-DatabaseObjectsAndDependencies</a></li>
</ul>
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/sql/reporting-services/reports/report-definition-language-ssrs">SQL Server RDL Specification</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/ssms/object/custom-reports-in-management-studio">Custom Reports in Management Studio</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-foreign-keys-transact-sql">SQL Server System Catalog View: sys.foreign_keys</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-objects-transact-sql">SQL Server System Catalog View: sys.objects</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-sql-expression-dependencies-transact-sql">SQL Server System Catalog View: sys.sql_expression_dependencies</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-table-types-transact-sql">SQL Server System Catalog View: sys.table_types</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>SQL Server Reporting Services: Inventory-Report with Parameters, Dependencies and Permissions</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/sql-server-reporting-services-inventory-report-with-parameters-dependencies-and-permissions</link>
      <pubDate>Fri, 25 Aug 2017 16:06:41 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=215</guid>
      <description><![CDATA[During a SSRS project with a lot of reports, there was the idea to have a detailed non-static...]]></description>
      <content:encoded><![CDATA[During a SSRS project with a lot of reports, there was the idea to have a detailed non-static documentation for the following aspects:
<ul>
 	<li>Report-Server-Structure</li>
 	<li>Permissions</li>
 	<li>Parameters
<ul>
 	<li>Basic data like name, type etc.</li>
 	<li>Value List, static or reference to dataset</li>
 	<li>Default Value, static or reference to dataset</li>
</ul>
</li>
 	<li>References
<ul>
 	<li>Shared DataSources</li>
 	<li>Shared DataSets</li>
 	<li>Subreports</li>
 	<li>URL- and Report-Actions</li>
 	<li>Referenced Assemblies</li>
</ul>
</li>
 	<li>ReferencedBy =&gt; like References, only vice versa</li>
</ul>
There are many good resources out there covering different parts of the story. But I haven't found one that covers all. Therefore, I dig deeper into the Report Server Database. Doing so also knowing that its structure may change with without notice by SQL Server Updates, Service Packs and Releases and that all queries must be done carefully, not affecting others using the Report Server.

The result should also be neat and handy to be usable also directly within SQL Server Management Studio as Custom Report with no further requirements. And on top, it should work for a current SQL Server Reporting Services Instance (2016) in native mode and for SSRS 2012 in SharePoint-Integrated-Mode.
<h1>How the result should look like</h1>
<strong>Basic View with Hierarchy and Control-Panel on top to add Parameter, References, ReferencedBy and Permissions on demand:</strong>

<img width="874" height="650" class="alignnone wp-image-257" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/Report-ReportResult-Permissions.png" />

<strong>Parameters:</strong>

<img width="2313" height="1166" class="alignnone size-full wp-image-255" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/Report-ReportResult-Parameter.png" />

<strong>References and Referenced By:</strong>

<img width="810" height="650" class="alignnone wp-image-265" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/Report-ReportResult-ReferencesAndReferencedBy.png" />

&nbsp;
<h1>Report Design</h1>
<h2>Report Design</h2>
The design for the report consists of:
<ul>
 	<li>Headline</li>
 	<li>Table bound to a Dummy-Dataset, containing the TextBoxes used as Column-Toggle-Items in the Detail-Table</li>
 	<li>Main-Table
<ul>
 	<li>containing icons for different element types</li>
 	<li>Grouped by ItemID of Report and recursively connected by ParentID</li>
 	<li>No Detail-Rows directly in Detail-Table, only a Group-Header-Row</li>
 	<li>Basic columns with Icon, Name, Level (0, 1, 2, ... n), Hidden (true / false), Permission Type (set / inherited)</li>
 	<li>Further columns for Parameters, References, Referenced By and Permissions
<ul>
 	<li>Column-Visibility could be toggled with textboxes of separate table</li>
 	<li>2nd Header-Row of main-table also contains a sub-table for each column with the corresponding row-headers
<ul>
 	<li style="list-style-type: none">
<ul>
 	<li style="list-style-type: none"></li>
</ul>
</li>
</ul>
</li>
 	<li>Detail rows and columns are in separate sub-tables
<ul>
 	<li>Tables are filtered with "[Relevant-Data-Column] &lt;&gt; Nothing"
=&gt; E.g. hide rows with empty Parameter-Data, same row may contain Permission-Data</li>
 	<li>Detail-Columns contain Bookmark-Actions if the content references something else, e.g. Parameters based on Shared DataSets, Report-Actions, Subreports</li>
</ul>
</li>
</ul>
</li>
 	<li> Footer with data from a SystemInfo-Dataset containing the current SQL Server and database name
<ul>
 	<li>
<pre>="Report " &amp; Globals!ReportName &amp; " executed by " &amp; User!UserID &amp; " on " &amp; First(Fields!ServerName.Value, "DSSystemInfo") &amp; ", Database '" &amp; First(Fields!DatabaseName.Value, "DSSystemInfo") &amp; "' at " &amp; Globals!ExecutionTime</pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote></blockquote>
<h1>Report Data Elements</h1>
To get that basic design, the following data elements are used:
<ul>
 	<li>Different icons for each element types</li>
 	<li>Datasource pointing on the target ReportServer-Database</li>
 	<li>Dummy-Dataset</li>
 	<li>SystemInfo-Dataset</li>
 	<li>CatalogDetails-Dataset with all the important data</li>
</ul>
In Report Designer, it looks like:

<img width="255" height="468" class="wp-image-275 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/Report-ReportData-Basic.png" />
<h2>Follow the rules: Reports for SQL Server Management Studio (SSMS)</h2>
In order to get the report executable also directly in SSMS, it may not reference Shared DataSources, Shared DataSets and Subreport, also Parameter-Handling is constrained. Therefore, the report does all the work itself, not referencing anything else but the Report Server Database.

<img width="512" height="400" class="alignnone wp-image-306" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SSMS-CustomReports.png" />
<h2>Differences between Report Versions</h2>
There are only very few differences between the Report Definitions for SSRS 2008R2 (including 2012 / 2014) and SSRS 2016:
<ol>
 	<li>Schema of RDL-File and namespace for XML-Queries within Main-Dataset
<ul>
 	<li>2008R2: https://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition</li>
 	<li>2016: https://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition</li>
</ul>
</li>
 	<li>ReportParametersLayout
With SSRS 2016, the layout of the parameters is customizable. Within the RDL-File, this is stored as ReportParametersLayout-Element.</li>
</ol>
The SSRS 2016 Report may also work with newer versions - as long as the schema doesn't change.
<h1>Getting the data</h1>
<strong>The Report Server Database is intended as black box, not to be accessed directly. Therefore no offical documentation about the database structure exists. If you proceed to query it, do so with extreme caution and at your own risk.</strong>
<h2>SharePoint-Integrated-Mode vs. Native-Mode</h2>
The Report Server Catalog mostly looks the same for SharePoint-Integrated-Mode and Native-Mode, except the following scenarios, handled by the query:
<ul>
 	<li>Report-Elements stored with File-Extension, e.g. ".rdl" for Reports, ".rsd" for Shared DataSets and ".rsds" for Shared DataSources</li>
 	<li>No report structure, only root-folder</li>
</ul>
The DataSet-Query gets the basic element structure into Database-Temp-Tables (#...) first and then checks, if it runs on a SharePoint-Integrated-Mode Database - modifying the data in the Temp-Tables before further processing.

Condition for SharePoint-Integrated-Mode:
<ul>
 	<li>only one folder exists according to Catalog-Table</li>
 	<li>but elements exist with paths signalizing sub-folders (several slashes in it, e.g. "/.../...")</li>
</ul>
Actions:
<ul>
 	<li>Remove extensions</li>
 	<li>Generate intermediate folder structure</li>
</ul>
In addition to that, permissions of SharePoint-Integrated-Mode are stored in SharePoint itself and not handled the query - and therefore not shown in the report. In this case, the ReportServer-Database only contains rudimentary permissions which are listed in the result. In case of Native-Mode, these stored permissions reflect the actual permissions.
<h2>Getting the details</h2>
Some of the data is directly available in tables, other is only available in the report definition (RDL-File) or in a separate XML-Block:
<ul>
 	<li>Hierarchy and Basic Data
<ul>
 	<li>stored in the table dbo.Catalog</li>
</ul>
</li>
 	<li>Parameter
<ul>
 	<li>stored in separate ParameterXml in dbo.Catalog, because it may differ from the actual report definition and modified through Report Server GUI (e.g. different label or default value)
<ul>
 	<li>separate XML is modified</li>
 	<li>the report definition itself is untouched</li>
</ul>
</li>
 	<li>in addition, the Values-DataSetName and DefaultValues-DataSetName is only available in the report definition</li>
</ul>
</li>
 	<li>References
<ul>
 	<li>SharedDataSource
<ul>
 	<li>stored in the table dbo.DataSource</li>
 	<li>the target item can be linked in the report</li>
</ul>
</li>
 	<li>SharedDataSet
<ul>
 	<li>stored in the table dbo.Datasets</li>
 	<li>the target item can be linked in the report</li>
</ul>
</li>
 	<li>Actions and SubReports
<ul>
 	<li>stored in report definition</li>
 	<li>Bookmark-Actions are not listed, because they are only references within the report itself, but not to other components</li>
 	<li>Report-Actions are also not listed, if they refer to the report name itself or using an expression "=Globals!ReportName"</li>
 	<li>More complex references to itself, e.g. using a JavaScript-URL are not filtered out and therefore may be listed</li>
 	<li>if possible, the target items are linked in the report - e.g. if identified in same folder or by full path</li>
</ul>
</li>
 	<li>CodeModules
<ul>
 	<li>stored in report definition</li>
</ul>
</li>
</ul>
</li>
 	<li>Permissions
<ul>
 	<li>stored in tables dbo.PolicyUserRole, dbo.Users and dbo.Roles</li>
</ul>
</li>
</ul>
<h2>Returning the details: DSCatalogDetails</h2>
The result contains separate rows for each element:
<ul>
 	<li>One row per element containing the basic data, all other columns for Parameters, References, ReferencedBy and Permissions are null</li>
 	<li>Multiple rows with Parameters and basic data, all other columns are null
<ul>
 	<li>Static Values and Static Default Values are returned as single columns containing HTML using FLWOR-Statements</li>
 	<li>The corresponding textboxes therefore contain data with MarkupType set to "HTML"</li>
</ul>
</li>
 	<li>Multiple rows with References and basic data, all other columns are null</li>
 	<li>Multiple rows with ReferencedBy and basic data, all other columns are null</li>
 	<li>Multiple rows with Permissions and basic data, all other columns are null</li>
</ul>
Gathering the References is designed using a Common-Table-Expression (CTE). Getting the References and ReferencedBy both uses this CTE - the one way and the other way.

In addition, the DataSet contains three calculated columns, used for Bookmark-Navigation within report:
<ul>
 	<li>ReferenceDefinition_Unique
<ul>
 	<li>
<pre>=IIf(Fields!ReferenceDefinition.Value = "", "", Fields!ItemID.Value.ToString() &amp; ":" &amp; Fields!ReferenceDefinition.Value)</pre>
</li>
</ul>
</li>
 	<li>ParameterValidValuesDataSetName_Unique
<ul>
 	<li>
<pre>=IIf(Fields!ParameterValidValuesDataSetName.Value = "", "", Fields!ItemID.Value.ToString() &amp; ":" &amp; Fields!ParameterValidValuesDataSetName.Value)</pre>
</li>
</ul>
</li>
 	<li>ParameterDefaultValuesDataSetName_Unique
<ul>
 	<li>
<pre>=IIf(Fields!ParameterDefaultValuesDataSetName.Value = "", "", Fields!ItemID.Value.ToString() &amp; ":" &amp; Fields!ParameterDefaultValuesDataSetName.Value)</pre>
</li>
</ul>
</li>
</ul>
Using the calculated columns is necessary to display and reference the actual Shared DataSet for Valid-Values and Default-Values of a DataSet:
<ol>
 	<li>The Parameter-Rows contain the ItemID of the current report and the in-report name of the DataSet.</li>
 	<li>Using both, the referenced ItemID according the separate References-Row could be determined, if the in-report Dataset Name itself refers to a Shared DataSet.</li>
 	<li>Doing so, a Bookmark-Link within the report can be added.</li>
</ol>
The calculated columns don't add value to the data itself and only necessary for rendering - because of a missing multi-column lookup-function within SSRS. Therefore, I've added them as calculated columns and not in the SQL itself.

A look at all columns together:

<img width="357" height="775" class="alignnone wp-image-285" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/Report-ReportData-DSCatalogDetails.png" />
<h1>Conclusion</h1>
The result became very complex, but in the end, it fulfills the purpose. Knowing the parts and how they fit together, the overall picture should be complex, but no longer complicated.
<h1>Download</h1>
<ul>
 	<li><a href="https://github.com/Johann-Hackl/SSRS-Inventory">Reports for SSRS-Inventory at GitHub</a></li>
</ul>
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/sql/reporting-services/reports/report-definition-language-ssrs">SQL Server RDL Specification</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/ssms/object/custom-reports-in-management-studio">Custom Reports in Management Studio</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/xquery/flwor-statement-and-iteration-xquery">FLWOR Statement and Iteration (XQuery)</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>SQL Server Migration Assistant (SSMA): Export Data Type Mappings</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/sql-server-migration-assistant-ssma-export-data-type-mappings</link>
      <pubDate>Fri, 04 Aug 2017 15:48:10 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=175</guid>
      <description><![CDATA[Using the SQL Server Migration Assistant (SSMA), Database Migrations to various Microsoft Database...]]></description>
      <content:encoded><![CDATA[Using the SQL Server Migration Assistant (SSMA), Database Migrations to various Microsoft Database targets (SQL Server Versions on premise and cloud) are possible. One aspect of the migration is the mapping between source data types and target data types.

During an Oracle-2-SQL-Migration project, it was necessary to send the used Data Type Mapping to the customer.

The mapping is editable in SSMA on different levels, e.g. Default Project Settings, Project Level, Schema Level, Object Level and is separated into four types:
<ul>
 	<li>Arguments type mapping</li>
 	<li>Columns type mapping</li>
 	<li>Local variables type mapping</li>
 	<li>Return values type mapping</li>
</ul>
If possible, keep the mapping as simple as possible, not creating different mappings on different levels, e.g. BOOLEAN -&gt; BIT on Project Level vs. BOOLEAN -&gt; INT for a special Schema or even table or procedure.

There is currently no way to export the mapping within SSMA. In this blog post, I'll show how to export the Project Level Data Type Mapping to a tab separated file.

This blog post is based on SQL Server Migration Assistant for Oracle, Version 7.4. Other SSMA versions may work (slightly) differently.
<h1>Project Level Data Type Mapping</h1>
The Data Type Mapping at the Project Level is accessible in SSMA by the menu item "Tools" -&gt; "Project settings", tab "Type Mapping":

<img width="606" height="440" class="alignnone wp-image-195" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/ProjectLevelDataTypeMapping.png" />

A single mapping may depend on the source precision, resulting in different data types and/or precisions (Button "Edit..."):

<img width="392" height="250" class="alignnone wp-image-196" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/ProjectLevelDataTypeMappingSingleMapping.png" />

The mapping is stored in the file project-container.mappings, located in the root folder of the SSMA Project. The file is a XML file.

Sample (shortened to show all four types):

<img width="713" height="750" class="alignnone wp-image-206" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/project-container.mappings.png" />
<h1>Export to tab separated values file</h1>
Using PowerShell and XSLT, the mapping could be transformed to tab separated values and stored as file:
<blockquote>
<pre>$inputMapping = "project-container.mappings"
$inputXsl = "ProjectMappings2TSV.xsl"
$outputFile = "project-container.mappings.tsv"

$xslt = New-Object System.Xml.Xsl.XslCompiledTransform;
$xslt.Load($inputXsl);
$xslt.Transform($inputMapping, $outputFile);</pre>
</blockquote>
<div>The resulting output file project-container.mappings.tsv looks like:</div>
<blockquote>
<pre>Mapping Oracle SQL-Server
Arguments type mapping BFILE VARBINARY(MAX)
Arguments type mapping BINARY_DOUBLE FLOAT[53]
Arguments type mapping BINARY_FLOAT FLOAT[53]
Arguments type mapping BINARY_INTEGER INT
...
Columns type mapping BLOB VARBINARY(MAX)
Columns type mapping CHAR CHAR
...
Local variables type mapping CHAR CHAR
Local variables type mapping CHAR[*..8000] CHAR[*]
Local variables type mapping CHAR[8001..*] VARCHAR(MAX)
...
Return values type mapping INT INT
Return values type mapping INTEGER INT
Return values type mapping LONG VARCHAR(MAX)
...</pre>
</blockquote>
<div>ProjectMappings2TSV.xsl, used for export:</div>
<div>
<blockquote>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;xsl:stylesheet version="1.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
&gt;
    &lt;xsl:output method="text" indent="no"/&gt;

    &lt;!-- Output: CSV with three columns, tab separated--&gt;
    &lt;xsl:template match="/"&gt;
      &lt;xsl:text&gt;Mapping Oracle SQL-Server
&lt;/xsl:text&gt;
      &lt;xsl:for-each select="container/schema"&gt;
        &lt;xsl:variable name="MappingType" select="@type" /&gt;
        &lt;xsl:for-each select="mapping"&gt;
          &lt;xsl:value-of select="$MappingType" /&gt;
          &lt;xsl:text&gt; &lt;/xsl:text&gt;
          &lt;xsl:call-template name="PrintType"&gt;
            &lt;xsl:with-param name="TypeNode" select="source" /&gt;
          &lt;/xsl:call-template&gt;
          &lt;xsl:text&gt; &lt;/xsl:text&gt;
          &lt;xsl:call-template name="PrintType"&gt;
            &lt;xsl:with-param name="TypeNode" select="target" /&gt;
          &lt;/xsl:call-template&gt;
          &lt;xsl:text&gt;
&lt;/xsl:text&gt;
        &lt;/xsl:for-each&gt;
      &lt;/xsl:for-each&gt;
    &lt;/xsl:template&gt;

  &lt;!-- Print Type--&gt;
  &lt;xsl:template name="PrintType"&gt;
    &lt;xsl:param name="TypeNode" /&gt;
    &lt;!-- either non-parametrized (without @) or parametrized--&gt;
    &lt;xsl:choose&gt;
      &lt;xsl:when test="contains($TypeNode/@type-id, '@')"&gt;
        &lt;xsl:value-of select="substring-before($TypeNode/@type-id, '@')" /&gt;
        &lt;xsl:for-each select="$TypeNode/*"&gt;
          &lt;xsl:text&gt;[&lt;/xsl:text&gt;
          &lt;xsl:choose&gt;
            &lt;!-- Value exists =&gt; use value--&gt;
            &lt;xsl:when test="@value"&gt;
              &lt;xsl:value-of select="@value"/&gt;
            &lt;/xsl:when&gt;
            &lt;!-- Start and/or End exists =&gt; Interval {Start or *}..{End or *}--&gt;
            &lt;xsl:when test="@start or @end"&gt;
              &lt;xsl:choose&gt;
                &lt;xsl:when test="@start"&gt;
                  &lt;xsl:value-of select="@start"/&gt;
                &lt;/xsl:when&gt;
                &lt;xsl:otherwise&gt;
                  &lt;xsl:text&gt;*&lt;/xsl:text&gt;                  
                &lt;/xsl:otherwise&gt;
              &lt;/xsl:choose&gt;
              &lt;xsl:text&gt;..&lt;/xsl:text&gt;
              &lt;xsl:choose&gt;
                &lt;xsl:when test="@end"&gt;
                  &lt;xsl:value-of select="@end"/&gt;
                &lt;/xsl:when&gt;
                &lt;xsl:otherwise&gt;
                  &lt;xsl:text&gt;*&lt;/xsl:text&gt;
                &lt;/xsl:otherwise&gt;
              &lt;/xsl:choose&gt;
            &lt;/xsl:when&gt;
            &lt;!-- No interval and also no value =&gt; * --&gt;
            &lt;xsl:otherwise&gt;
              &lt;xsl:text&gt;*&lt;/xsl:text&gt;
            &lt;/xsl:otherwise&gt;
          &lt;/xsl:choose&gt;
          &lt;xsl:text&gt;]&lt;/xsl:text&gt;
        &lt;/xsl:for-each&gt;
      &lt;/xsl:when&gt;
      &lt;!-- Non-parametrized =&gt; use type-id unmodified--&gt;
      &lt;xsl:otherwise&gt;
        &lt;xsl:value-of select="$TypeNode/@type-id" /&gt;
      &lt;/xsl:otherwise&gt;
    &lt;/xsl:choose&gt;
  &lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;</pre>
</blockquote>
</div>
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/sql/ssma/sql-server-migration-assistant">SQL Server Migration Assistant</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql">SQL Server Data types</a></li>
 	<li><a href="https://docs.oracle.com/database/121/SQLRF/sql_elements001.htm#SQLRF0021">Oracle 12c Data types</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/powershell/">PowerShell</a></li>
 	<li><a href="https://msdn.microsoft.com/en-us/library/ms256069.aspx">XSLT Reference</a></li>
 	<li><a href="https://msdn.microsoft.com/de-de/library/system.xml.xsl.xslcompiledtransform.transform.aspx">XslCompiledTransform.Transform</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Dealing with the Report Server Report Rendering Object Model</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/dealing-with-the-report-server-report-rendering-object-model</link>
      <pubDate>Sun, 16 Jul 2017 14:41:08 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=65</guid>
      <description><![CDATA[After creating a working stub of a Custom Rendering Extension, it was time to add the actual...]]></description>
      <content:encoded><![CDATA[After <a href="https://blogs.technet.microsoft.com/johannh/2017/07/14/creating-and-debugging-a-ssrs-custom-rendering-extension/">creating a working stub of a Custom Rendering Extension</a>, it was time to add the actual functionality. In order to do so, the Report Server Object Model used for Report Rendering has to be understood. In this blog post, I want to share some insights gained during this process. It will hopefully help to speed up the process of getting in touch with the Report Server Object Model, but isn't a replacement for existing reference and the need of digging deeper by yourself.
<h1>Namespaces</h1>
Depending on the Type of Reporting Extension (Processing, Rendering, Delivery etc.), different Namespaces are used. The Types itself are similar, but not equal. E.g. the Processing Extension uses the Microsoft.ReportingServices.RdlObjectModel-Namespace, containing details for the Report Parameter Layout - irrelevant for Report Rendering and therefore missing in the Microsoft.ReportingServices.OnDemandReportRendering-Namespace.

This Blog Post is focused on the Report Rendering Object Model. Nevertheless, it is useful to identify the corresponding element and structure in the Report Definition and then have a look after the corresponding elements in the Rendering Object Model.

E.g. Table =&gt; Tablix-Type - SSDT and RDL-File:

<img width="689" height="160" class=" wp-image-105 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Tablix-SSDT.png" />

<img width="314" height="200" class=" wp-image-96 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Tablix-RDL.png" /><b></b><i></i><u></u><sub></sub><sup></sup><span style="text-decoration: line-through"></span>
<h1>Type Hierarchies &amp; Structure</h1>
<h2>Report Structure</h2>
The overall report is structured into Report Sections, containing Report Items in their Report Body:

<img width="778" height="562" class=" wp-image-115 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Report-Structure.png" />
<h2>Report Item Hierarchy</h2>
Report Items are the visual elements, dragged into a report. The Type for Table, Matrix and List is all Tablix. Also Data Bar and Chart are both of Type Chart.

<img width="1517" height="219" class="size-full wp-image-76 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/ReportItem-TypeHierarchy.png" />
<h2>Report Element Hierarchy</h2>
Report Element is the Base Type of Report Item with some other derived Types used for internal Report Structure.

<img width="615" height="110" class="wp-image-86 alignnone" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/ReportElement-TypeHierarchy.png" />
<h2>Basic Type Structure</h2>
There are two kind of types, the ones for Report Definition, containing the uncalculated expressions, e.g. "=Fields!xyz.Value", and the ones with the actual calculated values. The latter usually have the Type-Postfix "Instance", e.g. "TextBox" vs. "TextBoxInstance". The Types for actual instances are much more differentiated than the definition ones in order to differentiate between static and dynamic ones, e.g. in case of a table, header columns are static and data bound columns are dynamic - requiring the row-by-row processing of the underlying data.

The following three types are the base Report Definition Types for many other types in the Report Object Model, containing some basic information of all elements:

<img width="907" height="502" class="alignnone wp-image-125" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/ReportElement-ReportItem-and-DataRegion-Structure.png" />
<h2>Dynamic Instances / Row-Processing</h2>
If an element is bound to a dataset, the instance contains the value of the current row. To get the values of all rows, they have to be processed.

Common methods, e.g. according to <a href="https://technet.microsoft.com/en-us/library/microsoft.reportingservices.ondemandreportrendering.tablixdynamicmemberinstance_methods.aspx">TablixDynamicMemberInstance</a>:
<ul>
 	<li>void ResetContext() =&gt; Reset before first instance</li>
 	<li>bool MoveNext() =&gt; Move to next instance, return true if successful</li>
 	<li>int GetInstanceIndex() =&gt; Index of current instance</li>
 	<li>bool SetInstanceIndex(int index) =&gt; Move to specific instance, return true if successful</li>
</ul>
Types supporting these methods:
<ul>
 	<li>ChartDynamicMemberInstance</li>
 	<li>DataDynamicMemberInstance</li>
 	<li>GaugeDynamicMemberInstance</li>
 	<li>MapDynamicMemberInstance</li>
 	<li>TablixDynamicMemberInstance</li>
</ul>
To process all instances, simply call ResetContext() and then process each instance inside a while(instance.MoveNext()). Depending on the complexity of the Report Item, several nested processings may be required to get all data, e.g. in a Tablix with dynamic rows and columns.
<h1>Understanding Tablix and Chart</h1>
Tablix and Chart are the common "more complex ones" of the Report Items.
<h2>Tablix</h2>
A tablix may contain several row- and column-hierarchies. Each of them may contain several TablixMember, which may be nested. To get a look on all items in SSDT, switch to Advanced Mode:

<img width="1481" height="564" class="alignnone size-full wp-image-135" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Tablix-AdvancedMode.png" />

Depending on the nature of the Tablix Member - either static or dynamic (Property TablixMember.IsStatic), its instance is either of Type TablixMemberInstance or TablixDynamicMemberInstance.
<h3>Matrix-Example</h3>
Report Definition:

<img width="339" height="200" class="alignnone wp-image-145" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/SampleTablix-Definition.png" />

Report Preview:

<img width="367" height="250" class="alignnone wp-image-146" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/SampleTablix-Result.png" />

Diagnostics Output:
<blockquote>
<pre>- Tablix1: Type = Tablix 
, Rows = 1, Columns = 1 
=== Row Hierarchies === 
 - 0: isStatic = False 
=== Column Hierarchies === 
 - 0: isStatic = False 
=== Stats: dynamic rows = 1, columns = 1 === 
=== Tablix Data === 
Data in format row hierarchy | column hierarchy | row recursion level | column recursion level | dataRowCounter-Prefix: val-1, val-2 ... 
0 | 0 | 1 | 0 | /1/1:458, 487, 498, 274 
0 | 0 | 1 | 0 | /1/2:1.159.731, 1.175.475, 1.201.006, 646.509 
0 | 0 | 1 | 0 | /2/1:342, 395, 416, 167 
0 | 0 | 1 | 0 | /2/2:778.424, 969.668, 962.583, 381.662 
0 | 0 | 1 | 0 | /3/1:350, 335, 367, 127 
0 | 0 | 1 | 0 | /3/2:859.417, 779.221, 804.262, 351.505 
0 | 0 | 1 | 0 | /4/1:229, 251, 296, 139 
0 | 0 | 1 | 0 | /4/2:514.022, 617.589, 757.623, 410.201 
0 | 0 | 1 | 0 | /5/1:1.027, 1.115, 1.248, 488 
0 | 0 | 1 | 0 | /5/2:2.448.470, 2.685.292, 2.888.463, 1.128.373 
0 | 0 | 1 | 0 | /6/1:454, 571, 643, 258 
0 | 0 | 1 | 0 | /6/2:1.161.575, 1.330.468, 1.540.239, 586.586</pre>
</blockquote>
Code to get the Diagnostics Output:
<blockquote>
<pre>public static void DebugItem(Tablix tablix)
{
	Debug.WriteLine(string.Format("- {0}: Type = {1}",
			tablix.Name,
			tablix.GetType().Name
			));
	Debug.WriteLine(string.Format(", Rows = {0}, Columns = {1}", tablix.Body.RowCollection.Count, tablix.Body.ColumnCollection.Count));

	int i = 0;
	Debug.WriteLine("=== Row Hierarchies ===");
	List&lt;TablixMember&gt; dynamicRows = new List&lt;TablixMember&gt;();
	foreach (TablixMember m in tablix.RowHierarchy.MemberCollection)
	{
		Debug.WriteLine(string.Format(" - {0}: isStatic = {1}", i++, m.IsStatic));
		if (!m.IsStatic)
		{
			dynamicRows.Add(m);

		}
	}

	i = 0;
	Debug.WriteLine("=== Column Hierarchies ===");
	List&lt;TablixMember&gt; dynamicColumns = new List&lt;TablixMember&gt;();
	foreach (TablixMember m in tablix.ColumnHierarchy.MemberCollection)
	{
		Debug.WriteLine(string.Format(" - {0}: isStatic = {1}", i++, m.IsStatic));
		if (!m.IsStatic)
		{
			dynamicColumns.Add(m);
		}
	}

	// Print data: if classic table 
	Debug.WriteLine(string.Format("=== Stats: dynamic rows = {0}, columns = {1} ===", dynamicRows.Count, dynamicColumns.Count));

	Debug.WriteLine("=== Tablix Data ===");
	if (dynamicColumns.Count == 0)
	{
		// Process rows
		foreach (TablixMember m in tablix.RowHierarchy.MemberCollection)
		{
			if (m.IsStatic)
			{
				Debug.WriteLine(CreateRowCsv(tablix, m, ";"));
			}
			else
			{
				TablixDynamicMemberInstance dynamicInstance = (TablixDynamicMemberInstance)m.Instance;
				dynamicInstance.ResetContext();
				while (dynamicInstance.MoveNext())
				{
					Debug.WriteLine(CreateRowCsv(tablix, m, ";"));
				}
			}
		}
	}
	else 
	{
		Debug.WriteLine("Data in format row hierarchy | column hierarchy | row recursion level | column recursion level | dataRowCounter-Prefix: val-1, val-2 ...");
		// Process rows
		foreach(TablixMember mR in tablix.RowHierarchy.MemberCollection)
		{
			DebugRowHierarchy(tablix, mR, 0, "");
		}
	}
}

/// &lt;summary&gt;
///  recursively process all row hiearchies, afterwards all column hierarchies and then the values itself
/// &lt;/summary&gt;
/// &lt;param name="tablix"&gt;&lt;/param&gt;
/// &lt;param name="hierarchy"&gt;&lt;/param&gt;
/// &lt;param name="rowDepth"&gt;0..n, depth of row: 0 = starting level, 1 = child, 2 = child of child... &lt;/param&gt;
/// &lt;param name="colDepth"&gt;0..n, depth of col: 0 = starting level, 1 = child, 2 = child of child... &lt;/param&gt;
private static void DebugRowHierarchy(Tablix tablix, TablixMember rowMember, int rowDepth, string dataRowCounterPrefix)
{
	if (rowMember.Children == null || rowMember.Children.Count == 0)
	{
		// row-hierarchy has no children =&gt; process columns, either static or dynamic
		if (rowMember.IsStatic)
		{
			foreach (TablixMember mC in tablix.ColumnHierarchy.MemberCollection)
			{
				DebugColumnHierarchyForRow(tablix, rowMember, rowDepth, mC, 0, dataRowCounterPrefix);
			}

		}
		else
		{
			int dataRowCounter = 0;
			TablixDynamicMemberInstance dynamicInstance = (TablixDynamicMemberInstance)rowMember.Instance;
			dynamicInstance.ResetContext();
			while (dynamicInstance.MoveNext())
			{
				foreach (TablixMember mC in tablix.ColumnHierarchy.MemberCollection)
				{
					dataRowCounter++;
					DebugColumnHierarchyForRow(tablix, rowMember, rowDepth, mC, 0, string.Format("{0}/{1}",dataRowCounterPrefix, dataRowCounter));
				}
			}
		}
	} else
	{
		// row-hierarchy has children =&gt; process children, either static or dynamic
		if (rowMember.IsStatic)
		{
			foreach (TablixMember c in rowMember.Children)
			{
				DebugRowHierarchy(tablix, c, rowDepth + 1, dataRowCounterPrefix);
			}
		}
		else
		{
			int dataRowCounter = 0;
			TablixDynamicMemberInstance dynamicInstance = (TablixDynamicMemberInstance)rowMember.Instance;
			dynamicInstance.ResetContext();
			while (dynamicInstance.MoveNext())
			{
				foreach (TablixMember c in rowMember.Children)
				{
					dataRowCounter++;
					DebugRowHierarchy(tablix, c, rowDepth + 1, string.Format("{0}/{1}", dataRowCounterPrefix, dataRowCounter));
				}
			}
		}
	}
}

private static void DebugColumnHierarchyForRow(Tablix tablix, TablixMember rowMember, int rowDepth, TablixMember colMember, int colDepth, string dataRowCounterPrefix)
{
	if (colMember.Children == null || colMember.Children.Count == 0)
	{
		// col-hierarchy has no children =&gt; process values, either static or dynamic
		if (colMember.IsStatic)
		{
			foreach (TablixMember mC in tablix.ColumnHierarchy.MemberCollection)
			{
				Debug.WriteLine(string.Format("{0} | {1} | {2} | {3} | {4}: {5}",
					rowMember.MemberCellIndex, colMember.MemberCellIndex,
					rowDepth, colDepth,
					dataRowCounterPrefix,
					GetValue(tablix, rowMember.MemberCellIndex, colMember.MemberCellIndex)
					)
					);
			}

		}
		else
		{
			StringBuilder str = new StringBuilder();
			str.AppendFormat("{0} | {1} | {2} | {3} | {4}:",
					rowMember.MemberCellIndex, colMember.MemberCellIndex,
					rowDepth, colDepth,
					dataRowCounterPrefix
				);
			TablixDynamicMemberInstance dynamicInstance = (TablixDynamicMemberInstance)colMember.Instance;
			bool addSeparator = false;
			dynamicInstance.ResetContext();
			while (dynamicInstance.MoveNext())
			{
				// add separator, except before first value
				if (addSeparator)
				{
					str.Append(", ");
				}
				else
				{
					addSeparator = true;
				}
				// add value
				str.Append(GetValue(tablix, rowMember.MemberCellIndex, colMember.MemberCellIndex));
			}
			Debug.WriteLine(str);
		}
	}
	else
	{
		// column-hierarchy has children =&gt; process children, either static or dynamic
		if (colMember.IsStatic)
		{
			foreach (TablixMember c in colMember.Children)
			{
				DebugColumnHierarchyForRow(tablix, rowMember, rowDepth, c, colDepth + 1, dataRowCounterPrefix);
			}
		}
		else
		{
			int dataRowCounter = 0;
			TablixDynamicMemberInstance dynamicInstance = (TablixDynamicMemberInstance)colMember.Instance;
			dynamicInstance.ResetContext();
			while (dynamicInstance.MoveNext())
			{
				dataRowCounter++;
				foreach (TablixMember c in rowMember.Children)
				{
					DebugColumnHierarchyForRow(tablix, rowMember, rowDepth, c, colDepth + 1, string.Format("{0}/{1}", dataRowCounterPrefix, dataRowCounter));
				}
			}
		}
	}
}

public static string CreateRowCsv(Tablix tablix, TablixMember row, string separator)
{
	StringBuilder b = new StringBuilder();
	int count = tablix.Body.ColumnCollection.Count;
	int i = 0;
	while (i &lt; count)
	{
		CellContents content = tablix.Body.RowCollection[row.MemberCellIndex][i].CellContents;
		b.Append(GetValue(content));

		// adjust column counter
		i += content.ColSpan;

		// add separator, if not last column
		if (i &lt; count)
		{
			b.Append(separator);
		}
	}
	return b.ToString();
}

public static string GetValue(Tablix tablix, int rowHierarchy, int columnHierarchy)
{
	TablixCell tablixCell = tablix.Body.RowCollection[rowHierarchy][columnHierarchy];
	if (tablixCell != null) { 
		return GetValue(tablixCell.CellContents);
	}
	return null;
}

public static string GetValue(CellContents content)
{
	// add content, if textbox - TODO: support other cell content
	if (content != null &amp;&amp; content.ReportItem != null)
	{
		TextBoxInstance textBoxInstance = content.ReportItem.Instance as TextBoxInstance;
		if (textBoxInstance != null)
		{
			return textBoxInstance.Value;
		}
		else if (content.ReportItem != null)
		{
			return string.Format("UNSUPPORTED REPORT-ITEM {0}", content.ReportItem.GetType().Name);
		}
	}
	return null;
}
</pre>
</blockquote>
<h2>Chart</h2>
The visual elements of a chart are grouped into Chart Areas.
The data itself is split into 4 dimensions:
<ul>
 	<li>Series Hierarchy</li>
 	<li>Category Hierarchy</li>
 	<li>Series Collection =&gt; containing a Chart Area Name and a Chart Series Type (Bar, Column, Line etc.)</li>
 	<li>Data Points</li>
</ul>
<h3>Example</h3>
Chart Definition:

<img width="658" height="400" class="alignnone wp-image-155" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Chart-Definition.png" />

Chart Preview:

<img width="482" height="250" class="alignnone wp-image-157" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/Chart-Result.png" />

Diagnostics Output:
<blockquote>
<pre>- Chart1: Type = Chart, Dataset = DSTableList 
=== Chart Titles === 
 - 0: Caption = Table-Count by Schema 
=== Areas === 
 - 0: Name = Default 
 - 1: Name = Area1 
=== Series Hierarchies === 
 - 0: Label = Table Name, isStatic = True 
=== Category Hierarchies === 
 - 0: Label = =Fields!SchemaName.Value, isStatic = False 
=== Series === 
 - 0: Name = TableName, Chart Area = , Chart Series Type = Bar 
=== Derived Series === 
 - 1: Name = DerivedSeries1, Chart Area = Area1, Chart Series Type = Line, Formula = MovingAverage 
=== Chart Details === 
 - Series Hierarchy | Category Hierarchy | Series-Name : Values (x|y) 
 - Table Name | Application | TableName : (|15) 
 - Table Name | dbo | TableName : (|1) 
 - Table Name | Purchasing | TableName : (|7) 
 - Table Name | Sales | TableName : (|12) 
 - Table Name | Warehouse | TableName : (|14)</pre>
</blockquote>
Code to get the Diagnostics Output:
<blockquote>
<pre>public static void DebugItem(Chart chart)
{
 Debug.WriteLine(string.Format("- {0}: Type = {1}, Dataset = {2}",
   chart.Name,
   chart.GetType().Name,
   chart.DataSetName
   ));

 int i = 0;
 Debug.WriteLine("=== Chart Titles ===");
 foreach (ChartTitle t in chart.Titles)
 {
  Debug.WriteLine(string.Format(" - {0}: Caption = {1}", i++, ((ChartTitleInstance)t.Instance).Caption));
 }

 i = 0;
 Debug.WriteLine("=== Areas ===");
 foreach (ChartArea a in chart.ChartAreas)
 {
  Debug.WriteLine(string.Format(" - {0}: Name = {1}", i++, a.Name));
 }

 i = 0;
 Debug.WriteLine("=== Series Hierarchies ===");
 foreach (ChartMember m in chart.SeriesHierarchy.MemberCollection)
 {
  Debug.WriteLine(string.Format(" - {0}: Label = {1}, isStatic = {2}", i++, (m.Label.IsExpression ? m.Label.ExpressionString : m.Label.Value), m.IsStatic));
 }

 i = 0;
 Debug.WriteLine("=== Category Hierarchies ===");
 foreach (ChartMember m in chart.CategoryHierarchy.MemberCollection)
 {
  Debug.WriteLine(string.Format(" - {0}: Label = {1}, isStatic = {2}", i++, (m.Label.IsExpression ? m.Label.ExpressionString : m.Label.Value), m.IsStatic));
 }

 i = 0;
 Debug.WriteLine("=== Series ===");
 foreach (ChartSeries s in chart.ChartData.SeriesCollection)
 {
  Debug.WriteLine(string.Format(" - {0}: Name = {1}, Chart Area = {2}, Chart Series Type = {3}", i++, s.Name, s.Instance.ChartAreaName, s.Type.Value));
 }
 Debug.WriteLine("=== Derived Series ===");
 foreach (ChartDerivedSeries s in chart.ChartData.DerivedSeriesCollection)
 {
  Debug.WriteLine(string.Format(" - {0}: Name = {1}, Chart Area = {2}, Chart Series Type = {3}, Formula = {4}", i++, s.Series.Name, s.Series.Instance.ChartAreaName, s.Series.Type.Value, s.DerivedSeriesFormula));
 }

 Debug.WriteLine("=== Chart Details ===");
 Debug.WriteLine(" - Series Hierarchy | Category Hierarchy | Series-Name : Values (x|y)");
 foreach (ChartMember m in chart.SeriesHierarchy.MemberCollection)
 {
  DebugChartSeriesHierarchy(chart, m);
 }
}

private static void DebugChartSeriesHierarchy(Chart chart, ChartMember seriesHierarchy)
{
 if (seriesHierarchy.IsStatic)
 {
  foreach (ChartMember cm in chart.CategoryHierarchy.MemberCollection)
  {
   DebugChartCategoryHierarchy(chart, seriesHierarchy, cm);
  }
 }
 else
 {
  ChartDynamicMemberInstance dI = (ChartDynamicMemberInstance)seriesHierarchy.Instance;
  dI.ResetContext();
  while (dI.MoveNext())
  {
   foreach (ChartMember cm in chart.CategoryHierarchy.MemberCollection)
   {
    DebugChartCategoryHierarchy(chart, seriesHierarchy, cm);
   }
  }
 }
}

private static void DebugChartCategoryHierarchy(Chart chart, ChartMember seriesHierarchy, ChartMember categoryHierarchy)
{
 if (categoryHierarchy.IsStatic)
 {
  foreach (ChartSeries s in chart.ChartData.SeriesCollection)
  {
   DebugChartSeries(chart, seriesHierarchy, categoryHierarchy, s);
  }
 }
 else
 {
  ChartDynamicMemberInstance dI2 = (ChartDynamicMemberInstance)categoryHierarchy.Instance;
  dI2.ResetContext();
  while (dI2.MoveNext())
  {
   foreach (ChartSeries s in chart.ChartData.SeriesCollection)
   {
    DebugChartSeries(chart, seriesHierarchy, categoryHierarchy, s);
   }
  }
 }
}

private static void DebugChartSeries(Chart chart, ChartMember seriesHierarchy, ChartMember categoryHierarchy, ChartSeries series)
{
 StringBuilder sDataPoint = new StringBuilder();
 sDataPoint.AppendFormat(" - {0} | {1} | {2} : ", seriesHierarchy.Instance.Label, categoryHierarchy.Instance.Label, series.Name);
 for (int iDataPoint = 0; iDataPoint &lt; series.Count; iDataPoint++)
 {
  ChartDataPoint p = series[iDataPoint];
  ChartDataPointValues pV = p.DataPointValues;
  ChartDataPointValuesInstance pVI = pV.Instance;
  sDataPoint.AppendFormat("({0}|{1})", pVI.X, pVI.Y);
  if (iDataPoint &lt; series.Count - 1)
  {
   sDataPoint.Append(", ");
  }
 }
 Debug.WriteLine(sDataPoint);
}
</pre>
</blockquote>
<h1>Conclusion</h1>
During a few days, I've got only a first glimpse of the possibilities with Custom Rendering Extension. This post therefore covers only a part of the overall picture - but hopefully is a good start, if you have to proceed with this journey.
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://msdn.microsoft.com/en-us/library/microsoft.reportingservices.ondemandreportrendering.aspx">Microsoft.ReportingServices.OnDemandReportRendering Namespace</a></li>
 	<li><a href="https://msdn.microsoft.com/en-us/library/microsoft.reportingservices.rdlobjectmodel.aspx">Microsoft.ReportingServices.RdlObjectModel Namespace</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/reporting-services/reports/report-definition-language-ssrs">Report Definition Language / RDL-Schema</a></li>
</ul>
&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>Creating and Debugging a SSRS Custom Rendering Extension</title>
      <link>https://docs.microsoft.com/archive/blogs/johannh/creating-and-debugging-a-ssrs-custom-rendering-extension</link>
      <pubDate>Fri, 14 Jul 2017 12:39:34 GMT</pubDate>
      <dc:creator><![CDATA[Johann Hackl]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/johannh/?p=35</guid>
      <description><![CDATA[SQL Server Reporting Services (SSRS) is shipped with many predefined rendering extensions to save a...]]></description>
      <content:encoded><![CDATA[SQL Server Reporting Services (SSRS) is shipped with many predefined rendering extensions to save a report in various different formats, e.g. CSV, Excel, PDF, Word, PowerPoint. Nevertheless, a customer requested creating his own rendering extension. The goal was to create a new extension based on the predefined PowerPoint-Extension with fallback to the original one. The fallback will be done depending on custom properties, set in the report definition of the rendered report. The Report Server GUI should only contain the new PowerPoint-Rendering Extension.

This blog post is for demo purposes of the overall progress with working fallback to the original extension. It will not contain the code for creation of a custom PPTX itself. Further blog posts will describe the Report Server Object Model to understand the rendered report with its complex structure of Report Design and Runtime Instances with calculated data.

The scenario is based on SSRS 2016 and SQL Server Data Tools (SSDT) / Visual Studio 2015 (VS).
<h1>VS-Project for Custom Rendering Extension</h1>
The VS-Project is a Class-Library-Project, which uses .NET framework 4.5.2 in my scenario.
<h2>Project References</h2>
Reference following assemblies, located in Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies:
<ul>
 	<li>Microsoft.ReportingServices.Interfaces</li>
 	<li>Microsoft.ReportingServices.ProcessingCore</li>
 	<li>Microsoft.ReportingServices.RPLObjectModel</li>
</ul>
<h2>Extension Class</h2>
The extension class, using a switch depending on the existence of a custom report property "PPTXExportMode". If it is not set or set to "Default", the original PPTX-Export is called. If set, it throws a NotImplementedException - the code for creating the desired custom PPTX has to be added there.
<blockquote>
<pre>using System;
using Microsoft.ReportingServices.Interfaces;
using Microsoft.ReportingServices.OnDemandReportRendering;

namespace CustomPowerPointExtension
{
    public class MyPptxRenderingExtension : IRenderingExtension
    {

        #region IRenderingExtension
        public string LocalizedName
        {
            get {
                return LOCALIZED_NAME;
            }
        }

        public void GetRenderingResource(CreateAndRegisterStream createAndRegisterStreamCallback,
                                         System.Collections.Specialized.NameValueCollection deviceInfo)
        {
            this.StandardPptxRendering.GetRenderingResource(createAndRegisterStreamCallback, deviceInfo);
        }

        public bool Render(Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                           System.Collections.Specialized.NameValueCollection reportServerParameters,
                           System.Collections.Specialized.NameValueCollection deviceInfo,
                           System.Collections.Specialized.NameValueCollection clientCapabilities,
                           ref System.Collections.Hashtable renderProperties,
                           CreateAndRegisterStream createAndRegisterStream)
        {
            return DoRender(report, reportServerParameters, deviceInfo, clientCapabilities, ref renderProperties, createAndRegisterStream);
        }

        public bool RenderStream(string streamName,
                                 Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                                 System.Collections.Specialized.NameValueCollection reportServerParameters,
                                 System.Collections.Specialized.NameValueCollection deviceInfo,
                                 System.Collections.Specialized.NameValueCollection clientCapabilities,
                                 ref System.Collections.Hashtable renderProperties,
                                 CreateAndRegisterStream createAndRegisterStream)
        {
            return DoRender(report, reportServerParameters, deviceInfo, clientCapabilities, ref renderProperties, createAndRegisterStream);
        }


        public void SetConfiguration(string configuration)
        {
            this.StandardPptxRendering.SetConfiguration(configuration);
        }
        #endregion

        #region Custom Code
        private const string LOCALIZED_NAME = "My PPTX renderer";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE = "PPTXExportMode";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT = "Default";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE_CUSTOM = "Custom";
        private string standardPptxRenderingTypeName = "Microsoft.ReportingServices.Rendering.PowerPointRendering.PptxRenderingExtension,Microsoft.ReportingServices.PowerPointRendering";
        private IRenderingExtension standardPptxRendering;

        protected IRenderingExtension StandardPptxRendering
        {
            get
            {
                if (this.standardPptxRendering == null)
                {
                    Type t = Type.GetType(this.standardPptxRenderingTypeName);
                    this.standardPptxRendering = (IRenderingExtension)Activator.CreateInstance(t);
                }
                return this.standardPptxRendering;
            }
        }

        private static string GetExportMode(Microsoft.ReportingServices.OnDemandReportRendering.Report report)
        {
            CustomProperty cpExportMode = report.CustomProperties[REPORT_PROPERTY_PPTX_EXPORT_MODE];
            return (cpExportMode == null || cpExportMode.Instance == null || cpExportMode.Instance.Value == null)
                 ? REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT : cpExportMode.Instance.Value.ToString();
        }

        public MyPptxRenderingExtension()
        {

        }

        private bool DoRender(Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                           System.Collections.Specialized.NameValueCollection reportServerParameters,
                           System.Collections.Specialized.NameValueCollection deviceInfo,
                           System.Collections.Specialized.NameValueCollection clientCapabilities,
                           ref System.Collections.Hashtable renderProperties,
                           CreateAndRegisterStream createAndRegisterStream)
        {
            string exportMode = GetExportMode(report);
            if (exportMode == REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT)
            {
                return this.StandardPptxRendering.Render(report,
                                                  reportServerParameters,
                                                  deviceInfo,
                                                  clientCapabilities,
                                                  ref renderProperties,
                                                  createAndRegisterStream);
            }

            // TODO: custom implementation
            throw new NotImplementedException(string.Format("Not implemented yet, report mode: {0}", exportMode));
        }
        #endregion
    }

}</pre>
</blockquote>
<h1>Configuration</h1>
SSRS supports many types of extensions to create custom controls, renderings, deliveries and many more.

These extensions are configured in the SSRS configuration files. In order to enable them for report design in SSDT, they have to be configured there as well:
<ul>
 	<li>Design Time: Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies</li>
 	<li>Run Time: SSRS-Directory, e.g. C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer</li>
</ul>
To use the extension, modify RSReportDesigner.config (Design Time) and RSReportServer.config (Run Time).

Replace default extension
<blockquote>
<pre>&lt;Extension Name="PPTX" Type="Microsoft.ReportingServices.Rendering.PowerPointRendering.PptxRenderingExtension,Microsoft.ReportingServices.PowerPointRendering" /&gt;</pre>
</blockquote>
with new extension
<blockquote>
<pre>&lt;Extension Name="MYPPTX" Type="CustomPowerPointExtension.MyPptxRenderingExtension,CustomPowerPointExtension" /&gt;</pre>
</blockquote>
The extension must be copied to these folders:
<ul>
 	<li>Design Time: Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies</li>
 	<li>Run Time: Bin-Subdirectory of SSRS-Directory, e.g. C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\bin</li>
</ul>
In my scenario, in order to get the fallback for the original extension working, it is also necessary to flag the resulting custom assembly as trustworthy within RSPreviewPolicy.config (Design Time) and RSSrvPolicy.config (Run Time).
<ul>
 	<li>Design Time</li>
</ul>
<blockquote>
<pre>&lt;CodeGroup   
	class="UnionCodeGroup"  
	version="1"  
	PermissionSetName="FullTrust"  
	Name="MyCustomCodeGroup"  
	Description="Code group for my custom extension"&gt;  
		&lt;IMembershipCondition class="UrlMembershipCondition"  
		version="1"  
		Url="C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies\CustomPowerPointExtension.dll"  
		/&gt;  
&lt;/CodeGroup&gt;	
</pre>
</blockquote>
<ul>
 	<li>Run Time
watch out for correct position to add the configuration, otherwise the Full Trust may not work: directly after $CodeGen$, see <a href="https://docs.microsoft.com/en-us/sql/reporting-services/extensions/secure-development/using-reporting-services-security-policy-files">policy-files</a></li>
</ul>
<blockquote>
<pre>&lt;CodeGroup   
	class="UnionCodeGroup"  
	version="1"  
	PermissionSetName="FullTrust"  
	Name="MyCustomCodeGroup"  
	Description="Code group for my custom extension"&gt;  
		&lt;IMembershipCondition class="UrlMembershipCondition"  
		version="1"  
		Url="C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\bin\CustomPowerPointExtension.dll"  
		/&gt;  
&lt;/CodeGroup&gt;	
</pre>
</blockquote>
<h1>VS Post-Build-Event</h1>
To deploy the assembly automatically to the local SSDT-Directory, simply add a Post-Build-Event to the VS-Project. Because of the targeted directory within Program Files, it may be necessary to start Visual Studio in Elevated Mode ("start as Admin").
<blockquote>
<pre>copy /Y "$(TargetPath)" "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies"</pre>
</blockquote>
Additionally, the copy only works, if the assembly is not currently accessed by SSDT - simply close the Report-Project within SSDT to get rid of the file lock.
<h1>Debugging</h1>
To debug Custom Renderings of SSDT, attach Visual Studio to the process "PreviewProcessingService.exe". The process is started by SSDT for report rendering. If the process doesn't exist yet, simply switch to Report Preview within SSDT. The debugging of Custom Renderings of SSRS is also possible, simply attach VS to the ReportServer-Process.
<h1>Further Reading</h1>
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/sql/reporting-services/extensions/reporting-services-extension-library">Reporting Services Extension Library</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/sql/reporting-services/report-server/reporting-services-configuration-files">Reporting Services Configuration Files</a></li>
 	<li><a href="https://msdn.microsoft.com/en-us/library/office/gg278317.aspx">Create Power Point Documents using OpenXML</a></li>
</ul>
&nbsp;]]></content:encoded>
    </item>
  </channel>
</rss>