<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Eckhard Zemp</title>
	<atom:link href="https://www.zemp.ch/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.zemp.ch</link>
	<description>Business Intelligence, Lifehacks and Travelblog</description>
	<lastBuildDate>Fri, 20 Nov 2020 08:38:03 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
<site xmlns="com-wordpress:feed-additions:1">57859474</site>	<item>
		<title>SQL Server locking methods or the fight against dead locks</title>
		<link>https://www.zemp.ch/2020/04/11/sql-server-locking-methods-or-the-fight-against-dead-locks/</link>
					<comments>https://www.zemp.ch/2020/04/11/sql-server-locking-methods-or-the-fight-against-dead-locks/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 11 Apr 2020 09:18:28 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1962</guid>

					<description><![CDATA[After the last Index blog post here another technical issue I recently thought to have solved. At least maybe for the next year until I find another solution. As you know I dropped all indexes of my data warehouse and therefore also unique constrains on my tables. The application should make sure that everything is [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>After the last <a href="https://www.zemp.ch/2018/09/01/my-indexing-journey/" >Index blog post</a> here another technical issue I recently thought to have solved. At least maybe for the next year until I find another solution.</p>
<p>As you know I dropped all indexes of my data warehouse and therefore also unique constrains on my tables. The application should make sure that everything is fine. But I have a regular report which checks, if the constrains are still intact.</p>
<p>Lately after working on a project with huge amount of data, I got hit occasionally with duplicate rows in my hubs and links.</p>
<p>This was the reason to investigate further about why it did happen. Although I have used all measures below, it happened. Maybe the unique constraint indexes have additional functions when loading data to it. I must admit, I&#8217;m not the export in that area. I&#8217;m educated by mistakes and experience. Therefore, this is a little sum up about my lessons learnt.</p>
<p>Depending on the number of queries running at the same time with SELECT, UPDATE and DELETE, SQL Server decides, which data is locked, read, waited for, overwritten or inserted. There are some methods to influence its decision making.</p>
<h2 id="set-transaction-isolation-level-transact-sql">SET TRANSACTION ISOLATION LEVEL</h2>
<p>Working with transactions is usually something for applications having SQL Server as a backend, to ensure, that the data is written correctly to the database spanning multiple table manipulations. In the data area I didn&#8217;t respect to work with transactions for some time, as I was usually transforming data from source to target, hardly inserting data in parallel into the same table. With Data Vault it is rather a usual pattern.</p>
<p>The transaction is written like that:</p>
<pre>BEGIN TRANSACTION
  INSERT INTO ...
  SELECT *
  FROM ...
COMMIT
OPTION(RECOMPILE)
</pre>
<p>By default I add <a href="https://www.zemp.ch/2019/03/30/option-recompile/" ><code>OPTION(RECOMPILE)</code></a> after fighting against bad execution plans.</p>
<p>This layout I had in place, when I discovered duplicate entries. My search for a solution led me to learn more about Transaction Isolation Level. SQL Server has a few of them:</p>
<ul>
<li>READ UNCOMMITTED</li>
<li>READ COMMITTED (default)</li>
<li>REPEATABLE READ</li>
<li>SNAPSHOT</li>
<li>SERIALIZABLE</li>
</ul>
<p>I don&#8217;t want to explain all the nifty details about each option. There are plenty of resources at google.</p>
<p>I suspected that although I had all those table locks (see below) in place, data got changed between an INSERT and its SELECT statement. Especially in fact loading with looking up dimension keys. Sounds weird, but I couldn&#8217;t help suspecting.</p>
<p>Many websites express, that SERIALIZABLE is the most secure option, but has a very hard disadvantage &#8211; it leads to deadlocks. A deadlock is an issue when SQL Server tries to find out what to do next in a safe way. Sometimes 2 queries want to have access to the same resource, but queuing won&#8217;t help as other resources are blocked too. SQL Server terminates 1 query telling it that is a &#8222;victim of deadlock&#8220;. With restartable jobs this isn&#8217;t an issue. But still bad.</p>
<p>I added the following statement to my scripts:</p>
<pre>SET TRANSACTION ISOLATION LEVEL SERIALIZABLE</pre>
<p>All following transactions are then executed with that isolation level.</p>
<p>I was hit with a lot of deadlock errors in the coming weeks. But I got the issue solved about having duplicates in hubs and links.</p>
<h2>Hints</h2>
<p>Hints are another way to manipulate the SQL Servers decision engine. There are a lot of <a target="_blank" href="https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query?view=sql-server-2016" >hints</a>, which we can add to a query. Some are affecting the whole query manipulating the execution plan (query hints) and some the queried table (table hints).</p>
<p>Query hints are added at the end with <code>OPTION(something)</code>. As you know, by default I add <code>RECOMPILE</code> to it to enforce getting the best plan for every data set instead of recycling old plans. Sometimes I add <code>FORCE ORDER</code> or <code>HASH JOIN</code> to it, depending on performance.</p>
<p>To combat my issue, I rather use table hints. My favorite is <code>TABLOCKX</code>. By default, SQL Server tries to lock as little as possible. Sometimes only pages or rows of a table.  But I want to be sure that no other query sneaks into a running query manipulating something in between. <code>TABLOCK</code> will try to grab a &#8222;shared&#8220; lock on that table while the <code>X</code> gets an exclusive lock.</p>
<p>My statement looked usually like that:</p>
<pre>INSERT INTO ... WITH (TABLOCKX)
SELECT *
FROM ...</pre>
<p>But somehow, I still got duplicates in my hubs and links. How could that be? It must be something with the <code>SELECT</code> query. My <code>SELECT</code> query has also a part in it where it checks the very same table, I&#8217;m inserting data into for records that already exists. Could it be that the <code>INSERT</code> locks are not applied to the <code>SELECT</code> tables although being the same table?</p>
<p>Reading through other blogs I have seen following recipe:</p>
<pre>INSERT INTO ... WITH (TABLOCKX)
SELECT *
FROM ... WITH (UPDLOCK)</pre>
<p><code>UPDLOCK</code> acquires a modify lock telling the system it intends to modify the data but in fact I won&#8217;t modify it. I just want that other queries will modify them when I&#8217;m reading it.</p>
<p>Maybe this is the way to go, maybe not. It&#8217;s hard to find something useful in the data warehousing space as usually all the questions asked are for applications and record level issues.</p>
<p>While we talk about table hints, I got also into another issue. In my pattern collection I have 3 different insert methods. Each optimized for perfect fast loading. One is for the initial load. When the table is empty, I don&#8217;t need to retrieve the last record e.g. with Satellite loading for comparing hash values and deciding if it is a change or not. If the table is empty, just pump the data in.</p>
<pre>IF EXISTS (SELECT 1 FROM table)
  QUERY INITIAL INSERT
ELSE
  QUERY NORMAL INSERT
</pre>
<p>So, what happens? The first query will check if there is a record in the table. Then it will run query 1 or query 2. But what happens in the split second between the 2 queries? Or maybe there is another <code>INSERT</code> already running? As the first query will run in <code>READ COMMITTED</code> it won&#8217;t find a record and tells the initial insert to not check for existing data.</p>
<p>We must modify it a little and adding table hints.</p>
<pre>IF EXISTS (SELECT 1 FROM table WITH(UPDLOCK,HOLDLOCK))
  QUERY INITIAL INSERT
ELSE
  QUERY INSERT
</pre>
<p>The <code>UPDLOCK</code> will wait until any insert has finished and sets a modify lock. If the query is run with only <code>UPDLOCK</code> it will check for the record and releases the lock immediately again. The <code>HOLDLOCK</code> will keep the <code>LOCK</code> until the transaction is committed. The first statements sets a lot which is kept until the end.</p>
<p>The final layout looked like that:</p>
<pre>IF EXISTS (SELECT 1 FROM table WITH(UPDLOCK,HOLDLOCK))
  INSERT INTO ... WITH (TABLOCKX,HOLDLOCK)
  SELECT *
  FROM ... WITH (UPDLOCK,HOLDLOCK)
ELSE
  QUERY INSERT
</pre>
<p>I believe there are way too many locks. And again, I had a lot of deadlock issues to deal with.</p>
<p>Is there not something easier to work with? Maybe something like a queue?</p>
<h2>Application Lock</h2>
<p>There is another method in SQL Server to work with: <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql" >Application Lock</a>. This means, it is completely independent of any table locks.</p>
<p>With this lock we can invent a locking mechanism by defining a random name for a resource. As my procedures are loading data to a target, I use the target name as a resource. It has nothing to do with the underlying table and doesn&#8217;t affect it.</p>
<p>How to set a lock:</p>
<pre>sp_getapplock @Resource = 'table', @LockMode = 'Exclusive', @LockOwner = 'Session'</pre>
<p>With this statement we request a virtual lock for the resource named &#8218;table&#8216;. If any other query or stored procedure issues the same statement it will get into the queue and waits until the first lock gets released.</p>
<p>There are 2 levels to define these locks:</p>
<ul>
<li>Session</li>
<li>Transaction</li>
</ul>
<p>If we define <code>@LockOwner = 'Transaction'</code> the lock gets released after the commit.</p>
<p>But as I could have multiple transactions in one script, I rather like to specify it on session level. The whole script has to run through until I release the lock with the following statement at the end.</p>
<pre>sp_releaseapplock @Resource = 'table', @LockOwner = 'Session'</pre>
<p>My full script looks like that:</p>
<pre>DECLARE @v_lock_code_{table} INT
EXEC @v_lock_code_{table} = sp_getapplock @Resource = '{table}', @LockMode = 'Exclusive', @LockOwner = 'Session'
IF @v_lock_code_{table} NOT IN (0,1)
BEGIN
  RAISERROR('Unable to acquire exclusive lock on {table}', 16, 1)
  RETURN
END
... a lot of code ...
EXEC @v_lock_code_{table} = sp_releaseapplock @Resource = '{table}', @LockOwner = 'Session'
</pre>
<h2>Summary</h2>
<p>With this method I create locks on all data manipulation objects. The queue will complete query after query and tries not to sneak into other workflows. If I finish an object, I release the lock and everything is good.</p>
<p>The focus is really to have a queue for every <code>INSERT</code> or <code>UPDATE</code> object.</p>
<p><code>SELECT</code> queries or <code>JOIN</code>s don&#8217;t need to issue this lock. The table is free to read. By the way: with the table hint method above there is also a hint named <code>NOLOCK</code> which should disrespect any existing locks. But when I use it, I seldom get any data returned until all locks are released.</p>
<p>With application locks I could also move back to <code>READ COMMITTED</code> isolation level.</p>
<p>Another side effect is, that other non-related queries are not locked too. Even though a <code>TABLOCKX</code> is only for a specific table, I have experienced locks on other transactions and in other databases. Lately I made a test. I stopped the whole execution engine on my test machine and started all jobs at the same time. I have never seen so much traffic on my databases and CPU was at 100%.</p>
<p>I hope, I have now peace of mind and no dead locks anymore.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1962" data-postid="1962" class="themify_builder_content themify_builder_content-1962 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2020/04/11/sql-server-locking-methods-or-the-fight-against-dead-locks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1962</post-id>	</item>
		<item>
		<title>How to build a Business Vault Satellite</title>
		<link>https://www.zemp.ch/2020/01/18/how-to-build-a-business-vault-satellite/</link>
					<comments>https://www.zemp.ch/2020/01/18/how-to-build-a-business-vault-satellite/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 18 Jan 2020 13:03:27 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1785</guid>

					<description><![CDATA[At the last meetup I learnt that many people don&#8217;t know how to build a business vault satellite (or even that it exists!). Many are struggling to get the data into a Raw Data Vault. Persisting data from the source over time. On the market are a few solutions, which build Data Vaults nearly automatically. [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>At the last meetup I learnt that many people don&#8217;t know how to build a business vault satellite (or even that it exists!). Many are struggling to get the data into a Raw Data Vault. Persisting data from the source over time.</p>
<p>On the market are a few solutions, which build Data Vaults nearly automatically. According to some metadata fields from the source a data vault can be built quite easily. Some are spending a lot of time tuning model conversion rules or with data modelling. Don&#8217;t get me wrong, modelling is an important part! But when the core model is defined, matching source data to it shouldn&#8217;t take long.</p>
<p>Most of my time is spent on implementing business rules. Persisting data in a Raw Vault needs no time.</p>
<p>As you have seen with my other blog posts, I like patterns. Repeatable patterns. I have heard that there are rule engines and other fancy stuff out there. So far, I can&#8217;t imagine how such a rule engine could work. Maybe somebody can show me their solution.</p>
<h2>Developing</h2>
<p>Before we are going into the patterns, what kind of tools are available:</p>
<ul>
<li>Graphical interfaces such as SSIS, Informatica, Talend and others</li>
<li>Programming outside SQL with Python, Scala, etc.</li>
<li>Programming in SQL</li>
</ul>
<h3>Graphical Interface</h3>
<p>I used to work with SSIS. Graphical interfaces are nice. Especially the look at how the data is streaming through each step is very intriguing.</p>
<p>But I found it difficult to use with repeatable patterns. Usually when I learnt something new about how to ingest data, I had to go through all existing loading pipelines and modify the thing I learnt.</p>
<p>To import data from source and building raw vaults definitely a nice way to go.</p>
<h3>Programming outside SQL</h3>
<p>I&#8217;m sure that Python and Scala have their use case and the patterns below can be easily adapted.</p>
<p>If the data platform of your choice has no compute nodes, then it is the default thing to take the data out, do something and save it again in the platform.</p>
<h3>Programming in SQL</h3>
<p>But as I work with SQL Server, there is no need to take the data out, do something and load it again. Just a waste of bandwidth and the compute power of the server is not used. But I understand that for bigger solutions other ways must be found and used.</p>
<p>When working with SQL I have several choices:</p>
<ul>
<li>Work with Stored Procedure aligning all calculation steps after each other</li>
<li>Work with Common Table Expression and combining all calculation steps into one statement</li>
<li>Work with Views/Tables building on each other</li>
<li>Or a combination of all above</li>
</ul>
<p>I use templates to create stored procedures doing all the necessary steps AFTER I create the base SQL query. Therefore, I don&#8217;t want to meddle with it by modifying. This would break the maintainability.</p>
<p>I love to work with Common Table Expressions. At least for drafts. Fantastic to build data sets upon each other and enhancing the calculation. But when it comes to debugging, it is very cumbersome to always rewrite the <code>SELECT</code> statement at the end extracting data from each step.</p>
<p>Therefore, I like a combination of Views and Tables to separate each data set and calculation step. For debugging it is perfect. Sometimes I list all elements after each other and filter on a certain key to find out what it is doing in each step and if that is the right behavior.</p>
<p>Feel free to use your own method. Wanted just to share my experience.</p>
<h2>Patterns</h2>
<p>Just a quick note: If I write about keys, I mean all possible keys in Data Vault: Hub Hash Key, Link Hash Key, Natural Keys, Business Key etc. It just defines the grain of data I&#8217;m looking at. Is it on sales order or sales item level? Or something else?</p>
<p>What patterns did I find and use nowadays:</p>
<ul>
<li>Define the target key</li>
<li>Define the source key</li>
<li>Get all source keys</li>
<li>Calculation
<ul>
<li>Get the data for the calculation
<ul>
<li>All values, last values or Point-In-Time</li>
</ul>
</li>
<li>Define the LoadDate</li>
<li>Calculate</li>
</ul>
</li>
<li>Prepare Result Data Set</li>
</ul>
<h3>Example</h3>
<p>For easier understanding, let&#8217;s take an easy example. We have the following Data Vault Model:</p>
<ul>
<li><code>hub_customer</code></li>
<li><code>hub_sales_order</code></li>
<li><code>link_sales_order_item</code> (and feel free to read it also as <code>hub_sales_order_item</code>)</li>
</ul>
<p>I have a raw vault satellite attached to the <code>hub_sales_order</code> which has a value for <code>discount_amount</code>. To calculate the margin on item level, I need to distribute this amount to all <code>sales_order_items</code>.</p>
<h3>Define the target key</h3>
<p>Before I start any job, I need to define what the grain of my result set should be. Maybe we have not yet an idea, how the calculation should look like, but I have to make sure, what the target grain should be.</p>
<p>In this example it will be a satellite attached to <code>link_sales_order_item</code>.</p>
<h3>Define the source key</h3>
<p>Apart from the calculation this is the most important step. What key do I need as a basis.</p>
<ul>
<li>What will be my source data key?</li>
<li>What will be the grain of detail for the calculation?</li>
<li>Does a &#8222;higher level&#8220; key make more sense?</li>
</ul>
<p>Sometimes it is the same grain as the target. Like when calculating the <code>vat_amount</code> for each <code>sales_order_item</code>.</p>
<p>In our case the level of <code>hub_customer</code> would be too high. The level of <code>hub_sales</code> would just be right. If just a SINGLE <code>sales_order_item</code> changes, I want to recalculate everything for ALL <code>sales_order_items</code> of a given order.</p>
<h3>Get all source keys</h3>
<p>When the grain of the source key is defined, I collect all source keys for which the calculation is needed.</p>
<p>I discovered that persisting that data into a table has the best performance.</p>
<p>Usually I define in my &#8222;delta&#8220; object, what my target satellite will be. The template will look up the maximum LoadDate value from it and use that value to retrieve the newest data from the source.</p>
<p>Sometimes I add a time window by subtracting x days from the retrieved LoadDate to make sure that I don&#8217;t miss any beat. At the beginning of my journey I had the aspiration to have only real deltas and wondered how others are doing that when you have 2 source tables while 1 lags a little behind of time. With subtracting x days of the LoadDate this issue can be taken care of. The focus is on preparing the data, but if there is no change, it won&#8217;t load to the target.</p>
<p>And sometimes I have multiple satellites to gather my source keys from.</p>
<p>To have such a &#8222;bucket&#8220; of keys is really a great thing. I try to find as many rules as possible why something needs an update. The goal is to get all possible keys for which I need to do a calculation or an update of old calculations. E.g. for calculating contribution margin 3, I need to add payment costs. Payment costs are not easily attributable. Therefore a factor is usually needed. If management decides to modify it later and also for old orders, what can I do? Either truncate the satellite and re-calculate it again or add a rule! I usually do the later. I don&#8217;t want to think about all actions others might do. It just has to work!</p>
<h3>Calculation</h3>
<p>As we have now all keys to work with, we can start collecting data and do the calculation.</p>
<h4>Get Data for Calculation</h4>
<p>Identify all satellites, reference tables and other tables where you need to extract data from.</p>
<p>As all my calculations are views, I create sometimes views of all my source satellites to make it visible what data I&#8217;m extracting from where and what I need. Just for easier reading for others. And sometimes not.</p>
<p>Then I join all those data views to my source keys. Of course, there is sometimes the case, that I join a source view later in the calculation steps.</p>
<p>In my example I would join now <code>discount_amount</code> from <code>sat_sales_order_discount</code> and <code>gross_sales_amount</code> from <code>lsat_sales_order_item_amount</code>.</p>
<p>The result data set is now complete. All source keys are available, all needed data points for the calculation are there.</p>
<h4>All values, last values or Point-In-Time</h4>
<p>While joining the source data I need also to answer the question, if I need all values, last values or to create a mini Point-In-Time table.</p>
<p>If the source key is of the same grain as the target, for example when I clean strings of bad characters, I just take the source satellite and use each row in the calculation.</p>
<p>If the grain is different like in our example, I usually take the last value of the source satellites. For easier immediate access I create by default for all of my satellites last value views.</p>
<p>Of course, I&#8217;m not limited to the last values only. I have also other views: Last, First, Current (excluding deleted), Timeline (from timestamp till 9999-12-31), Infinite (from 0001-01-01 till 9999-12-31).</p>
<p>Sometimes my users request also values being calculated for each data point in the past. This means that I create on the fly a mini point-in-time table for all LoadDates since the last load.</p>
<p>At the beginning, I thought that I need now to create a PIT table. But maintaining a full-fledged PIT table is usually not possible. Sometimes I have multiple business rules sitting on each other. When I would create the first PIT for my source satellites, do I create a new PIT for the just updated business vault satellite? And what about the next business vault satellite? Another PIT including it? Or just updating the old one? Kind of stupid. Therefore, I calculate a PIT on the fly with only the LoadDates needed in that pipeline and only with the newest LoadDates.</p>
<p>For our example it is enough to extract the last known value.</p>
<h4>Define LoadDate</h4>
<p>Usually my calculation is based on one main raw vault satellite. Then I continue to take THAT LoadDate for my newly created business vault satellites data.</p>
<p>If I load from more than one satellite, I calculate sometimes the maximum value of ALL LoadDates for a given business key.</p>
<pre>,(
    SELECT MAX(v)
    FROM  (
             VALUES
                     (satellite_1.dss_load_datetime)
                    ,(satellite_2.dss_load_datetime)
                    ,(...)
          ) AS VALUE(v)
) AS dss_load_datetime
</pre>
<p>If the calculation like the current <code>age</code> and <code>age_group</code> of a customer is so random and needs to get calculated daily, I use the current system datetime as LoadDate. If the age changes I need a new LoadDate, otherwise the new value can&#8217;t be loaded into the Satellite.</p>
<p>If the LoadDate is not defined correctly, updates for new results will not get imported into the target, as the primary key (hash key and LoadDate) from the old calculation already exists.</p>
<h4>Calculate</h4>
<p>Do whatever you want. Sometimes it is a simple calculation, sometimes many steps with intermediate result sets.</p>
<p>In my case, I would create a view summarizing all <code>gross_sales_amounts</code> of <code>sales_order_items</code> for each <code>sales_order</code>.</p>
<p>In the next step I would use a calculation like <code>gross_sales_amount</code> / <code>sum_gross_sales_amount</code> * <code>discount_amount</code> to distribute it to each <code>sales_order_item</code>.</p>
<p>If I need to round it to 2 digits, I will have also to take care about cent differences (sum of all <code>discount_amounts</code> of all <code>sales_order_items</code> versus the <code>discount_amount</code> on <code>sales_order</code> level). I would sort the rows by <code>gross_sales_amount DESC</code> and add the cent difference to that line with the biggest <code>gross_sales_amount</code>.</p>
<h3>Prepare Result Data Set</h3>
<p>If the calculation is looking correct and I believe I have finished, I select those columns which should be loaded to the business vault satellite, calculate the <code>hash_diff</code> and load the data set to the target. Sometimes I also calculate new <code>hash_keys</code> to create same-as-links or other data vault objects.</p>
<p>Before continuing, take a break and congratulate yourself for the fantastic job you did. Of course, it is 100% error free.</p>
<h2>Bonus Task</h2>
<p>As you have your results saved, think about ways how to ensure that the result is always correct. Here comes the Error Mart into play.</p>
<p>What can you check:</p>
<ul>
<li>Do the same task again but in a different way and compare both results</li>
<li>Compare summary of amounts per key of source and target satellite (based on last view)</li>
<li>Compare counts of rows per key of source and target satellite (based on last view)</li>
<li>Define keys which should have a result and check if the keys in the business vault satellite exists</li>
<li>Etc.</li>
</ul>
<p>Make it a habit that each business vault satellite has at least 1 QA view.</p>
<p>In our case we collect them in a separate database. We placed it next to the reports database. In my opinion it should be rather named as error reports than error mart. And I have error reports for Data Stores, Data Vault, Data Mart and Exports.</p>
<p>The created QA view shows only the faulty keys or rows. Everyday a job is run to count the rows in each QA view and reports a summary for all checks. Then we take actions and fix the issues.</p>
<h2>Summary</h2>
<p>You have seen my way of building business vault satellites. I hope you agree with the main patterns. If you have more patterns, let me now.</p>
<p>I have heard of business rules engines, but newer seen one. If you have one, get in touch. I would be happy to learn more about it.</p>
<p>Here is also a picture of such a transfer (another case):</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2020/01/business_vault_satellite_example.png" ><img data-tf-not-load="1" fetchpriority="high" loading="auto" decoding="auto" fetchpriority="high" decoding="async" class="alignleft size-full wp-image-1910" src="https://www.zemp.ch/wordpress/wp-content/uploads/2020/01/business_vault_satellite_example.png" alt="" width="497" height="520" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2020/01/business_vault_satellite_example.png 497w, https://www.zemp.ch/wordpress/wp-content/uploads/2020/01/business_vault_satellite_example-191x200.png 191w" sizes="(max-width: 497px) 100vw, 497px" /></a></p>
<!--themify_builder_content-->
<div id="themify_builder_content-1785" data-postid="1785" class="themify_builder_content themify_builder_content-1785 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2020/01/18/how-to-build-a-business-vault-satellite/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1785</post-id>	</item>
		<item>
		<title>Universal Loading Pattern Reloaded</title>
		<link>https://www.zemp.ch/2019/08/30/universal-loading-pattern-reloaded/</link>
					<comments>https://www.zemp.ch/2019/08/30/universal-loading-pattern-reloaded/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Fri, 30 Aug 2019 07:30:06 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1636</guid>

					<description><![CDATA[There was quite some noise, when I published last year my blog post about Universal Loading Pattern, a common view how to process data to any target. Be it a Data Vault Hub or a Fact table. Since then I have worked with this pattern happily without any incidents. Some time ago I thought, well, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>There was quite some noise, when I published last year my blog post about <a href="https://www.zemp.ch/2018/04/07/universal-loading-pattern/" >Universal Loading Pattern</a>, a common view how to process data to any target. Be it a Data Vault Hub or a Fact table.</p>
<p>Since then I have worked with this pattern happily without any incidents.</p>
<p>Some time ago I thought, well, are these patterns still true? Did I forget a pattern? Or has my view towards these patterns changed? This falls fully in line <a target="_blank" href="https://m.signalvnoise.com/some-advice-from-jeff-bezos/" >being smart</a> in accordance to Jeff Bezos. Before I read it, I fixed stuff &#8222;stealthily&#8220;. I didn&#8217;t want to appear not knowing it beforehand.</p>
<p>I restarted at the drawing board and have been pondering about my patterns. And then I waited&#8230; until some inspiring thoughts appeared. On the way to work, in a lunch break, in the night in the bed (where I should rather sleep than thinking about issues) or while feeding my newly born baby.</p>
<p>And I came up with some changes and surprises. And I believe that the following pattern are so easy to understand, that it is a shame that I didn&#8217;t came up with earlier. But that&#8217;s usual. First digging into it, exploring the edges, finding common ground to work with and then summarizing up.</p>
<p>In my last post I split up the pipeline into 2 parts for easier consumption. In one of my meetups I learnt that to view it that way is not common. It was caused by the way how WhereScape works.</p>
<p>Here I present the enhanced vendor neutral Universal Loading Pattern. Before we start, do you think Data Vault, Dimensional Modeling or Anchor Modelling are so different? They are just flavors to work with data! I believe I can load them all with this pattern.</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern.png" ><img data-tf-not-load="1" decoding="async" class="alignnone wp-image-1722" src="https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern.png" alt="" width="700" height="2225" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern.png 2304w, https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern-63x200.png 63w, https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern-768x2441.png 768w, https://www.zemp.ch/wordpress/wp-content/uploads/2019/07/Universal-Loading-Pattern-322x1024.png 322w" sizes="(max-width: 700px) 100vw, 700px" /></a></p>
<h2>Source Query Automation</h2>
<p>Here are the bits and pieces, to help me with writing a query and taking care about all those nifty little details. But for that to work, we need a query.</p>
<h3>Extract &amp; Transform</h3>
<p>This is the place where my input to the processing of my data starts. A simple <code>SELECT</code> statement with <code>FROM</code>, <code>JOIN</code>s and <code>WHERE</code> clauses. If I&#8217;m happy with the content and the calculation, I use it.</p>
<p>Now the interesting stuff starts.</p>
<h3>Delta Loading</h3>
<p>For any of my targets I try to load and process only delta data. But I&#8217;m too tired to write that in my origin query. And I have an option implemented, to do a full load if I want to. I need to be flexible.</p>
<p>If I structure all the layers as sub queries, I can specify on the next layer just the following statement. SQL Server will still make the best of it and will apply the filter already to the origin query.</p>
<pre><code>WHERE dss_load_datetime &gt; (SELECT COALESCE(MAX(dss_load_datetime),'0001-01-01') FROM [Target])</code></pre>
<p>This works best for views. If I have a stored procedure, I would load the result of the above statement into a variable <code>@last_dss_load_date</code> and use it instead. A lot faster.</p>
<h3>Source to Target Data Type mapping</h3>
<p>This layer is only important if you care about data type consistency. Like in programming when you define variables with a specific type.</p>
<p>The mapped columns between source and target doesn&#8217;t necessarily have to be of the same data type. And if I have transformations in my source query, it is hardly known which data type I just created. Except you specify that in the source query. But I&#8217;m lazy about that. The system should fix that.</p>
<p>Any platform tries to avoid producing errors while inserting data into a table. They silently ignore the fact, that the data type don&#8217;t match. This is called implicit conversion. Have a look at the rules, what <a target="_blank" href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine?view=sql-server-2017" >SQL Server</a> does.</p>
<p>In this layer I create an explicit data type conversion. If I have transformations, I <code>CAST</code> it to the target data type. If the source and the target data type don&#8217;t match, I use some of the rules from the implicit conversion where I believe they are safe. Anything else throws an error while creating a view or stored procedure.</p>
<h2>Business Keys</h2>
<p>Last time I was thinking, yeah, a business key is just a business key. Yeah, there are some rules. I know that they are important. But I didn&#8217;t know that they are THAT important.</p>
<p>This time I want to spend more time elaborating about business keys.</p>
<p>Business Keys are really fundamental! A table without business keys is really hard to understand and work with. Until now I have been cleaning business keys only for hubs as suggested by Dan Linstedt. If we load data later to dimensions and facts, the business keys for them are already cleaned.</p>
<p>Sometimes I don&#8217;t build a data vault model but going straight from my Data Store (=Persistent Staging Area) to Dimensions &amp; Facts. Having a Data Store gives me the freedom to do that and later adding a Data Vault when the business rules are getting too complex and loading time is slow.</p>
<p>I changed my view that not only data vault objects should have their business keys cleaned, but all my tables: Data Stores, Hubs, Links, Satellite, Dimensions and Facts.</p>
<p>But wait, are we not going to change the data which it consists? Shouldn&#8217;t be the Data Store equal than the source system? If the source systems <code>CHAR</code> column has mixed cases, shouldn&#8217;t we preserve it?</p>
<p>I make strong distinctions of business key columns and data columns. All columns of a source table are by definition data columns. I would derive my business key column from a data column. Business Key columns are here to ensure uniqueness in my target objects. But if the business key column is of type <code>INTEGER</code>, there is no need to duplicate it. If you want to preserve a mixed-case <code>CHAR</code> column, duplicate and preserve it.</p>
<p>Just a side note: If the mixed-case business key column should be preserved, we would have anyway a problem. Usually database joins don&#8217;t make a distinction of mixed cases. Then I would need to specify a collation with &#8222;ignore cases&#8220; being turned off or using a binary collation.</p>
<h3>Cleaning</h3>
<p>So, how are business keys cleaned? The goal is, to get the value as close as possible to <code>NULL</code>. So far I only clean <code>CHAR</code> columns. Remove any spaces. If the data in the column is entered manually by a user and the application doesn&#8217;t check for clean data, I must even search for tabs, returns and other strange characters. The goal is to get the essence of the column value. Only human readable characters. A <code>CHAR</code> column should also avoid having mixed cases. I set them all upper case.</p>
<p>My statement is: <code>NULLIF(UPPER(LTRIM(RTRIM(column))),'')</code></p>
<p>The benefit of trying to <code>NULL</code>ify everything is, that the business key column has a constraint of <code>NOT NULL</code>. Therefore, if I hit a <code>NULL</code> it will crash and I will be safe of bad data.</p>
<p>Usually people see the business key cleaning and the following zero key replacement as a minor distinction. In this step I focus on the cleanliness of business keys. If the lineage from the source system via a data store and now to the target is kept, I clean it only once!</p>
<h3>Zero Key Replacement</h3>
<p>Here I &#8222;complete&#8220; unknown data to ensure the working of my target. Data Stores, Hubs, Links, Satellites, Dimensions, Facts have different requirements. And all have Primary Keys. Primary Key values with <code>NULL</code> don&#8217;t work. And asking somebody to bring me product &#8222;<code>NULL</code>&#8220; usually confuses people.</p>
<p>In the above step we might hit now a business key having a value of <code>NULL</code>. What do we do with it when the target tells me that it shouldn&#8217;t be <code>NULL</code>?</p>
<p>The book says for Data Vault to replace it with &#8222;-1&#8220;. &#8222;-1&#8220; is just the value to represent &#8222;unknown&#8220;.</p>
<p>Let&#8217;s take a step back. Is Data Vault the only model which has <code>NULL</code> values? Fact tables referencing Dimensions could have <code>NULL</code> values too. Can we formulate a rule for that?</p>
<p>I used to use &#8222;-1&#8220; everywhere in my templates. But I got into problems because of different loading patterns. The right thing to do is, to lookup the value on the linked objects business key meta data. Hubs and Dimensions are the objects, for which we can define zero keys. Any other object references to them to find the right zero key. In WhereScape I can define a zero key for any dimensional column. I will lookup that value and use it as a zero key. For hubs it doesn&#8217;t exist, but I would do that too, if it would exist.</p>
<p>Here are my self-defined &#8222;unknown&#8220; replacement values, if the linked objects business key has no zero key defined:</p>

<table id="tablepress-36" class="tablepress tablepress-id-36">
<thead>
<tr class="row-1">
	<th class="column-1">Data&nbsp;Type</th><th class="column-2">Zero Key</th>
</tr>
</thead>
<tbody class="row-hover">
<tr class="row-2">
	<td class="column-1">CHAR</td><td class="column-2"><code>ISNULL(source_column,'-1')</code></td>
</tr>
<tr class="row-3">
	<td class="column-1">INT</td><td class="column-2"><code>ISNULL(source_column,-1)</code></td>
</tr>
<tr class="row-4">
	<td class="column-1">BIT</td><td class="column-2"><code>ISNULL(source_column,0)</code></td>
</tr>
<tr class="row-5">
	<td class="column-1">DECIMAL</td><td class="column-2"><code>ISNULL(source_column,0)</code></td>
</tr>
<tr class="row-6">
	<td class="column-1">DATE</td><td class="column-2"><code>ISNULL(source_column,'0001-01-01')</code><br />
Unknown LoadDates are getting <code>'0001-01-01'</code> so they will update when a record appears.<br />
In the Date Dimension I use <code>'9999-12-31'</code> for the zero key, so it is shown next to the newest data.</td>
</tr>
<tr class="row-7">
	<td class="column-1">TIME</td><td class="column-2"><code>ISNULL(source_column,'00:00:00')</code></td>
</tr>
</tbody>
</table>
<!-- #tablepress-36 from cache -->
<p>As you can see, for <code>BIT</code> and <code>TIME</code> the unknown value falls into normal values. It is not possible to find values outside the scope. <code>DECIMAL</code>s are used for money values, quantities etc. Having a &#8222;-1&#8220; is very dangerous.</p>
<p>Roelant Vos has written a blog post about more <a target="_blank" href="http://roelantvos.com/blog/unknown-keys-zero-keys-or-ghost-keys-in-hubs-for-dv2-0/" >flavors of unknown</a>. Maybe this is something more for your liking.</p>
<p>Having 2 distinct rule sets for cleaning business keys, we could still hit a <code>NULL</code> value. How could that be? My fact tables are having also business keys defined. But not all business key columns have a related dimension. E.g. a line number of a sales order.</p>
<p>The result is now, that we have nice-looking business keys. Great!</p>
<h2>Surrogate Key</h2>
<p>In my previous model I had a layer to calculate hash keys and a layer to lookup dimension keys.</p>
<p>While pondering over the layers, a thought appeared: What do hash keys and dimension keys have in common? And how does a <a target="_blank" href="http://roelantvos.com/blog/using-a-natural-business-key-the-end-of-hash-keys/" >natural key</a> fit into this picture?</p>
<p>Actually, they are all surrogate keys. A surrogate key is a key to represent a business key for further usage. To make it easier and faster.</p>
<p>But what type of surrogate keys do exist? <a target="_blank" href="https://en.wikipedia.org/wiki/Surrogate_key" >Wikipedia</a> lists the following characteristics for a surrogate key:</p>
<ul>
<li>the value is system generated</li>
<li>the value is not manipulable by the user or application</li>
<li>the value contains no semantic meaning</li>
<li>the value is not visible to the user or application</li>
</ul>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Surrogate_key#Surrogates_in_practice" >Surrogate keys in practice</a> just lists sequence keys with different data types (<code>INT</code>, <code>UUID</code>, <code>GUID</code>, etc.) as valid options.</p>
<p>I would say, Dan Linstedt invented a new surrogate key in the form of a string.</p>
<p>When searching the web, I didn&#8217;t find more types of surrogate keys. So, what are the options:</p>
<h3>System-generated Surrogate Key (e.g. Sequence Key)</h3>
<p>If we have a Fact object as a target, we want to join Dimensions to it. By default, we can join on business keys saved in both objects. If Dimensions and Facts are Views on top of a Data Vault, this will be definitely that case as it is impossible, to include a system-generated sequence key into a view.</p>
<p>If we have tables, we can gain some performance when we add an auto-generated sequence key on the dimension and use that value in the fact object. Instead of a multi-column <code>CHAR</code> join, we could have now a four-byte <code>INTEGER</code> really fast join option.</p>
<p>Theoretically you can also use <code>UUID</code> or <code>GUID</code> or whatever data type is auto generated in your system. But <code>INTEGER</code> is the fastest option.</p>
<p>This was also the idea and option in Data Vault 1.0. Being really fast on joins. There the surrogate key of hubs and links is also a sequence key. The systems used at the beginning of this centenary were not that fast as today&#8217;s systems. It made sense to have something like that.</p>
<h3>User-generated Surrogate Key (e.g. Natural Key for Hubs and Links)</h3>
<p>Instead of looking up the sequence key, Dan Linstedt improved the model to load data in parallel to the target. Everybody is speaking about the variation which consists of Hash Keys and so did I.</p>
<p>But in the last year I learnt more about the patterns behind it. There are platforms for which a hash key is not suggested. (e.g. Teradata has its own mechanism to treat business keys)</p>
<p>Therefore, it makes sense to focus more on the part which is shared on all platforms and what Dan really meant about the improvement.</p>
<p>Instead of having a sequence key we would need a concatenated string of all business keys separated by a separator. Just a string without hashing.</p>
<p>It took me some time to understand what the implications for that is. How it is saved on a hub, how it moves to the link to form the natural link key which then is used in satellites. All as strings.</p>
<p>This confirmed my resolution to see this as the real surrogate key for hubs and links. A hashed version of it is just for saving space, having a consistent data type (e.g. <code>BINARY(16)</code> for MD5) and faster joins.</p>
<p>Although in Dimensions a sequence key is usually used, you could use also natural keys. Inserting data into Dimensions and Facts in parallel as in Data Vault. Do you see the intriguing similar pattern? Dan Linstedt has improved the dimensional model into the new model Data Vault. The patterns are the same. And I believe when I will dig into Anchor Modeling, I will discover the same loading pattern as shown here. Nothing new. To improve your fact and dimension views on top of a Data Vault, generate a natural key as a dimension key. Should give a better join performance.</p>
<p>This is truly a Universal Loading Pattern. Give me your flavor of modelling and I will load it.</p>
<p>To summarize, surrogate keys are just representing the business key and are used to improve performance and to make it easier to work with multi-column business keys.</p>
<h2>Data Column</h2>
<p>There is nothing we can or should do with data columns. All manipulations are in the origin source query in the first section of the pipeline.</p>
<h3>Calculate Change Diff</h3>
<p>The only thing what we can do is, to improve the loading speed to the target. While inserting the data we need to know if a row is a change to the existing row or not.</p>
<p>We can compare data in a huge <code>WHERE</code> clause, where we compare each column with the target.</p>
<p>Or we concatenate all column values into one string. Then the <code>WHERE</code> clause is just a one-liner.</p>
<p>This pattern can be used for all objects auch as Data Store, Data Vault and Data Mart.</p>
<p>Of course, saving a huge string is a waste of space. The next section will help with that.</p>
<h2>Hashing</h2>
<p>I thought hashing is the most important part in Data Vault. Actually, we need it only because of technical deficiencies of the platform.</p>
<p>In the pipeline we have now data columns, cleaned business key columns and surrogate keys.</p>
<p>Now comes the part to improve the performance (or decrease it, as hashing takes its toll on speed too). Hashing is only there to improve things. Not because we need it! If things get bad, don&#8217;t hash.</p>
<p>Instead of using natural keys as long strings, we could calculate and use a &#8222;representation&#8220; value of that. A representation! <a target="_blank" href="https://en.wikipedia.org/wiki/Hash_function" >Hashing</a> would be an ideal way to accomplish that, as it transforms data of arbitrary size into a fixed size.</p>
<p>We can hash only the natural keys and change diffs.</p>
<p>That&#8217;s it! Nothing more to write. Stupid boring hashing.</p>
<p>By the way, have you noticed the multiple use of &#8222;representation&#8220;? A business key is represented by a surrogate key which is further represented by a hash value. Just to make it easy and fast to work with data.</p>
<h2>UNION ALL</h2>
<p>So far, we talked only about one data set, one source query. What about having multiple data sets which are loaded to the same destination?</p>
<p>Here is the layer to merge them all into one data set before continuing to the next steps.</p>
<h2>Row Condensing</h2>
<p>All steps before were source oriented. Now we change view to the target. What do we need to do, to load data to the target?</p>
<p>Which data is necessary to preserve and what can we omit?</p>
<h3>Removing Duplicates</h3>
<p>Depending on the target object type, we can filter out now unneeded data. The target object tells us what it needs.</p>

<table id="tablepress-33" class="tablepress tablepress-id-33">
<thead>
<tr class="row-1">
	<th class="column-1">Target&nbsp;Object</th><th class="column-2">Rule</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Data Store</td><td class="column-2">We need only changes in rows partitioned by Business Key and sorted by LoadDate and SequenceNo</td>
</tr>
<tr class="row-3">
	<td class="column-1">Hub</td><td class="column-2">We need only the first row partitioned by Hub Hash Key and sorted by LoadDate</td>
</tr>
<tr class="row-4">
	<td class="column-1">Link</td><td class="column-2">We need only the first row partitioned by Link Hash Key and sorted by LoadDate</td>
</tr>
<tr class="row-5">
	<td class="column-1">Satellite</td><td class="column-2">We need only changes in rows partitioned by Surrogate Key and sorted by LoadDate (and sorted by EventDate, if we don't need to persist every event)</td>
</tr>
<tr class="row-6">
	<td class="column-1">Dimension</td><td class="column-2">Dimensions should be already unique. No condensing.</td>
</tr>
<tr class="row-7">
	<td class="column-1">Fact</td><td class="column-2">Facts should be already unique. No condensing.</td>
</tr>
</tbody>
</table>
<!-- #tablepress-33 from cache -->
<p>Ok, people are using <code>DISTINCT</code> for hub loading. But this works only for a single batch, a single LoadDate. If you load data in a full data set as I do, we need somehow to get 1 row out. Sorting it is the easiest form.</p>
<p>If you want to learn more about row condensing and more advanced forms, check Roelant&#8217;s <a target="_blank" href="http://roelantvos.com/blog/?s=condensing" >blog</a>.</p>
<h3>Compare last saved row with first new row</h3>
<p>When we load data incrementally to Data Stores or Satellites, it could happen, that a particular row with the same change hash is already present in the target.</p>
<p>Therefore, we can filter out all already existing rows in the target.</p>
<p>We have now a clean and relevant data set, ready to get inserted.</p>
<h2>Timeline Calculation</h2>
<p>Data Vault evolved in the meantime into an insert-only model. All timeline calculations are deferred to views generated on top of a satellite.</p>
<p>As you know, I try to make my work as easy as possible. For creating views, I use this very same pipeline we have been discussing so far. If any of my targets has any of the specific timeline columns, it kicks in. No matter if the object is a table or a view.</p>
<p>Therefore, let&#8217;s discuss, how a timeline is built.</p>
<p>I maintain 2 timelines. One is based on the LoadDate, the other on EventDate.</p>
<p>For LoadDate I have the following columns:</p>
<ul>
<li>dss_start_datetime</li>
<li>dss_end_datetime</li>
<li>dss_is_current</li>
</ul>
<p>For EventDate I have the following columns:</p>
<ul>
<li>dss_effectivity_datetime</li>
<li>dss_expiry_datetime</li>
</ul>
<p>Currently I have no template to build bi-temporal objects. Hopefully&#8230; some when&#8230;</p>
<p>In a view we just use the calculation. In a table if one of the following columns exists, we need to <code>UPDATE</code> outdated rows after <code>INSERT</code>ing new data to a table:</p>
<ul>
<li>dss_end_datetime</li>
<li>dss_is_current</li>
<li>dss_expiry_datetime</li>
</ul>
<h3>WHERE (NOT) EXISTS</h3>
<p>Now comes the last test. If the data to get inserted already exists, I don&#8217;t want to cause any primary key violation.</p>
<p>What are the rules to detect existing rows:</p>

<table id="tablepress-34" class="tablepress tablepress-id-34">
<thead>
<tr class="row-1">
	<th class="column-1">Target Object</th><th class="column-2">Unique Constraint Columns</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Data Store</td><td class="column-2">Business Key, Load Date and Sequence No</td>
</tr>
<tr class="row-3">
	<td class="column-1">Hub</td><td class="column-2">Business Key or Natural Key</td>
</tr>
<tr class="row-4">
	<td class="column-1">Link</td><td class="column-2">Surrogate Keys from Hubs</td>
</tr>
<tr class="row-5">
	<td class="column-1">Satellite</td><td class="column-2">Surrogate Key (from Hub or Link) and LoadDate</td>
</tr>
<tr class="row-6">
	<td class="column-1">Dimension</td><td class="column-2">Business Key</td>
</tr>
<tr class="row-7">
	<td class="column-1">Fact</td><td class="column-2">Business Key</td>
</tr>
</tbody>
</table>
<!-- #tablepress-34 from cache -->
<p>The unique constrain can be created with the table and this is the true check for existing data.</p>
<p>But&#8230; Database systems might not behave as expected. In a <code>WHERE</code> clause, a database system might try to match characters with each other like <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support" >SQL Server</a> does. It will not find a difference in &#8222;ss&#8220; and &#8222;ß&#8220;. Set a binary collation in the <code>WHERE</code> clause or create the Business Key <code>CHAR</code> columns with a binary collation.</p>
<p>Don&#8217;t use hash keys for comparison. An accidental hash collision will be skipped and you are bereft of telling a story that you found one. The primary key constraint violation on the hash key will tell you that you found one. I believe, winning in a lottery is easier than finding a hash collision.</p>
<p>If you load Hubs and Links separately from Satellites (like <a target="_blank" href="http://roelantvos.com/blog/embrace-your-persistent-staging-area-for-eventual-consistency/" >here</a>), this step can happen already after the Business Key cleaning. No need to go into the slow hashing function.</p>
<h2>INSERT, UPDATE, DELETE</h2>
<p>When we know now, if the data already exists or not, we can fire now the commands to change the data.</p>

<table id="tablepress-35" class="tablepress tablepress-id-35">
<thead>
<tr class="row-1">
	<th class="column-1">Target&nbsp;Object</th><th class="column-2">NOT&nbsp;EXISTS</th><th class="column-3">EXISTS</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Data Store</td><td class="column-2"><code>INSERT</code></td><td class="column-3">Skipped. There can't be any changes in data.</td>
</tr>
<tr class="row-3">
	<td class="column-1">Hub</td><td class="column-2"><code>INSERT</code></td><td class="column-3">Skipped</td>
</tr>
<tr class="row-4">
	<td class="column-1">Link</td><td class="column-2"><code>INSERT</code></td><td class="column-3">Skipped</td>
</tr>
<tr class="row-5">
	<td class="column-1">Satellite</td><td class="column-2"><code>INSERT</code></td><td class="column-3">Skipped. A business vault satellite should rather be truncated and reloaded, if a business rule changes.</td>
</tr>
<tr class="row-6">
	<td class="column-1">Dimension</td><td class="column-2"><code>INSERT</code></td><td class="column-3"><code>UPDATE</code></td>
</tr>
<tr class="row-7">
	<td class="column-1">Fact</td><td class="column-2"><code>INSERT</code></td><td class="column-3">Nothing, <code>DELETE</code> &amp; <code>INSERT</code> or <code>UPDATE</code> (depending on the way you work with facts and what is faster)</td>
</tr>
</tbody>
</table>
<!-- #tablepress-35 from cache -->
<h2>Persistent Points</h2>
<p>In the diagram above you see some points in the pipeline. The whole pipeline could work without interruption if your database system can handle all the rules in a timely manner.</p>
<p>I discovered to persist data into a temp table before hashing, increases performance. SQL Server has sometimes odd behaviors. Calculating 100m hashes and then taking the newest 100.000 in a delta load. Very bad.</p>
<p>The next point is the turning point from source to target. Having a temporary object helps with loading several targets such as Hubs, Links and Satellites in parallel.</p>
<p>It can help also with the next step: To Update timelines.</p>
<h2>Post-Process Update Timeline</h2>
<p>This step is only needed, if we have persisted timeline end dates. The end date of an existing record changes from &#8218;9999-12-31&#8216; to the start date of the new record.</p>
<p>If we have a temporary object from above, we only need to update the changed timelines for a given Business Key. All others can be skipped.</p>
<h2>Summary</h2>
<p>Quite a change from my last blog post about <a href="https://www.zemp.ch/2018/04/07/universal-loading-pattern/" >Universal Loading Pattern</a>. It has a lot more context now.</p>
<p>For me the biggest break-through was to emphasize on Business Keys and Surrogate Keys for any given object. Everything else will follow. And using hashes is just a technical necessity. Nothing else.</p>
<p>I hope it will help somebody designing loading procedures for any Data Warehouse Automation solution. Or at least being a checklist, if you have thought about all transformation rules to load data to a target.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1636" data-postid="1636" class="themify_builder_content themify_builder_content-1636 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2019/08/30/universal-loading-pattern-reloaded/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1636</post-id>	</item>
		<item>
		<title>Data Warehouse Naming Conventions</title>
		<link>https://www.zemp.ch/2019/08/10/data-warehouse-naming-conventions/</link>
					<comments>https://www.zemp.ch/2019/08/10/data-warehouse-naming-conventions/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 10 Aug 2019 11:37:54 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1207</guid>

					<description><![CDATA[As the amount of objects in my data warehouse is increasing, structuring them became an important topic. The goal is, that also new employees will grasp the content immediately. There are different naming conventions on the internet, but they usually just tell about prefixing objects. This is probably the easy part. How about structuring hubs [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>As the amount of objects in my data warehouse is increasing, structuring them became an important topic. The goal is, that also new employees will grasp the content immediately.</p>
<p>There are different naming conventions on the internet, but they usually just tell about prefixing objects. This is probably the easy part. How about structuring hubs with 100s of satellites?</p>
<p>There is no general rule. I believe, that most other developers like to develop their own style. And when you have a look into the data warehouse maybe you can identify who was working on them. Like people who can tell, which code came from which hacker or virus programmer.</p>
<p>So this is my style!</p>
<h2>Projects</h2>
<p>In WhereScape I work with projects. A project is just a collection of objects. And these projects can be grouped.</p>
<p>My project numbering follows the value flow. It starts with the load from the source and ends with the export to destinations.</p>
<p>I understand that there is also different approach to have a more holistic approach, combining all steps into one project, and deploy that as a whole. This didn&#8217;t work for me. If we would be a huge team, I would reconsider that.</p>

<table id="tablepress-28" class="tablepress tablepress-id-28">
<thead>
<tr class="row-1">
	<th class="column-1">Step</th><th class="column-2">Project</th><th class="column-3">Description</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">1</td><td class="column-2">Load</td><td class="column-3">Landing Area. Import all external data into this area</td>
</tr>
<tr class="row-3">
	<td class="column-1">2</td><td class="column-2">Data&nbsp;Store</td><td class="column-3">Persistant Staging Area. Historize all data from the source ever received.</td>
</tr>
<tr class="row-4">
	<td class="column-1">3</td><td class="column-2">Data&nbsp;Vault</td><td class="column-3">Build Data Vault Objects like Hubs, Links and Satellites. <br />
I don't separate Raw &amp; Business Vault. The lineage will tell what is raw and what is calculated.</td>
</tr>
<tr class="row-5">
	<td class="column-1">4</td><td class="column-2">Data&nbsp;Mart</td><td class="column-3">Build Data Mart Objects like Dimensions, Facts and Aggregations.</td>
</tr>
<tr class="row-6">
	<td class="column-1">5</td><td class="column-2">Reports</td><td class="column-3">Reporting Area managed by BI</td>
</tr>
<tr class="row-7">
	<td class="column-1">6</td><td class="column-2">User generated objects</td><td class="column-3">Reporting Area managed by the User</td>
</tr>
<tr class="row-8">
	<td class="column-1">7</td><td class="column-2">System objects</td><td class="column-3">Preparing the data for any platforms we manage like SQL Analysis Server, Elastic Search. Just Views.</td>
</tr>
<tr class="row-9">
	<td class="column-1">8</td><td class="column-2">Exports</td><td class="column-3">Exporting Data to any foreign system. Historize all data sent.</td>
</tr>
<tr class="row-10">
	<td class="column-1">9</td><td class="column-2">Meta</td><td class="column-3">Meta data, maintenance, script collection, etc.</td>
</tr>
</tbody>
</table>
<!-- #tablepress-28 from cache -->
<p>The list above are just the main nodes. The next 1-n digits are used to order the projects according to the execution steps.</p>
<h2>Databases</h2>
<p>Consequentially I use the naming convention also for my databases.</p>

<table id="tablepress-29" class="tablepress tablepress-id-29">
<thead>
<tr class="row-1">
	<th class="column-1">Database Name</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1"><code>bi_10_load_{partner/system}</code></td>
</tr>
<tr class="row-3">
	<td class="column-1"><code>bi_20_data_store_{partner/system}</code></td>
</tr>
<tr class="row-4">
	<td class="column-1"><code>bi_30_data_vault</code></td>
</tr>
<tr class="row-5">
	<td class="column-1"><code>bi_40_data_mart_{area}</code></td>
</tr>
<tr class="row-6">
	<td class="column-1"><code>bi_50_report_{area}</code></td>
</tr>
<tr class="row-7">
	<td class="column-1"><code>bi_60_user_{department/user}</code></td>
</tr>
<tr class="row-8">
	<td class="column-1"><code>bi_70_system_{name}</code></td>
</tr>
<tr class="row-9">
	<td class="column-1"><code>bi_80_export_{partner/system}</code></td>
</tr>
</tbody>
</table>
<!-- #tablepress-29 from cache -->
<p>Usually I have a main database without a suffix. If I see that the source or the area will get quite big, I separate them.</p>
<h2>Objects</h2>
<p>Here a list of how I name my main objects:</p>

<table id="tablepress-30" class="tablepress tablepress-id-30 tbody-has-connected-cells">
<thead>
<tr class="row-1">
	<th class="column-1">Object Type</th><th class="column-2">Naming Scheme</th><th class="column-3">Description</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Load</td><td class="column-2"><code>load_{partner/system}_{table}_<br />
{version}__{group}</code></td><td class="column-3">Landing area. <br />
For file loading I add a version to it. See my <a href="https://www.zemp.ch/2018/11/10/how-to-organize-a-raw-data-lake/" >blog</a><br />
For loading data in parallel from multiple same looking databases, I use "group" to separate them.</td>
</tr>
<tr class="row-3">
	<td rowspan="2" class="column-1">Data Store</td><td class="column-2"><code>ds_{partner/system}_{table}</code></td><td class="column-3">Import oriented history</td>
</tr>
<tr class="row-4">
	<td class="column-2"><code>ds_exp_{partner/system}_{table}</code></td><td class="column-3">Export oriented history</td>
</tr>
<tr class="row-5">
	<td class="column-1">Hub</td><td class="column-2"><code>hub_{business_concept}_{bk_space}</code></td><td class="column-3">Depending on the way you save business keys (concatenated or composite) you might come into the situation to have multiple same hubs with different business keys. Separate them by business key space (it is not the source system!)</td>
</tr>
<tr class="row-6">
	<td class="column-1">Link</td><td class="column-2"><code>link_{hub}_{hub}..._{bk_space}</code></td><td class="column-3">Hubs are sorted by size and importance</td>
</tr>
<tr class="row-7">
	<td rowspan="3" class="column-1">Satellite</td><td class="column-2"><code>sat_{hub}_{content}</code></td><td class="column-3">Hub Satellite. I don't separate them by raw or business vault. Content rules! If I'm interested what is raw and was calculated I follow the lineage.</td>
</tr>
<tr class="row-8">
	<td class="column-2"><code>lsat_{link}_{content}</code></td><td class="column-3">Link Satellite</td>
</tr>
<tr class="row-9">
	<td class="column-2"><code>lsat_{link}_eff</code></td><td class="column-3">Link Satellite for effectivity</td>
</tr>
<tr class="row-10">
	<td class="column-1">PIT</td><td class="column-2"><code>pit_{hub/link}</code></td><td class="column-3">Point-in-time object</td>
</tr>
<tr class="row-11">
	<td class="column-1">Bridge</td><td class="column-2"><code>bridge_{content}</code></td><td class="column-3">Bridge object</td>
</tr>
<tr class="row-12">
	<td class="column-1">Dimension</td><td class="column-2"><code>dim_{dimension}_{area}</code></td><td class="column-3">If there are more similar dimensions, separate them by area</td>
</tr>
<tr class="row-13">
	<td class="column-1">Fact</td><td class="column-2"><code>fact_{fact}_{area}</code></td><td class="column-3">If there are more similar facts, separate them by area</td>
</tr>
<tr class="row-14">
	<td rowspan="8" class="column-1">View</td><td class="column-2"><code>{object}__{description}</code></td><td class="column-3">Views for specific purpose</td>
</tr>
<tr class="row-15">
	<td class="column-2"><code>{object}__current</code></td><td class="column-3">Shows the current active record of a business key (e.g. Data Store)</td>
</tr>
<tr class="row-16">
	<td class="column-2"><code>{object}__first</code></td><td class="column-3">Shows the first value of a business key</td>
</tr>
<tr class="row-17">
	<td class="column-2"><code>{object}__last</code></td><td class="column-3">Shows the last known value of a business key (e.g. Data Store, Satellite)<br />
In contrast to current, a record could be marked as deleted.</td>
</tr>
<tr class="row-18">
	<td class="column-2"><code>{object}__timeline</code></td><td class="column-3">Shows the timeline with start date and end date based on dss_load_datetime or dss_event_datetime</td>
</tr>
<tr class="row-19">
	<td class="column-2"><code>{object}__infinity</code></td><td class="column-3">Similar to timeline but the first start date is 0001-01-01</td>
</tr>
<tr class="row-20">
	<td class="column-2"><code>{object}__cube</code></td><td class="column-3">Specific transformation for cubes</td>
</tr>
<tr class="row-21">
	<td class="column-2"><code>error_{object}__{rule}</code></td><td class="column-3">Error views to automatically QA the data inside the object or compare against former objects for completeness of data. Are checked daily.</td>
</tr>
<tr class="row-22">
	<td class="column-1">Stage</td><td class="column-2"><code>stage_{object}__{step_id}_{step_name}</code><br />
<code>stage_{object}__{step_id}_{step_name}__<br />
{sub_step_id}_{sub_step_name}</code></td><td class="column-3">Temporary tables and views to organize the calculation steps towards the target.<br />
Next time I will rather name that <code>temp</code> instead of <code>stage</code>.<br />
All steps report results to the upper level. Sub-steps are kind of sub-routines to report back results to the main steps</td>
</tr>
<tr class="row-23">
	<td class="column-1">Source Mapping</td><td class="column-2"><code>{object}__{source_object}</code></td><td class="column-3">WhereScape specific object to map multiple sources to one target.</td>
</tr>
<tr class="row-24">
	<td rowspan="4" class="column-1">Procedures</td><td class="column-2"><code>update_{object}</code></td><td class="column-3">Default unmodified procedure created by automation solution. Updatable.</td>
</tr>
<tr class="row-25">
	<td class="column-2"><code>custom_{object}</code></td><td class="column-3">Modified procedure created by developer</td>
</tr>
<tr class="row-26">
	<td class="column-2"><code>user_{}</code></td><td class="column-3">User-defined procedures for shared functionality</td>
</tr>
<tr class="row-27">
	<td class="column-2"><code>meta_{}</code></td><td class="column-3">Meta data procedures</td>
</tr>
</tbody>
</table>
<!-- #tablepress-30 from cache -->
<p>Double-underscore &#8222;__&#8220; have the meaning of being &#8222;interpretation&#8220; or &#8222;steps&#8220;. The main object name has only dashes to separate the words.</p>
<h2>Columns</h2>
<p>And here is the list about all my columns and system columns used. Quite a lot.</p>

<table id="tablepress-31" class="tablepress tablepress-id-31">
<thead>
<tr class="row-1">
	<th class="column-1">Column Name</th><th class="column-2">Description</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1"><code>link_{link}_key</code></td><td class="column-2">Link Hash Key</td>
</tr>
<tr class="row-3">
	<td class="column-1"><code>hub_{hub}_key</code></td><td class="column-2">Hub Hash Key</td>
</tr>
<tr class="row-4">
	<td class="column-1"><code>dim_{dimension}_key</code></td><td class="column-2">Dimension Key</td>
</tr>
<tr class="row-5">
	<td class="column-1"><code>business key columns</code></td><td class="column-2">Business Keys. No rule.</td>
</tr>
<tr class="row-6">
	<td class="column-1"><code>data columns</code></td><td class="column-2">Data columns. No rule.</td>
</tr>
<tr class="row-7">
	<td class="column-1"><code>event_datetime</code></td><td class="column-2">If we need to persist all events (also with no changes in data). Here it will get into the dss_change_hash and therefore persisted.</td>
</tr>
<tr class="row-8">
	<td class="column-1"><code>is_active</code></td><td class="column-2">Used as a data column in effectivity satellites. A satellite without content doesn't exist in my system.</td>
</tr>
<tr class="row-9">
	<td class="column-1"><code>dss_data_set_type</code></td><td class="column-2">Used to help to process FULL, DELTA and CDC loads into Data Stores</td>
</tr>
<tr class="row-10">
	<td class="column-1"><code>dss_event_datetime</code></td><td class="column-2">The event datetime can also be used in the data area. Here it is used to automate the timeline calculation</td>
</tr>
<tr class="row-11">
	<td class="column-1"><code>dss_effectivity_datetime</code></td><td class="column-2">Calculate the start date based on dss_event_datetime</td>
</tr>
<tr class="row-12">
	<td class="column-1"><code>dss_expiry_datetime</code></td><td class="column-2">Calculate the end date based on dss_event_datetime</td>
</tr>
<tr class="row-13">
	<td class="column-1"><code>dss_load_datetime</code></td><td class="column-2">The first time the record appeared in my data warehouse. See my <a href="https://www.zemp.ch/2018/10/21/praise-be-to-the-loaddate/" >blog</a>.</td>
</tr>
<tr class="row-14">
	<td class="column-1"><code>dss_sequence_no</code></td><td class="column-2">Numbering of a loading batch. Either on the way in or calculating after landing the batch.<br />
Persisted in Data Stores to form a primary key, to be added as a sub-sequence key in satellites (=multi-active) or alternatively manipulate in edge cases the dss_load_datetime in satellites.</td>
</tr>
<tr class="row-15">
	<td class="column-1"><code>dss_record_source</code></td><td class="column-2">Where does the data come from.<br />
{partner}_{database}_{schema}_{table}<br />
{file path}</td>
</tr>
<tr class="row-16">
	<td class="column-1"><code>dss_change_hash</code></td><td class="column-2">Change Hash of all data columns. Easier to compare only 1 columns with the target if something has changed than comparing all columns with the target.</td>
</tr>
<tr class="row-17">
	<td class="column-1"><code>dss_start_datetime</code></td><td class="column-2">Calculate the start date based on dss_load_datetime</td>
</tr>
<tr class="row-18">
	<td class="column-1"><code>dss_end_datetime</code></td><td class="column-2">Calculate the end date based on dss_load_datetime</td>
</tr>
<tr class="row-19">
	<td class="column-1"><code>dss_is_current</code></td><td class="column-2">Flag a record as current or old in Data Stores</td>
</tr>
<tr class="row-20">
	<td class="column-1"><code>dss_is_active</code></td><td class="column-2">Flag a record as active or deleted in Data Stores</td>
</tr>
<tr class="row-21">
	<td class="column-1"><code>dss_create_datetime</code></td><td class="column-2">Datetime when a record got inserted into the table</td>
</tr>
<tr class="row-22">
	<td class="column-1"><code>dss_update_datetime</code></td><td class="column-2">Datetime when a record got changed in the table</td>
</tr>
</tbody>
</table>
<!-- #tablepress-31 from cache -->
<p>The list above is also my sort order. The system sorts automatically the columns accordingly.</p>
<h2>Templates</h2>
<p>And for those who work with templates and automation, here my list of naming conventions:</p>

<table id="tablepress-27" class="tablepress tablepress-id-27">
<tbody class="row-striping row-hover">
<tr class="row-1">
	<td class="column-1">DDL</td><td class="column-2"><code>{company}_{technology}_ddl_<br />
{table/view}_{object}__{description}</code></td><td class="column-3">A DDL generator is always technology dependent. It creates views or tables. Objects are from above and descriptions are all those different views, such as <code>last</code> or <code>current</code></td>
</tr>
<tr class="row-2">
	<td class="column-1">Procedure</td><td class="column-2"><code>{company}_{technology}_procedure_<br />
{object}_{special_case}</code></td><td class="column-3">Creates loading procedures to load data into objects.<br />
Stored procedures are technology dependent. </td>
</tr>
<tr class="row-3">
	<td class="column-1">Script</td><td class="column-2"><code>{company}_{technology}_script_<br />
{object}_{special_case}</code></td><td class="column-3">Creates loading procedures to load data into objects.<br />
Scripts are usually technology dependent. They might be written in PowerShell, Python or other languages, but they load data to a specific environment. Although they might all talk SQL, they behave different and can be optimized differently.</td>
</tr>
<tr class="row-4">
	<td class="column-1">Block</td><td class="column-2"><code>{company}_{technology}_block_<br />
{command}_{topic}</code></td><td class="column-3">Reusable patterns to build DDLs and procedures. Commands are SELECT, INSERT, UPDATE or DELETE.</td>
</tr>
<tr class="row-5">
	<td class="column-1">Utility</td><td class="column-2"><code>{company}_utility_{topic}<br />
{company}_{technology}_utility_{topic}</code></td><td class="column-3">Library to support all above. I have configuration, variables, elements, snippets,<br />
metadata, ddl, procedure, etc.<br />
Both technology agnostic and technology dependent.</td>
</tr>
</tbody>
</table>
<!-- #tablepress-27 from cache -->
<p>I hope, somebody finds that useful. If you have more stuff to add or have another approach, please get in touch. I&#8217;m interested in learning your approach.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1207" data-postid="1207" class="themify_builder_content themify_builder_content-1207 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2019/08/10/data-warehouse-naming-conventions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1207</post-id>	</item>
		<item>
		<title>The Hub-lemma (a.k.a. Hub Dilemma)</title>
		<link>https://www.zemp.ch/2019/04/13/the-hub-lemma/</link>
					<comments>https://www.zemp.ch/2019/04/13/the-hub-lemma/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 13 Apr 2019 11:02:50 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1328</guid>

					<description><![CDATA[When I started to learn and work with Data Vault, I encountered the brilliance of hubs. It has taken me some time to adapt to the idea, that a hub can be loaded from more than 1 source system – if the Business Keys are well defined. Then I got my training and I still see [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>When I started to learn and work with Data Vault, I encountered the brilliance of hubs. It has taken me some time to adapt to the idea, that a hub can be loaded from more than 1 source system – if the Business Keys are well defined.</p>
<p>Then I got my training and I still see a slide with my inner eye where a whole pipeline of source systems along a business process are pumped into 1 hub. With different business keys and structure.</p>
<p>This was very intriguing. Design an industry data model and save your data in satellites around it! What a break-through!</p>
<p>But&#8230; with implementation and working with the data models, questions started to arise, and solutions had to be found.</p>
<p>At first when I started to model my hubs, I started with what was lying in front of me. Let&#8217;s take an easy example.</p>
<p>We are selling nutrition products over the internet. We have multiple web shops. In every country 1. In Switzerland 2 because of multiple languages.</p>
<p>The source system has for each web shop a database. And customers who are buying products are saved in the database which the web shop is depended on. This means if I move to another country, I have to register again. I don&#8217;t question the model, in BI we use what we get.</p>
<p>So how to ingest data? I have been extensively evaluating which business key is best. As I was teached in my certification class, I tried to avoid the usage of an <code>id</code> but failed to find a better solution. Finally, I went with <code>shop_code </code>and <code>customer_id</code>.</p>
<p>So, I created a hub with the following columns:</p>
<ul>
<li><code>hub_customer_key</code></li>
<li><code>shop_code</code></li>
<li><code>customer_id</code></li>
<li><code>dss_load_datetime</code></li>
<li><code>dss_record_source</code></li>
</ul>
<p>After I have been developing our model for the source system of our shop, I started to load data from our email campaign provider. They have also an <code>id</code> as primary key. But the better business key is <code>email</code>.</p>
<p>For each shop we have an account. It was easy to reuse the existing business key columns:</p>
<ul>
<li><code>shop_code</code> = account (same as web shop)</li>
<li><code>customer_id</code> = email address</li>
</ul>
<p>The only thing I had to change was the data type of customer_id. From <code>INT</code> to <code>NVARCHAR</code>. Everything was smooth.</p>
<p>(Of course, I could have chosen to create another hub and name it <code>hub_recipients</code>, but didn&#8217;t)</p>
<p>On top of that I built a same-as-link to travel from one system to the other.</p>
<p>On the horizon is another source system, but this doesn&#8217;t match the existing business key definition.</p>
<p>So, this is the first Hub-lemma.</p>
<h2>Hum-lemma 1: Composite vs Concatenated Business Key</h2>
<p>Although the hub definition matches, the business key looks different among source systems. How to model and ingest them?</p>
<h3>Option 1: Create separate hubs</h3>
<p>If the existing business key definition for a hub doesn&#8217;t match, create separate hubs.</p>
<p><code>hub_customer_a</code></p>
<ul>
<li><code>shop_code</code></li>
<li><code>customer_id</code></li>
</ul>
<p><code>hub_customer_b</code></p>
<ul>
<li><code>email</code></li>
</ul>
<p>The advantage is, that you have a clear view and your design doesn&#8217;t conflict with existing structure.</p>
<h3>Option 2: Concatenate business key</h3>
<p>Instead of saving each business key column in its own column, some are advocating to concatenate the business keys into 1 column, separated by a separator. E.g. <strong>DE|1234</strong> as shop_code|customer_id.</p>
<p>The advantage is that a hub looks always the same. A hash key and ONE business key column. Great for automation.</p>
<p>The disadvantage is, that you need to load the business key columns into a separate satellite. Otherwise the business key is not reachable, and a need arises to decompose the hub business key, which is slow and bad.</p>
<h2>Hub-lemma 2: Overlapping business keys</h2>
<p>We have found now ways to load &#8222;not compatible&#8220; business keys into hubs. What happens, if of a sudden a business key of a hub overlaps meaning? The easiest example is, customer 1 of source system a is somebody else than customer 1 of source system b.</p>
<p>This &#8222;threat&#8220; had been nagging me since the beginning.</p>
<h3>Option 1: Separate by connection</h3>
<p>Maybe you remember my last <a href="https://www.zemp.ch/2018/11/10/how-to-organize-a-raw-data-lake/" >blog post</a> about structuring files? Some of these elements can be reused.</p>
<p>If you have same looking databases from a source system, use the connection name as a separator.</p>
<p>As you read above, when I wrote about my journey, I found the same answer. I added <code>shop_code</code> to separate my customers from each other. In that way the customers don&#8217;t overlap.</p>
<h3>Option 2: Separate by source system</h3>
<p>If the source systems are really disparate and you believe no better business key can be found, then adding a source system name to the business key could be an option.</p>
<p>I don&#8217;t like this option. If I add the source system name as a prevention measure to everything, the whole concept of data integrating with data vault leads into absurdity and makes things over-complicated.</p>
<h3>Option 3: Multi tenancy</h3>
<p>What is multi tenancy. The Wikipedia description adapted to data means:</p>
<blockquote><p><em>Multi tenancy refers to a data architecture in which a single instance of a business key saved in a hub serves multiple tenants.</em></p></blockquote>
<p>This is nothing else than adding to every hub by design another column for the tenant.</p>
<p>In contrast to the previous option, its not about multiple source systems, its about multiple customers if we are selling our model to clients. We would have a management system to maintain the tenants and collect meta data about them and features we offer to customers which are enabled or disabled.</p>
<h3>Option 4: Separate hubs</h3>
<p>Instead of combining everything into 1 hub, building separate hubs would be an option:</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2018/12/hub-lemma-separate-hubs.png" ><img loading="lazy" decoding="async" class="aligncenter wp-image-1337" src="https://www.zemp.ch/wordpress/wp-content/uploads/2018/12/hub-lemma-separate-hubs-1024x571.png" alt="" width="500" height="279" /></a>Instead of saying that the suffix is the source system name, I would rather say this is the &#8222;business key space&#8220;. If another source system reuses the existing business key (e.g. email), there is no need to add another source system name.</p>
<h3>Option 5: Separate satellites</h3>
<p>I must admit, that I came up with this option when I searched for solutions against the threat of overlapping business keys long time ago. I had a very strong belief to not having multiple hubs with same meaning. I can&#8217;t remember who implanted that into my thoughts. Probably me ;-). My focus was to have only a few well-defined hubs and integrate any data into them.</p>
<p>The naming scheme would be:</p>
<ul>
<li><code>sat_{hub}_{space}_{content}</code> (Raw + Business Satellites)</li>
<li><code>sat_{hub}_{content}</code> (Unified golden record)</li>
</ul>
<p>My understanding was, that a business key by itself has no meaning. To give meaning I would always need to join a satellite to it. On a resent meetup I learnt, that others believe a single hub entry is key and you should be able to join every satellite to it.</p>
<p>With that option, it could happen that a hub business key has different meaning depending on the satellite I join. Usually I join only satellites with the same &#8222;business key space&#8220; together. If I need to join data from another space, I would create a same-as-link and combine data sets from both sides and use the hub twice.</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2018/12/hub-lemma-separate-satellites.png" ><img loading="lazy" decoding="async" class="aligncenter wp-image-1336" src="https://www.zemp.ch/wordpress/wp-content/uploads/2018/12/hub-lemma-separate-satellites-1024x571.png" alt="" width="500" height="279" /></a></p>
<p>The advantage is that it looks clean and tidy. I see immediately from the satellite&#8217;s name which data I can join together. And there is no discussion about hub suffixes.</p>
<p>The disadvantage of that design is, that objects are getting clumsy and you need to work wisely on joins.</p>
<p>Maybe not the best design when I think from today&#8217;s standpoint and what I learnt so far.</p>
<h2>Hub-lemma 3: Surrogate Key</h2>
<p>A surrogate key is an alternate key to access a record. Maybe you have seen in dimensional modeling a dimension key which was created on a dimension and added to facts for faster and easier lookup. This is a surrogate key.</p>
<h3>Option 1: Data Vault &#8222;0&#8220;</h3>
<p>Dan Linstedt&#8217;s definition of a hub is: <em>a unique list of business keys</em></p>
<p>There is nowhere written that a surrogate key is required to get a hub running.</p>
<p>In his certification course he told the story about the invention. The initial data vault model had no surrogate keys.</p>
<h3>Option 2: Data Vault 1</h3>
<p>At the beginning of this millennium, Dan Linstedt published his paper about Data Vault. There he wrote about the usage of an <code>INT</code> surrogate key similar to dimensional modelling.</p>
<p>This pattern is very fast on joining. But slow on loading.</p>
<p>First, not existing hub business keys need to get loaded. Then those newly created surrogate keys can be extracted to form the business key for a link load which in turns provide a new surrogate key to be used in satellites. The orchestration of this loading pattern seems to me quite challenging.</p>
<p>I have to admit that I don&#8217;t have hands-on experience with this. Just from documentation and books.</p>
<h3>Option 3: Data Vault 2</h3>
<p>The sequential loading issue had been addressed when Dan published Data Vault 2. The <code>INT</code> surrogate key was replaced with a hash key.</p>
<p>Suddenly we are able now to load all targets in parallel. A huge speed improvement.</p>
<p>The only caveat with hash keys is hash collisions. A hash collision is when suddenly 2 business keys generate 1 hash key. I don&#8217;t have one at hand but to make it clearer:</p>
<ul>
<li>&#8222;ABC&#8220; =&gt; 0A4561&#8230;</li>
<li>&#8222;ZXY&#8220; =&gt; 0A4561&#8230;</li>
</ul>
<p>In this fantasy model both keys generate the same hash value.</p>
<p>The chance that a hash collision will occur is very very very VERY rare. A hash key is used with one hub. It is not used system-wide. So the hash collision could only happen in a hub. I remember reading somewhere, to get a 50% chance of hitting a collision based on MD5, I would need to insert every second 600.000 records into 1 hub for the next 50 years.</p>
<p>Not on my system. Okay, I&#8217;m safe to use it!</p>
<h3>Option 4: Natural Key</h3>
<p>Back to the roots! Data Vault &#8222;0&#8220;. This topis was raised in a <a target="_blank" href="http://roelantvos.com/blog/using-a-natural-business-key-the-end-of-hash-keys/" >blog post</a> from Roelant Vos. And I just stumbled over a <a target="_blank" href="https://danlinstedt.com/allposts/datavaultcat/datavault-2-0-and-hash-keys-again/" >blog post</a> of Dan about this topic.</p>
<p>As there is no required need for any surrogate key, some want to move back to the origins. From a users perspective this has some benefits. If I look into a satellite and seeing a meaningless INT value or hash key, I always need to join a hub to see the business keys.</p>
<p>If you go with a concatenated business key option from above this would make it quite a good combo. Having a look into a satellite and immediately understanding the business key.</p>
<p>Just a word of caution. Using standard &#8222;text&#8220; on JOIN is not always safe. Especially on databases which have collations which translate and sort characters at their own discretion. E.g. é = e or ss = ß.</p>
<p>But who will have such keys consisting of those strange characters anyway?!? I know, nobody&#8230; Except us. We bid on keywords and people use these strange characters. I&#8217;m pushing for 100% being safe for anything. If you go for that, create the business key columns with a binary collation. Then you are safe.</p>
<p>Performance wise joining NVARCHAR(100) (with 200 bytes) takes a lot more resources than joining a binary MD5 hash key with 16 bytes or INTs with 4 bytes.</p>
<h2>Summary</h2>
<p>The hub-lemma is a broad topic. By analyzing it, we have different options. Choose which patterns help you most and consider, what you leave behind. There is no perfect solution. And if you ask for advice, everybody&#8217;s favorite answer is &#8222;it depends&#8220; ;-).</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1328" data-postid="1328" class="themify_builder_content themify_builder_content-1328 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2019/04/13/the-hub-lemma/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1328</post-id>	</item>
		<item>
		<title>OPTION(RECOMPILE)</title>
		<link>https://www.zemp.ch/2019/03/30/option-recompile/</link>
					<comments>https://www.zemp.ch/2019/03/30/option-recompile/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 30 Mar 2019 12:50:30 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1386</guid>

					<description><![CDATA[Again, a technical topic for SQL Server. At the beginning as an analyst you are going to learn to write standard SQL queries to get your desired results. With time your skills evolve, and you learn how to optimize the queries, maybe using a temporary table or a common table expression. The more you learn, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Again, a technical topic for SQL Server. At the beginning as an analyst you are going to learn to write standard SQL queries to get your desired results.</p>
<p>With time your skills evolve, and you learn how to optimize the queries, maybe using a temporary table or a common table expression. The more you learn, the more you are going to use. And suddenly it appears, as if your queries are stuck. You rewrite parts of the query, try to ease the pain of the SQL engine.</p>
<p>And some when you come across <code>OPTION</code>s, which can be added to your table, your joins, your query or your stored procedure. It will tell the SQL engine how it should optimize the execution plans. Reading and optimizing execution plans is a huge playing field. I&#8217;m far from being an expert in that field. I would like to discuss and explain certain behaviors I discovered in BI and BI only. No OLTP optimization or single record stuff. In BI I work with data sets.</p>
<p>As you have discovered, my loading functions are all created with Stored Procedures. My typical layout looks like this:</p>
<pre><code>CREATE PROCEDURE [update_stage]</code><br /><code>AS</code><br /><code></code><br /><code>  -- Initialize</code><br /><code>  TRUNCATE TABLE [STAGE]</code><br /><code></code><code><br />  -- Temporary Table for Hash Calculation</code><br /><code>  CREATE TABLE #temp</code><br /><code>  INSERT INTO #temp</code><br /><code>  SELECT *</code><br /><code>  FROM   [SOURCE]</code><br /><code>  JOIN   ...</code><br /><code><br />  -- Insert new records</code><br /><code>  INSERT INTO [STAGE]</code><br /><code>  SELECT *</code><br /><code>        ,HASHBYTES(...)</code><br /><code>  FROM   #temp<br /></code></pre>
<p>This is my typical layout. The temporary table is needed, because sometimes SQL Server doesn&#8217;t know if it should calculate 100 Mio. hashes and filter after that the needed 100000 or vice-versa.</p>
<p>When I compile now the stored procedure, SQL Server tries to already create an execution plan which is saved with the stored procedure. If tables are around, it gets the counts and other meta-data and tries to figure out the best way to process.</p>
<p>Usually in my business vault satellites, the first load will be the biggest. The following loads will be delta. So, SQL Server sees all the data and thinks, wow, a lot to process. Well, THIS execution plan would be the best to process THAT lot of data.</p>
<p>On the following loads, it sees the data and the already existing execution plan and thinks, okay, we have a plan, so let&#8217;s get it done. It takes the assumption of the previous run and applies it to the small delta load. And this can lead to longer running queries or even &#8222;hanging&#8220; queries. Again, I&#8217;m not expert, I just observe.</p>
<p>Now comes the <code>OPTION</code>s into play. In particular <code>OPTION(RECOMPILE)</code>. If you google for information about it, there are plenty of voices who suggest, to not use it. Instead improve your query writing skills and build better indexes and so on. What they don&#8217;t mention is, that they are writing those queries for OLTP application. There is speed king. If you have tons of transactions and need every time to <code>RECOMPILE</code>, it is a waste of time. Totally agreed.</p>
<p>But in BI we have little bit more time. At least on my system. Still needs to be fast. But I don&#8217;t need to update my objects every minute. I&#8217;m willing to wait a second to get a decent execution plan. Longer it hardly takes.</p>
<p>So, what options do we have:</p>
<h2>RECOMPILE Stored Procedure</h2>
<pre><code>CREATE PROCEDURE [update_stage]</code><br /><code>WITH RECOMPILE</code><br /><code>AS</code><br /><code>  ...<br /><br /></code></pre>
<p><code>WITH RECOMPILE</code> on stored procedure level is a very drastic measure. Always recompile everything. Each and every piece. Therefore, no cache and no stats. No way to sniff what SQL Server has actually done when the procedure did run.</p>
<h2>RECOMPILE Query</h2>
<pre><code>CREATE PROCEDURE [update_stage]</code><br /><code>AS</code><br /><code></code><br /><code>  -- Temporary Table for Hash Calculation</code><br /><code>  INSERT INTO #temp</code><br /><code>  SELECT *</code><br /><code>  FROM [SOURCE]</code><br /><code>  JOIN ...</code><br /><code>  OPTION(RECOMPILE)</code><br /><br /><code>  -- Insert new records</code><br /><code>  INSERT INTO [STAGE]</code><br /><code>  SELECT *</code><br /><code>        ,HASHBYTES(...)</code><br /><code>  FROM #temp</code><br /><code>  OPTION(RECOMPILE)<br /></code></pre>
<p>As you can see, each statement has its own <code>OPTION(RECOMPILE)</code>. In that way, we are still able to analyze the stats afterwards – if you ever want to.</p>
<p>It is just nicer to still have some control than using the club-like option <code>WITH RECOMPILE</code>&nbsp;on stored procedure level.</p>
<h2>Conclusion</h2>
<p>Since I have introduced this measure to my procedures, I&#8217;m not surprised anymore with waits or &#8222;hanging&#8220; queries.</p>

<!--themify_builder_content-->
<div id="themify_builder_content-1386" data-postid="1386" class="themify_builder_content themify_builder_content-1386 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2019/03/30/option-recompile/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1386</post-id>	</item>
		<item>
		<title>How to organize a Raw Data Lake</title>
		<link>https://www.zemp.ch/2018/11/10/how-to-organize-a-raw-data-lake/</link>
					<comments>https://www.zemp.ch/2018/11/10/how-to-organize-a-raw-data-lake/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 10 Nov 2018 10:00:45 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1078</guid>

					<description><![CDATA[How many times did you hear in the last few years the words &#8222;Data Lake&#8220;? And how many times have you asked yourself how it should be organized? And how often did you ask Google for more information and didn&#8217;t get any valuable results and only funky buzzwords? I did a lot and found not [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>How many times did you hear in the last few years the words &#8222;Data Lake&#8220;? And how many times have you asked yourself how it should be organized? And how often did you ask Google for more information and didn&#8217;t get any valuable results and only funky buzzwords? I did a lot and found not enough answers.</p>
<p>Over the time, I figured out myself an optimal design for organizing the very first layer of a data lake or in old school language a &#8218;file repository&#8216;. And recently I completed it by asking the experts at a <a target="_blank" href="https://datavaultusergroup.de/" >DDVUG</a> meetup, where <a target="_blank" href="https://www.linkedin.com/in/olschimke/" >Michael Olschimke</a> showed us their solution. Thanks Michael.</p>
<p>So, what layers have I found:</p>
<h4>/source</h4>
<p>This layer represents the data provider. If it is from an external partner, I name it after him. If it comes from an internal system, I name it after that.</p>
<p>It could be also the technology, which can be the master of all data following. E.g. mysql, kafka, etc.</p>
<p>There are also tons of files which are produced by my company and don&#8217;t fall into any category. E.g. Excel-Files, manually maintained data, etc. I set the structure above anything, so we as a company are a <em>source</em> too.</p>
<h4>/connection</h4>
<p>If there is a chance of having multiple connections for the same source, add a <em>connection </em>layer. If there is the slightest chance of having another set of credentials, separate it.</p>
<h4>/operation</h4>
<p>Usually I start collecting data and process them. But some when in the future I start to pump data in the opposite direction. So where to put these files? Could be a completely different file repository. Or it can be combined in this repository.</p>
<p>This is the place to specify the kind of <em>operation </em>we do. I have:</p>
<ul>
<li>Import</li>
<li>Export</li>
<li>Update (log files of operation on the source system I do)</li>
<li>&#8230;</li>
</ul>
<h4>/object type</h4>
<p>This layer could be used to group the source objects into a structure. E.g.</p>
<ul>
<li>tables</li>
<li>meta</li>
<li>log</li>
<li>&#8230;</li>
</ul>
<p>Could be also a directory structure like database_name/schema_name if data from SQL systems are to be saved.</p>
<h4>/object</h4>
<p>This is the data object. In data warehousing this is the table name. Another way to describe it, is to think about all other following directories and files below summed up at this level. If a file doesn&#8217;t match the object definition or object meaning, create another one.</p>
<p>I had difficulties to find a proper word for that layer. Maybe <strong>content</strong> is more likely. Choose your favorite.</p>
<p>Examples are &#8218;customer&#8216;, &#8217;stock&#8216; or something like &#8218;marketing_cost&#8216;. You get the point.</p>
<h4>/data set type</h4>
<p>What happens if we have different data sets for the very same object?</p>
<p>I have seen the following data sets so far:</p>
<ul>
<li>Full</li>
<li>Delta</li>
<li>CDC</li>
</ul>
<p><strong>Full</strong> means that we get the files every day in full. E.g. a customer table export from an application. Or an Excel-File which is modified by the users.</p>
<p><strong>Delta</strong> means that we only get changed records. E.g. log files, transaction files.</p>
<p><strong>CDC</strong> (Change Data Capture) is a very detailed transaction log file of a database having all <code>INSERT</code>s, <code>UPDATE</code>s and <code>DELETE</code>s of the source system. Usually it is ingested directly from a database to another database.</p>
<p>If the rows in a file are just data, why do we need to separate <strong>Full</strong> and <strong>Delta</strong> at all? The only reason for separating them is, if we need to detect deleted records in the source system. Then I&#8217;m able to create a status tracking satellites or in non-data vault systems to delete the record or mark it as deleted.</p>
<p>The difference between <strong>Delta</strong> and <strong>CDC</strong> is, that <strong>CDC</strong> contains information about deleted records.</p>
<h4>/version</h4>
<p>Version? What is a version for? This principle I derived from APIs. E.g. Google API evolved from v3 to v4. What happens, if the source suddenly decides to modify the file structure? E.g. translating all column names from German to English? Or modifying from csv to json? Adding columns is not a new version. The loading procedure should cope with that.</p>
<p>The main reason for a new <em>version </em>is, if the existing loading procedure can&#8217;t process the file. Then add a new <em>version</em> and write a new loading procedure.</p>
<p>My default <em>version </em>is &#8218;1&#8216;. I have only a few objects were a &#8218;2&#8216; had to be created. But I want to be prepared! Modifying an existing layer after a need occurs, is not future-proof. Tweaking loading procedures to understand were something ends and something new starts is bad.</p>
<h4>/load_date</h4>
<p>If you are going to model your data lake or file repository to build data vault objects, this layer is a must have. You&#8217;ll want to preserve, when the data first appeared in your data lake. The definition of the <em>load_date </em>can be found <a href="https://www.zemp.ch/2018/10/21/praise-be-to-the-loaddate/" >here</a>.</p>
<p>All others can probably omit that.</p>
<h4>/group</h4>
<p>Sometimes I need to ingest data by different characteristics. E.g. per company, per shop, per account, per machine, per anything. Per day is not a valid group (see below in event date).</p>
<p>Usually the differentiator is not part of the file name but part of the directory structure of the source system. Here is the place to preserve and save it.</p>
<p>The semantics of a <em>group </em>and <em>connection </em>could be kind of equal. Sometimes it makes sense to use it as a <em>connection </em>above or as a <em>group </em>here.</p>
<p>Note to myself 1: Why not placing <em>group </em>before <em>data_set_type</em>? Because we need a loading procedure for each <em>data_set_type</em>. <em>Groups </em>and the rest can get looped through.</p>
<p>Note to myself 2: Why not placing <em>group </em>before <em>load_date</em>? Could be, but a <em>load_date </em>should be a single entity. It serves better for incremental loading instead of having it inside each group. A <em>group </em>is data and data comes at the end.</p>
<p>Note to myself 3: Why not placing <em>group</em> after <em>event_date</em>? Sure, feel free.</p>
<h4>/event_date</h4>
<p>First question: Does there exists an event date as content inside the file? If yes, all good! No need for an extra layer. Nevertheless it could be created in a Hive environment for speed purposes to partition the files.</p>
<p>Second question: Should the file have an event date? If yes and we don&#8217;t have it, we need to add this somehow to the path.</p>
<p>Maybe the file has a string in the filename like &#8218;YYYY-MM-DD&#8216; or an integer value representing a timestamp. Then add this information as an <em>event_date</em>.</p>
<p>If there is nothing available, use as a last resort the create datetime of the file.</p>
<p>Structure it as /YYYY-MM-DD or /year/month/day. As you like.</p>
<h4>/missing</h4>
<p>Do we still miss something? If a key information is missing inside the file, add it as a layer. Manipulating files to add a missing column is a no-go!</p>
<p>The only way possible to add more information is to create them outside the file content. Maybe as another layer.</p>
<p>Actually <em>group</em> and <em>event_date</em> from above are just examples of missing data.</p>
<h4>/file.ext</h4>
<p>Finally, our source file.</p>
<p>If you ever need the create datetime of the file, preserve it by renaming the file. It doesn&#8217;t survive the copy process to any cloud platform and is lost.</p>
<p>And don&#8217;t give too much meaning to a filename. To extract data from the filename is difficult and needs precise instructions, where to look for what. No automation.</p>
<p>A file represented by a filename is just data!</p>
<h4>Summary</h4>
<p>All these components can be used as directories or if your system supports it, as meta data to the files. If you use meta data, you will probably find and use even more tags to describe your files.</p>
<p>Choose which elements from above are important for you. In a directory structure, the full string will look like this:<br />
<code>/source/connection/operation/object_type/object/data_set_type/version/load_date/group/event_date/file.ext</code></p>
<p>A very very long path and people might get lost. But computers will like it, as many pieces can help to automate the loading procedures. Some systems are even suggesting to use key=value as a directory name, e.g. year=2018 or shop_code=de. This makes it even easier to automate.</p>
<p>The absolute minimum structure would be in my opinion:<br />
<code>/source/object/version</code></p>
<p>If you believe that a table with 2 versions could be automated too, then the 2nd version is not of importance ;-).</p>
<p>For ingesting data, we need a procedure for each combination of<br />
<code>/source/object/data_set_type/version</code></p>
<p>If you believe that there are way too many directories and you don&#8217;t need all these nifty things, don&#8217;t use <em>load_date</em> and modify the filename to include the omitted layers:<br />
<code>{group}__{event_date}__original_file_name.ext<br />
{group}__{event_date}__{object}.ext<br />
</code></p>
<p>Please tell me your solution, if you believe I missed a case.</p>
<p><strong>Update 16.08.2020</strong></p>
<p>In the last two years I have been working with that model. So far I didn&#8217;t miss any new type and I didn&#8217;t find any new layer. It was a joy working with that. Every file found easily its place.</p>
<p>What I discovered was that I would merge the layer <strong>data set type</strong> and <strong>version</strong> to keep <strong>version</strong>. While it was clear that a certain data set type in the file is <strong>full</strong> I changed it to <strong>delta</strong> while loading. And I found a new data set type: <strong>Multiple full</strong>. A inventory file which consisted of multiple full data sets, each for a different date.</p>
<p>Therefore the data set type is deferred to how the data is transformed to Data Store or Data Vault.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1078" data-postid="1078" class="themify_builder_content themify_builder_content-1078 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/11/10/how-to-organize-a-raw-data-lake/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1078</post-id>	</item>
		<item>
		<title>Praise be to the LoadDate</title>
		<link>https://www.zemp.ch/2018/10/21/praise-be-to-the-loaddate/</link>
					<comments>https://www.zemp.ch/2018/10/21/praise-be-to-the-loaddate/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sun, 21 Oct 2018 08:26:40 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1263</guid>

					<description><![CDATA[The best invention in my Business Intelligence career is the adoption of the concept of having a LoadDate. This should be a post to give credit to Dan Linstedt and thank him for inventing Data Vault and introducing LoadDate. When I encountered the hub-and-spoke concept of Data Vault I thought, why on earth do I [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>The best invention in my Business Intelligence career is the adoption of the concept of having a <em>LoadDate</em>.</p>
<p>This should be a post to give credit to <a target="_blank" href="https://danlinstedt.com/" >Dan Linstedt</a> and thank him for inventing Data Vault and introducing <em>LoadDate</em>.</p>
<p>When I encountered the hub-and-spoke concept of Data Vault I thought, why on earth do I need a <em>LoadDate</em>? Combining in a Satellite the HubHashKey with the <em>LoadDate</em> to form the Primary Key makes sense. But why would I need it in a Hub or a Link? Just to know when a Hub entry was first introduced to the data warehouse? The usage is universal and far reaching!</p>
<p>What is the <em>LoadDate</em>? The definition is:</p>
<blockquote><p>&#8222;The <em>LoadDate</em> is the date and time of insert for the record&#8220;</p></blockquote>
<p>It is the moment when a record is first inserted into a Data Warehouse. It is not the extract date from the source or the current datetime in the source system. It is really the <code>INSERT</code> datetime in my Data Warehouse! The first time a record exists.</p>
<p>How to ingest it:</p>
<ul>
<li>Save the value in a variable and insert it with the data set into the load table</li>
<li>Set a default constrain like <code>SYSDATETIME()</code> on the <em>LoadDate</em> column</li>
</ul>
<p>What about when the insert takes a long time with a lot of data? Do I get a <em>LoadDate</em> for every second it loads? This would be very precise. In my world one <em>LoadDate</em> is one batch. One batch could be the load of one file. The next file has a new <em>LoadDate</em>. When loading data in a loop of something, each loop has a new <em>LoadDate</em>.</p>
<p>When the record has been inserted into the load table, the <em>LoadDate</em> stays with the record. Where ever it goes! Like a tracking marker.</p>
<p>The <em>LoadDate</em> is used in:</p>
<ul>
<li>Load</li>
<li>Data Store</li>
<li>Hub</li>
<li>Link</li>
<li>Satellite</li>
<li>Dimension</li>
<li>Facts</li>
</ul>
<p>I use it everywhere!</p>
<p>In a Data Store (aka Persistent Staging Area) I use it to order the data and batch-load the data to the target. Together with the SequenceNo it forms also the Primary Key.</p>
<p>In Link and Hub I know when the first appearance of a business key happened.</p>
<p>In Raw Vault Satellites I use it to load data incrementally from the Data Store.</p>
<p>In Business Vault Satellites where multiple source tables are joined together, which <em>LoadDate</em> succeeds? If history is important, I use it to create mini-Point-in-Time tables. If history is not important, I use the most <a href="https://www.zemp.ch/2018/06/16/timeline-calculation-for-satellite-views/" >recent record</a> and calculate the <code>MAX</code> value of all <em>LoadDates</em>.</p>
<pre><code>SELECT *
      ,(
         SELECT MAX(v)
         FROM (VALUES
                 (table_1.dss_load_date)
                ,(table_2.dss_load_date)
                ,...
              ) AS VALUE(v)
       ) AS dss_load_datetime
...</code></pre>
<p>In that way I can use delta loading and it fits perfectly to my <a href="https://www.zemp.ch/2018/04/07/universal-loading-pattern/" >Universal Loading Pattern</a>.</p>
<p>How can I use it in Dimensions &amp; Facts? The main question in those objects is, when do I need to <code>UPDATE</code> columns with new data? When I was researching the best way, I used first to calculate a ChangeHash. Very time consuming! After detecting the formula above, I thought, why not use the <em>LoadDate</em> for change detection? If for a given Business Key the <em>LoadDate</em> changes, chances are high that the record needs an <code>UPDATE</code>.</p>
<p>With this approach I&#8217;m much faster now. And I&#8217;m also faster in preparing the data in a stage table as I can use the <em>LoadDate </em>for the delta loading layer too.</p>
<p>Are there any other similar concepts? In SQL Server one could use <a target="_blank" href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql" >rowversion</a> (altered from timestamp). It is a system-wide incremental number saved as a binary. But copying data into the next table will create a new rowversion except you extract the value and save it as a normal value. In my view not easy to handle and not readable. How do you know, when record 1234 was inserted?</p>
<p><a target="_blank" href="https://danlinstedt.com/" >Dan Linstedt</a>: Thanks for introducing <em>LoadDate</em> to the Business Intelligence Community and forcing users to use it correctly. It is of tremendous help! I use it in batch loading, delta loading and change detection (only in Dimensions &amp; Facts). I couldn&#8217;t do without it anymore. Thanks a lot!</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1263" data-postid="1263" class="themify_builder_content themify_builder_content-1263 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/10/21/praise-be-to-the-loaddate/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1263</post-id>	</item>
		<item>
		<title>My indexing journey</title>
		<link>https://www.zemp.ch/2018/09/01/my-indexing-journey/</link>
					<comments>https://www.zemp.ch/2018/09/01/my-indexing-journey/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 01 Sep 2018 11:22:21 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1238</guid>

					<description><![CDATA[The unprepared From my background as an accountant and controller I had by nature a good feeling for numbers. This helped me to understand, how to structure data. A couple of years ago when I started to work in a startup, I usually grabbed data directly from source systems to support our cause to tackle [&#8230;]]]></description>
										<content:encoded><![CDATA[<h3>The unprepared</h3>
<p>From my background as an accountant and controller I had by nature a good feeling for numbers. This helped me to understand, how to structure data.</p>
<p>A couple of years ago when I started to work in a startup, I usually grabbed data directly from source systems to support our cause to tackle financial issues and finding process deficits.</p>
<p>Over time the queries grew in sizes and run-time got longer. When the run-time was not any more satisfying and our excel sheets with tons of formulas and recalculations broke, the need arose to build something more reliable.</p>
<p>First, I created queries and jobs which persisted data in advance. Just a bunch of reports. But we wanted to give these data also to users. So, we started to build a data warehouse with dimensions and facts and giving access to them with a nice tool. My data journey from Kimball Star Schema to Data Vault is for another blog post. I want to focus here on indexes.</p>
<p>My first objects had no indexes. When performance got degraded, I started to investigate. In SQL Server, there is a feature called &#8222;Database Engine Tuning Adviser&#8220;. I would consider its suggestions and create indexes and statistics.</p>
<p>Time passed and processing time was growing again. At one time, I analyzed my created indexes and thought, what would happen, if I would drop all indexes? Wow, the processing time decreased from 3.5 hours to 2 hours.</p>
<p>Lessons learned: Don&#8217;t create too many indexes. Index creation uses also processing time.</p>
<h3>Proper Indexing</h3>
<p>My next adventure was about clean data. From my background as an accountant, I want to be sure that my numbers are always correct. My nightmare situation is, when data is not correct and numbers, especially financials don&#8217;t match.</p>
<p>Sometimes my Dimensions, Facts or Stage tables would create cartesian products (duplicates). The solution was to make sure, that I would never ever get duplicates. I started to use <code>UNIQUE</code> indexes.</p>
<p>The confidence in my data grew, because any error would alert me.</p>
<p>The downside of it was, that when something broke, parts of my whole pipeline stopped at that stage. If the processing time is 6 &#8211; 8 hours and users want data at 9, what happens, if my processes breaks in the night?</p>
<p>We set up a shift plan among our team, so that first thing after getting up is, to check emails on the phone if everything runs smoothly. This would save time until my users get their data. Not a satisfying solution, but with indexes breaking the load, what should I do?!?</p>
<p>Lessons learned: Indexes are great for data quality. But can break loading processes.</p>
<h3>Exploring Cloud Databases</h3>
<p>At a fast-growing startup, available data explodes. We got into a habit to regularly replace our SQL server to accommodate our increasing data need.</p>
<p>We figured out fast, that always increasing the size of our server is not satisfying. At that time Hadoop with MapReduce made its appearance. Just adding commodity hardware. No high-performance expensive single-server anymore.</p>
<p>I&#8217;m a SQL guy. I had a trauma replacing all my code somehow with MapReduce.</p>
<p>To leverage that, I started to analyze some cloud offerings such as Redshift and others.</p>
<p>When testing those analytical platforms, I discovered that none of them supported constrains. No primary key, no UNIQUE indexes. Even nowadays Azure SQL Data Warehouse does <a target="_blank" href="https://docs.microsoft.com/en-us/azure/sql-data-warehouse/sql-data-warehouse-tables-overview#unsupported-table-features" >not support</a> it. Their primal goal and use is to analyze a lot of data fast without any obstacles.</p>
<p>Their answers to my issues was, that the application must make sure, that there will be no duplicates. BI has usually no application.</p>
<p>For my accountant&#8217;s heart this was kind of no go. How to make sure that my or my colleagues programming will be always correct?</p>
<p>Lessons learned: Cloud databases are not always the answer.</p>
<h3>BI automation</h3>
<p>One way to ensure good results is, to make sure that data processing will always be the same. This is also something that Dan Linstedt inspired me, when he showed me the <a target="_blank" href="https://en.wikipedia.org/wiki/Capability_Maturity_Model_Integration" >maturity</a> diagram which he translated to a data warehouse. On the highest level you would have:</p>
<ul>
<li>BI automation</li>
<li>Parallel Teams</li>
<li>Rapid Delivery</li>
</ul>
<p>This was something I wanted to achieve. I can say now, that I&#8217;m able to produce best results for any objects I&#8217;m loading data in. Give me more developers and all will produce great data!</p>
<p>Lessons learned: Using a framework to support data processing application is a big win!</p>
<h3>No indexes</h3>
<p>A year ago when I migrated to SQL Server 2016, I discovered a new index type. Actually, it is rather new table storing type. Instead of a row store, data is now stored in column stores. Think of saving in Excel column A, B, C, etc. instead of Rows 1, 2, 3.</p>
<p>Over time, I added those <code>CLUSTERED COLUMNSTORE INDEXES</code> to all my objects (except stage tables). The immediate effect was, that I saved a lot of disc space. Usually 10 &#8211; 20% from the former disc space is used now.</p>
<p>On top of that I still had my <code>NONCLUSTERED</code> indexes. With time and analyzing execution plans I discovered, that SQL Server hardly uses any of my <code>NONCLUSTERED</code> indexes. And when it uses them, the execution plan might include nasty &#8222;Nested Loops&#8220; instead of &#8222;Hash Matches&#8220; to join 2 data sets together.</p>
<p>With growing confidence in my BI automation templates, I&#8217;m now in the situation that my application makes sure, that anybody in my team produces great results and that I can remove any <code>UNIQUE</code> constrains.</p>
<p>The immediate effect was, that our processing speed increased. Why? Data is not anymore loaded into the databases logfile for checking the <code>UNIQUE</code> constrains before inserting the data, but immediately inserting the data without any delay. Like any other cloud database.</p>
<p>I&#8217;m now kind of par with the cloud offerings. There is no need to migrate to cloud because of speed issues. Any indexes, especially <code>UNIQUE</code> indexes, are speed blockers. They don&#8217;t have them, I don&#8217;t have them. Another benefit was disc space. The indexes are using up to 2 times the space of a <code>CLUSTERED COLUMNSTORE</code> table. Saving space and time!</p>
<p>Do I have a safeguard routine? Of course! As the BI automation solution has all the metadata, I regularly query for duplicates.</p>
<p>When I look back, I started with no indexes and ended with no indexes. The difference is, that with BI automation I increased data processing quality and with <code>CLUSTERED COLUMNSTORE</code> a new technology emerged.</p>
<p>From now on I don&#8217;t need to grab my phone at first sight in the morning. The reasons for breaks now are usually external dependencies.</p>
<p>Lessons learned: Sleep is a precious good!</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1238" data-postid="1238" class="themify_builder_content themify_builder_content-1238 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/09/01/my-indexing-journey/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1238</post-id>	</item>
		<item>
		<title>Timeline calculation for Satellite views</title>
		<link>https://www.zemp.ch/2018/06/16/timeline-calculation-for-satellite-views/</link>
					<comments>https://www.zemp.ch/2018/06/16/timeline-calculation-for-satellite-views/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 16 Jun 2018 12:24:59 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1177</guid>

					<description><![CDATA[My last blogpost about the universal loading pattern has generated a huge number of visitors to my blog. In the recent months I have been working and refining the model. Today I would like to talk about the last part in that model. Views based on satellites. Satellites are usually built with INSERT only. Any logic interpreting [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>My last blogpost about the <a href="https://www.zemp.ch/2018/04/07/universal-loading-pattern/" >universal loading pattern</a> has generated a huge number of visitors to my blog. In the recent months I have been working and refining the model.</p>
<p>Today I would like to talk about the last part in that model. Views based on satellites. Satellites are usually built with <code>INSERT</code> only. Any logic interpreting the data should be deferred to a view. What kind of views do I have in our system:</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2018/06/satellite_views.jpg" ><img loading="lazy" decoding="async" class="alignnone wp-image-1178" src="https://www.zemp.ch/wordpress/wp-content/uploads/2018/06/satellite_views-1024x562.jpg" alt="" width="511" height="281" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2018/06/satellite_views-1024x562.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/06/satellite_views-200x110.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/06/satellite_views-768x422.jpg 768w" sizes="(max-width: 511px) 100vw, 511px" /></a></p>
<ul>
<li>First: Not used yet but possible, retrieves the first record of a satellites surrogate key</li>
<li>Last: Retrieves the last valid record. Could also be called &#8222;current&#8220;</li>
<li>Timeline: Generates a timeline for LoadDatetime and EventDatetime. EndDate is 9999-12-31.</li>
<li>Infinity: Generates a timeline for LoadDatetime and EventDatetime. In contrast to the previous timeline the first record in the timeline has StartDate = 0001-01-01.</li>
</ul>
<p>Especially the last option &#8222;infinity&#8220; has been of great help. For effectivity satellites it was important to get the timeline as wide as possible. But also in other circumstances like adding purchasing costs to a sales order line. Instead of searching for the first valid purchase cost which might be after the first sales I can be sure, that I always get a price.</p>
<p>At first, I had a configurable timeline where I could &#8222;overwrite&#8220; the first StartDate. Over time it became difficult to distinguish timelines with a wide date bandwidth and those without. To make it clearer for us, we decided to create 2 separate views if needed. A traditional timeline and a wide timeline. &#8222;Infinity&#8220; sounds great for that.</p>
<p>Let&#8217;s see. In our database, satellites can have 2 date columns (of course even more, but I automated those).</p>
<ul>
<li>LoadDatetime</li>
<li>EventDatetime</li>
</ul>
<p>Instead of using LoadDatetime as the first value to calculate timelines on, we use own columns to represent the timelines. This has the advantage that I don&#8217;t &#8222;overwrite&#8220; the LoadDatetime to represent &#8218;0001-01-01&#8216;. This is also the strategy of WhereScape which is Data Vault compliant.</p>
<p>What are the names used:</p>
<ul>
<li>LoadDatetime
<ul>
<li>StartDatetime</li>
<li>EndDatetime</li>
</ul>
</li>
<li>EventDatetime
<ul>
<li>EffectivityDatetime</li>
<li>ExpiryDatetime</li>
</ul>
</li>
</ul>
<p>Let&#8217;s get to the code. Easy stuff first.</p>
<h3>First &amp; Last Value views</h3>
<p>One could think that calculating a timeline first and then getting the EndDatetime = &#8218;9999-12-31&#8216; would be a nice solution. Still doable, but not fast.</p>
<p>The fastest option I found was to calculate it like that.</p>
<pre><code>SELECT *
FROM   satellite
INNER JOIN (
    SELECT hub_hash_key AS hub_hash_key
          ,MAX(dss_load_datetime) AS dss_load_datetime
    FROM   satellite
    GROUP BY hub_hash_key
) AS last_rows
ON  last_rows.hub_hash_key = satellite.hub_hash_key
AND last_rows.dss_load_datetime = satellite.dss_load_datetime</code></pre>
<p>Nice, easy and fast. For the first value replace the <code>MAX</code> function with <code>MIN</code>.</p>
<h3>Timeline &amp; Infinity views</h3>
<p>When calculating only one timeline from a date is easy. To calculate it with 2 date columns is just a little trickier because of odd but understandable behavior. A bi-temporal view comes some when in the future when I grasp it.</p>
<p>Timeline calculation uses a lot of window functions and execution plans are quite bad as it is sorting the data forth and back.</p>
<p>What are the requirements:</p>
<ul>
<li><code>dss_load_datetime</code>/<code>dss_event_datetime</code>: Stays as it is</li>
<li><code>dss_start_datetime</code>/<code>dss_effectivity_datetime</code>: Takes the datetime. For infinity views it should represent the first value of the timeline as &#8218;0001-01-01&#8216;</li>
<li><code>dss_end_datetime</code>/<code>dss_end_datetime</code>: Takes the next datetime or if there is none &#8218;9999-12-31&#8216;</li>
</ul>
<p>What will be the <code>PARTITION BY</code> clauses for each timeline:</p>
<ul>
<li><code>dss_load_datetime</code>: The <code>hub_hash_key</code> of the satellite</li>
<li><code>dss_event_datetime</code>: The <code>hub_hash_key</code> or the <code>driving_key</code> of the satellite</li>
</ul>
<p>What is the driving key? The driving key is useful for effectivity satellites to force a 1:m instead of a m:n relationship. A good blog post which describes it in-depth can be found on <a target="_blank" href="http://roelantvos.com/blog/?p=1253" >Roelant Vos&#8217;s Blog</a>.</p>
<p>From my <a href="https://www.zemp.ch/2018/04/07/universal-loading-pattern/" >universal loading pattern</a> blog post, you see that I organized all logic in sub-queries. To calculate the timeline, I need 3 sub-queries.</p>
<ol>
<li>Sorting the whole dataset by <code>dss_load_datetime</code></li>
<li>Calculate <code>dss_start_datetime</code> and <code>dss_end_datetime</code></li>
<li>Calculate <code>dss_effectivity_datetime</code> and <code>dss_expiry_datetime</code></li>
</ol>
<h4>Sorting dataset</h4>
<p>I use this function not only in satellites but also for calculating timelines in stage tables. Sometimes a cartesian product happens and produces bad results. In a cartesian product 2 or more lines look the same. It can happen that for the LoadDatetime timeline the first row is being used and for the EventDatetime timeline the second. Resulting in both records being returned and violating a unique constrain.</p>
<p>Therefore, I sort the whole dataset by <code>dss_load_datetime</code> and use that number in the following queries.</p>
<pre><code>ROW_NUMBER() OVER (PARTITION BY hub_hash_key ORDER BY dss_load_datetime ASC) AS dss_row_no</code></pre>
<h4>Calculate LoadDatetime timeline</h4>
<p>For normal timelines just take the <code>dss_load_datetime</code> as <code>dss_start_datetime</code>. For infinity timelines where we want to set the first <code>dss_start_datetime</code> as <code>'0001-01-01'</code>, we need to figure out, if this record is the first or not.</p>
<pre><code>CASE WHEN LAG(dss_load_datetime,1,NULL) 
          OVER (PARTITION BY hub_hash_key ORDER BY dss_row_no ASC) IS NULL
     THEN '0001-01-01'
     ELSE dss_load_datetime
END</code></pre>
<p>For <code>dss_end_datetime</code> we need the following window function query:</p>
<pre><code>LEAD(DATEADD(ns,-100,dss_load_datetime),1,CAST('9999-12-31' AS DATETIME2(7))) 
OVER (PARTITION BY hub_hash_key ORDER BY dss_row_no ASC )</code></pre>
<p>As you can see, we order both queries with the <code>dss_row_no</code> from the previous query. As we need both window function <code>LEAD</code> and <code>LAG</code>, SQL Server will sort the data forth and back. I didn&#8217;t find another solution for that yet.</p>
<p>Currently I use a <code>DATEADD</code> function to subtract 1 time tick to being able to use <code>BETWEEN</code> queries. If you don&#8217;t need that, just remove the <code>DATEADD</code> function.</p>
<h4>Calculate EventDatetime timelines</h4>
<p>The calculation looks like the previous one. Except that we can sort the data by <code>hub_hash_key</code> or <code>driving_key</code>.</p>
<p>In contrast to satellites where we can be sure, that we don&#8217;t get any duplicates per <code>hub_hash_key</code> and <code>dss_load_datetime</code> we can get duplicates on <code>dss_event_datetime</code>. Instead of just sorting the data by <code>dss_event_datetime</code> we use also <code>dss_load_datetime</code> represented by <code>dss_row_no</code> for sorting.</p>
<p>The calculation for the <code>dss_effectivity_datetime</code> looks like the following:</p>
<pre><code>CASE WHEN LAG(dss_event_datetime,1,NULL) 
          OVER (PARTITION BY driving_key ORDER BY dss_event_datetime ASC,dss_row_no ASC) IS NULL
     THEN '0001-01-01'
     ELSE dss_event_datetime
END</code></pre>
<p>The calculation for the <code>dss_expiry_datetime</code> looks the same as for the <code>dss_end_datetime</code>.</p>
<pre><code>LEAD(DATEADD(ns,-100,dss_event_datetime),1,'9999-12-31') 
OVER (PARTITION BY driving_key ORDER BY dss_event_datetime ASC,dss_row_no ASC)</code></pre>
<p>Again, the <code>DATEADD</code> function can be removed.</p>
<p>You see that we can end up with 4 sorting functions which can have a huge impact on performance. Create 2 separate views or persist them (or not).</p>
<p>Happy sorting!</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1177" data-postid="1177" class="themify_builder_content themify_builder_content-1177 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/06/16/timeline-calculation-for-satellite-views/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1177</post-id>	</item>
		<item>
		<title>Love-hate relationship with WhereScape</title>
		<link>https://www.zemp.ch/2018/06/02/love-hate-relationship-with-wherescape/</link>
					<comments>https://www.zemp.ch/2018/06/02/love-hate-relationship-with-wherescape/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 02 Jun 2018 12:55:30 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1140</guid>

					<description><![CDATA[[Update see below] Well, I don&#8217;t know if this is the right way. But I want to share my experience about it with others. First, I want to say that WhereScape enabled me to reach my goals faster. At least now. It took a year to get to full speed. This is due to the [&#8230;]]]></description>
										<content:encoded><![CDATA[<p style="text-align: right"><strong>[Update see below]</strong></p>
<p>Well, I don&#8217;t know if this is the right way. But I want to share my experience about it with others.</p>
<p>First, I want to say that WhereScape enabled me to reach my goals faster. At least now. It took a year to get to full speed. This is due to the fact that I had first to discover, how it works and what is important to me. The first sentence of the sales consultants was: &#8222;We can do whatever you want!&#8220;.</p>
<p>You have to take that really literally! Don&#8217;t underestimate it. It is a blessing that you can form this tool to your likings. It is a curse that it takes time and has no blue print attached. Well, WhereScapes has own templates which are filled with options, accumulated from the beginning to present by user requests. Either I click through the whole process over and over again or I develop my own templates to remove manual work.</p>
<h4>Object creation</h4>
<p>It is a dream to design transfers. Create the first load object and then drag the objects or the columns from one step to next. E.g. Load → Stage&nbsp;→ Data Store&nbsp;→ Stage&nbsp;→ Satellite&nbsp;→ Stage&nbsp;→ Dimension&nbsp;→ Stage&nbsp;→ Export. So easy with drag &amp; drop. From one or multiple source tables. And then just building the right <code>JOIN</code> statements and an optional <code>WHERE</code> and <code>GROUP BY</code> clause.</p>
<p>The objects are created with the right data type with a mouse click. Stored Procedures are created without further input. Indexes are added automatically if needed. I just have to worry about business logic implementation.</p>
<p>But to get there has taken us a long time. And it has some limitations.</p>
<h4>32 bit vs 64 bit</h4>
<p>The application is still running in 32 bit. For a meta data management platform this is ok. It gets nasty when I have to work with execution.</p>
<p>My loading works with SSIS, ODBC or PowerShell. In ODBC I have to use a 32 bit ODBC driver. Calculate yourself, how much memory I can use for a data transfer from a database? Depending on row size crashes happen at around 50 to 100 Mio. records.</p>
<p>PowerShell is another beast. Windows has 3 versions of it. The 32 bit Version in the 64 bit Windows directory and vice versa. And then there is a third version as well. Figuring out which works in combination with WhereScape for a given job is tiresome. And if you need to extend PowerShell with more functionality, most modules are in 64 bit. So I have to start a 32 bit PowerShell, switching to a 64 bit version and then start executing.</p>
<h4>Object name length</h4>
<p>This limitation is really bad! RED has a limit of 64 characters for any object like tables, columns, indexes, stored procedures, etc. Only &#8222;parameters&#8220; (kind of global variable) have a length of 256 characters but the system can address only 64 of them?!?</p>
<p>The 64 characters is for all objects. So if I create a table with 64 characters, I still get errors because of other objects appending to its string.</p>
<ul>
<li>Stored Procedure: <code>update_{object name}</code></li>
<li>Index: <code>{object name}_idx_BK</code></li>
</ul>
<p>With this calculation I have only 57 characters left.</p>
<p>In 3D there is a limit at 1024 characters. Enough space to evolve.</p>
<p>I hate abbreviated object names. Nobody except me is going to understand them. If you think of something like <code>stage_{satellite}_{source system}_{source table}_{calculation step}</code>, you can see that I might run out of space easily. And when I need to compose a multi source mapping and adding <code>src_{hub}_</code> to the front, this runs out even faster.</p>
<h4>3D to RED deployment</h4>
<p>As described, there is a discrepancy of 1024 to 64 characters for object names. When bringing objects back to RED, object names are cut at 64 characters. For custom indexes it gets cut at 40 characters already. I don&#8217;t know why.</p>
<p>So I need to take care about object naming inside 3D. 3D is a nice program to design. But if I need to take care about object length for later usage in RED, it gets cumbersome.</p>
<p>My aim is currently to get a rough idea in 3D and then do the transfers in RED directly. With my customized templates this is as fast as working in 3D.</p>
<h4>Object defaults</h4>
<p>In 3D, I can set default column names with default data types. This is not possible in RED. E.g. I improved performance by switching from <code>CHAR(32)</code> to <code>BINARY(16)</code>. Or, all of my date system columns should be <code>datetime2(7)</code>. Not possible to set. Unfortunately until recently I had to manually modify them or using a QA query to modify the meta data in the database regularly. When I started writing this blog entry, I thought why not using triggers for that?!? Well, after such along time without it, I have that now in place.</p>
<p>In my opinion, users shouldn&#8217;t interfere with the application database directly. This is really really bad behavior, but unfortunately necessary.</p>
<h4>Pebble</h4>
<p>Pebble is the templating language WhereScape introduced. The intention was right to give the user the flexibility to develop their own transfers. It has taken me a year to develop fail prove transfers to get industries standards mapped. It is a trade off swapping manual time with development time. But satisfying when I get more insights, how things should work.</p>
<p>With successful usage the appetit grows to get more manual work incorporated into my templates. Then the problems are starting to appear. Pebble doesn&#8217;t scale with complexity. Macros suddently stop running, variables are getting forgotten while compiling. If you don&#8217;t check each and every creation, suddenly a bad stored procedure is there. Very unstable. Then time is wasted to find alternative ways to accomplish the same output. Another developer would question my bad design. But it has to work and I can&#8217;t wait!</p>
<p>For small and easy templates without logic, Pebble is running well.</p>
<h4>Object Types</h4>
<p>I like the split for every type of table I need: Stage, Data Store, Hub, Link, Satellite, Dimension, Fact, Export, etc. I love the concept of having a load object for getting outside data into the data warehouse. This is far more logical than having a stage table directly and load that content to the target.</p>
<p>Stage is my workhorse. It is there to prepare the data from target to target. Load is also a target.</p>
<p>7 object types = 7 templates to rule them all. But bringing logic inside it was not easy. A long and steep learning curve.</p>
<h4>Stage Table and Stage View</h4>
<p>See the Key Features graphic on their website: <a target="_blank" href="https://www.wherescape.com/solutions/automation-software/wherescape-red/" >https://www.wherescape.com/solutions/automation-software/wherescape-red/</a></p>
<p>You see that there are options for:</p>
<ul>
<li>Stage View</li>
<li>Stage Table</li>
</ul>
<p>Stage View doesn&#8217;t exist!</p>
<p>All target objects like Stage, Data Store, Hub, Satellite are tables. And there is an additional object type called View. This is the only view.</p>
<p>But when developing a transfer, I don&#8217;t want to recreate the object as a view when I want to find out, if this runs faster. If I have a stage table, I want to be able to switch between a view and a table.</p>
<p><span style="font-weight: 400">Objects are grouped for each target type. So switching between the stage menu node and view menu node is cumbersome.</span></p>
<p>There is an option to create a DDL statement. I developed a template for that. But once a view, it stays a view. I can&#8217;t switch back to a table without going manually to the database and dropping the view.</p>
<h4>Data Vault</h4>
<p>WhereScape is proud of their Data Vault skills. But it has taken them very long time to get that right. To add columns to a HashKey or ChangeHash is a dream!</p>
<p>The defaults are running ok. But they forgot that hubs are loaded from multiple stage tables. And just until recently I had to circumvent that with creating fake objects and using fancy templates. And in my opinion Links are not built correctly. The Unique Constrain of Hub Hash Keys are not enforced.</p>
<p>Advanced forms don&#8217;t work:</p>
<ul>
<li>Multi-Active Satellites</li>
<li>Links with peg-legged Hubs (e.g. sales order item id for which I don&#8217;t want to form a hub)</li>
<li>Point-in-time (no template)</li>
<li>Bridge (no template)</li>
</ul>
<h4>Meta Data</h4>
<p>The application collects all the meta data necessary to implement an object. It is really an open application with nearly total freedom. You are absolutely free to implement bugs and stuff which doesn&#8217;t work.</p>
<p>At first, every team member evolved their own development style. Through discussions and documentation we agreed on some principles and layout of our objects. But things are getting forgotten or are not getting checked.</p>
<p>The need arose to write some QA queries to check for issues. This proved to be very unreliable as there are too many bits and pieces to configure for each object.</p>
<p>This is the downside of having total freedom. Other programs which are guiding you through the development steps might be easier and more consistent to work with.</p>
<h4>Jobs</h4>
<p>When developing, I create tables and stored procedure. Through lineage I see which object depend on others. This is really a great feature.</p>
<p>When adding a job based on my work, I click the needed objects together. There it should end. But&#8230; I have to group them by execution layer and setting the dependencies manually?!? By default it maps EVERY object from one layer to EVERY object in the next layer. Then I need to delete the obsolete mappings. I mean, the system knows the dependencies of each object. Why the hell I need to teach the program it again?!? I see the need of a manual overwrite. But the first draft should be done by itself.</p>
<p>Another issue is that I can set the dependencies on another job. This is great and runs well. But sometimes, especially when developing, I want to force start this job. If the dependent job didn&#8217;t run, this job will not run either. Either I delete the dependency or I add another copy of the same job without a dependency. The later we do currently.</p>
<h4>Support</h4>
<p>The support is based in New Zealand. If I have an issue which I want to show somebody, I will have to schedule here in Europe a slot in the evening to midnight.</p>
<p>Usually this is not necessary. The help provided is fast. For bugs and feature requests, you will receive a number and then the issue is forgotten. Sometimes you see according to the low number it is known for years. The development focus is on delivering more fancy features than ironing out old bugs.</p>
<p>In every release I get newly introduced bugs of stuff which worked before. Then I get an info that they will fix it for the next release. Release cycles are roughly 9 to 12 months.</p>
<p>Support for Pebble doesn&#8217;t exist. Somehow understandable, as this is an area for a lot of user questions and resources would be bound. And I believe they already experienced themselves, that Pebble is unpredictable and don&#8217;t want to waste time on it. Or maybe we are too small and they make rather big players happy. I don&#8217;t know.</p>
<h4>Implementation</h4>
<p>I envy those companies which are able to implement WhereScape in one week. The success stories of WhereScape are full of this. Maybe they sent a full team of success engineers in and get them to full speed or did that themselves.</p>
<p>Our success engineer was great. Helping us here and there with the applications default functionality. Maybe our &#8222;We can do whatever you want&#8220; was too complicated. I can&#8217;t imagine how others are doing it. I mean, I have only 7 object types. Loading e.g. a Fact or a Data Store should be everywhere the same. My aim was to get standards implemented accurately, automatically and without too much manual work. And this has taken us a lot of time!</p>
<h4>Final Words</h4>
<p>I love WhereScape for their flexibility. I hate it because I have to (re-)invent everything. Their default is too &#8222;free&#8220; and only basic. I love the drag &amp; drop of columns. I love it for creating tables, views and stored procedures instantly. I can&#8217;t do without it anymore. I hate it for their limitations and bugs. I hate/love the job engine. I love it for the speed of deploying objects to production. I hate Pebble.</p>
<p>I love/hate WhereScape!</p>
<h3>[Update 09.12.2019]</h3>
<p>I see that this blog post is sometimes on the top of google search. In the meantime I have to rectify my view about WhereScape.</p>
<p>Some of the limitations above are still true. But overall I love WhereScape. With my perfectly tuned templates we are terribly fast in developing data products. The effect is, that we don&#8217;t maintain any release cycles anymore. Sure we can do that, but my users love the rather prompt help I can offer. Telling the issue, showing a draft, finishing up.</p>
<p>I can&#8217;t imagine to work with any box moving applications like SSIS. Rather write the query, select the template and it is done. And then going to the next issue.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1140" data-postid="1140" class="themify_builder_content themify_builder_content-1140 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/06/02/love-hate-relationship-with-wherescape/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1140</post-id>	</item>
		<item>
		<title>Universal Loading Pattern</title>
		<link>https://www.zemp.ch/2018/04/07/universal-loading-pattern/</link>
					<comments>https://www.zemp.ch/2018/04/07/universal-loading-pattern/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 07 Apr 2018 11:07:57 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=1017</guid>

					<description><![CDATA[While working on the templates I published recently, I came across similar looking pattern between object types. E.g. the persisted history of a satellite looks the same as in a persistent staging area (except of the primary key of course). Or loading a dimension is like loading a hub (just that hubs have no change [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>While working on the templates I published recently, I came across similar looking pattern between object types. E.g. the persisted history of a satellite looks the same as in a persistent staging area (except of the primary key of course). Or loading a dimension is like loading a hub (just that hubs have no change hash). So, a dimension loading pattern would nearly work for hubs.</p>
<p>I concluded that instead of having 7 templates (for each object type) 2 would be enough. One for stage and one for any target. They have all similar patterns.</p>
<p>While compiling all the patterns, I came to a solution that even 1 template would be enough. A universal loading pattern. Usable for any target type!</p>
<p>As we are loading data with stored procedures generated with WhereScape, see the following captions as sub-queries. In that way we add value in each layer to the result set. Also for maintaining template code this is much easier. SQL Server will find the best way to execute them.</p>
<p>I separated them into stage and target loading for easier reading. But in reality, it is just one big data flow.</p>
<h2>Stage Loading Process</h2>
<p>What would the loading pattern for a stage object look like?</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_stage.jpg" ><img loading="lazy" decoding="async" class="size-large wp-image-1060 aligncenter" src="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_stage-666x1024.jpg" alt="" width="666" height="1024" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_stage-666x1024.jpg 666w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_stage-130x200.jpg 130w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_stage-768x1182.jpg 768w" sizes="(max-width: 666px) 100vw, 666px" /></a></p>
<p>From my first <a href="https://www.zemp.ch/2017/12/02/template-stage/" >draft</a> only little changed.</p>
<h4>Extract &amp; Transform</h4>
<p>The only user generated logic is defined here. A simple <code>SELECT</code> statement and some additional meta data about what to save in addition, e.g. hash keys, change hashes or dimension keys. Everything afterwards is automated and for data preparation.</p>
<h4>Delta Loading</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Target Object Name is defined[/themify_box]</p>
<p>A stage table can optionally have a link to its target. With that in place delta loading is possible and will add the following <code>WHERE</code> clause.</p>
<pre><code>WHERE dss_load_datetime &gt; (SELECT COALSESCE(MAX(dss_load_datetime),'0001-01-01') FROM [Target])</code></pre>
<h4>CAST to Target Data Type</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has Hash Columns[/themify_box]</p>
<p>Hashing is a beast of itself. It looks trivial, but can still be tricky. Just recently we figured out, we would need to add another step in between: Casting to Target Data Type. E.g. the source column is of data type <code>datetime</code> and the target column has <code>date</code>. Of course, nobody does that ;-)! Hashing without casting would mean that it hashes e.g. &#8218;2018-03-08 12:13:14&#8216; instead of just the date &#8218;2018-03-08&#8216;. Any rehashing later will fail. Of course, it should have been taken care about in the transformation, but other examples can be found. This step is just adding the invisible implicit transformation to a visual explicit transformation before the hashing step.</p>
<p>Another way to model it, is to create a temporary table with the same data types as the stage table and persist the result before going into the hashing function. This is anyway faster in delta loading because SQL Server comes usually up with odd execution plans.</p>
<h4>Clean Business Key for Hash Keys (other Business Keys are not touched)</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has Hash Columns[/themify_box]</p>
<p>There are many options out there. I decided to go with the following:</p>
<p>
<table id="tablepress-18" class="tablepress tablepress-id-18">
<tbody class="row-hover">
<tr class="row-1">
	<td class="column-1">CHAR Data Types</td><td class="column-2"><code>ISNULL(NULLIF(UPPER(LTRIM(RTRIM(source_column))),''),'-1')</code></td>
</tr>
<tr class="row-2">
	<td class="column-1">INT Data Types</td><td class="column-2"><code>ISNULL(source_column,-1)</code></td>
</tr>
<tr class="row-3">
	<td class="column-1">DATE Data Types</td><td class="column-2"><code>ISNULL(source_column,'0001-01-01')</code></td>
</tr>
</tbody>
</table>
</p>
<p>If a <code>CHAR</code> column is empty I set it to <code>NULL</code> and every <code>NULL</code> column is set to -1. Then remove any spaces and set it to upper case.</p>
<p>If an <code>INT</code> column is empty set it to -1. 0 (=zero) is a good value. No need to NULL it.</p>
<h4>Compute Hash Keys and Change Hash</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has Hash Columns[/themify_box]</p>
<p>
<table id="tablepress-19" class="tablepress tablepress-id-19">
<tbody class="row-striping row-hover">
<tr class="row-1">
	<td class="column-1">Hash Key</td><td class="column-2"><code>HASHBYTES('md5',<br />
    CAST(column_1 AS NVARCHAR(MAX)) +'||'+<br />
    CAST(column_x AS NVARCHAR(MAX))<br />
)</code><br />
</td>
</tr>
<tr class="row-2">
	<td class="column-1">Change Hash</td><td class="column-2"><code>HASHBYTES('md5',<br />
    ISNULL(CAST(column_1 AS NVARCHAR(MAX)),'') +'||'+<br />
    ISNULL(CAST(column_x AS NVARCHAR(MAX)),'')<br />
)</code></td>
</tr>
</tbody>
</table>
</p>
<p>Casting everything to <code>NVARCHAR(MAX)</code> is the easiest thing. <code>NVARCHAR</code> has the best implicit conversion from any data type to string. Don&#8217;t ever cast to <code>VARCHAR(MAX)</code> only. As long as there is no true UTF8 character set (with variable byte length) for <code>VARCHAR</code>, stick with UTF16 in <code>NVARCHAR</code>. Looking beautiful. Only <code>DATETIME</code> and another (which I can&#8217;t remember) are looking ugly. But as I use only <code>DATETIME2</code> I don&#8217;t come across.</p>
<h4>Dimension Lookup</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has Dimension Columns[/themify_box]</p>
<p>Just looking up the surrogate key.</p>
<h4>Row Condensing</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Business Key is defined[/themify_box]</p>
<p>Removing true duplicates. Be aware. REALLY! Wait until you hit a duplicate and talk to the owner of your source system! And only then implement it, if he can&#8217;t fix it.</p>
<h4>Load</h4>
<p>Preparing everything from the previous steps and sort the columns in the right order for the <span style="color: #222222; font-family: monospace;"><span style="background-color: #e9ebec;">INSERT</span></span> statement.</p>
<h4>Post-Process Update Dimension Key</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has Dimension Columns[/themify_box]</p>
<p>If a business key in the stage table for a fact object doesn&#8217;t exist in the dimension we could add it if this is a desired outcome.</p>
<p>After <code>INSERT</code>ing the missing business key into the dimension, we need to update the stage table.</p>
<h2>Target Loading Process</h2>
<p>The target loading can be as simple as having a short <code>INSERT</code> statement.</p>
<p>Which differences are in the target object types:</p>
<ul>
<li>Persist timeline calculation? (e.g. having EndDate in a table or calculate it through views)</li>
<li>Update existing or insert as new row ?(e.g. Dimension vs Satellite)</li>
<li>Is it allowed to insert &#8222;in-between&#8220; data? (e.g. Satellite)</li>
</ul>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_target.jpg" ><img loading="lazy" decoding="async" class="size-large wp-image-1061 aligncenter" src="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_target-696x1024.jpg" alt="" width="696" height="1024" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_target-696x1024.jpg 696w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_target-136x200.jpg 136w, https://www.zemp.ch/wordpress/wp-content/uploads/2018/03/universal_loading_pattern_target-768x1130.jpg 768w" sizes="(max-width: 696px) 100vw, 696px" /></a></p>
<h4>Row Condensing</h4>
<p>Here happens the real magic, where every target type is different. Now is the right time to figure out, if we need to persist all the rows.</p>
<p>
<table id="tablepress-22" class="tablepress tablepress-id-22">
<thead>
<tr class="row-1">
	<th class="column-1">Target Type</th><th class="column-2">Rule</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Stage</td><td class="column-2">We need only the last record by Business Key (if it is specified)</td>
</tr>
<tr class="row-3">
	<td class="column-1">Data Store</td><td class="column-2">We need only changes in rows sorted by LoadDate</td>
</tr>
<tr class="row-4">
	<td class="column-1">Hub</td><td class="column-2">We need only the first record by Hub Hash Key</td>
</tr>
<tr class="row-5">
	<td class="column-1">Link</td><td class="column-2">We need only the first record by Link Hash Key</td>
</tr>
<tr class="row-6">
	<td class="column-1">Satellite</td><td class="column-2">We need only changes in rows sorted by LoadDate and sorted by EventDate.</td>
</tr>
<tr class="row-7">
	<td class="column-1">Dimension</td><td class="column-2">Dimensions without SCD should be already unique in stage. No condensing.</td>
</tr>
<tr class="row-8">
	<td class="column-1">Fact</td><td class="column-2">It should be already unique by Business Key in stage. No condensing.</td>
</tr>
</tbody>
</table>
<!-- #tablepress-22 from cache --></p>
<p>&nbsp;</p>
<h4>Compare last saved row with first new row</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has StartDate Column[/themify_box]</p>
<p>An additional step is to check the last saved row with the first new row. Happens only in tables with a timeline column. Usually Satellites or History.</p>
<p>The result after this step is, that the data set is now cleaned, useful, ready to be inserted in the target. From a data point of view.</p>
<h4>History Columns</h4>
<p style="text-align: left;">[themify_box icon=&#8220;highlight&#8220;]Has History Columns[/themify_box]</p>
<p>The history columns can be deferred to a view. Then we have an <code>INSERT</code> only system which can have a huge performance gain. The question is, if we can wait that long for the calculation of the timeline in the view to finish. If not, we should persist the history columns.</p>
<p>Column names for the first timeline. The calculation is based on the LoadDate.</p>
<ul>
<li>dss_start_datetime</li>
<li>dss_end_datetime</li>
<li>dss_version</li>
<li>dss_current_flag</li>
</ul>
<p>Column names for the second timeline: The calculation is based on the EventDate.</p>
<ul>
<li>dss_effectivity_datetime</li>
<li>dss_expiry_datetime</li>
</ul>
<p>For templates, not all columns are needed. Just add those which you need.</p>
<p>I know, this process fails if we are talking about bi-temporal objects. I need to learn first, how to build and use them.</p>
<h4>Load</h4>
<p>This step brings all the data from the previous steps together and adds the columns, which were missing in the extract layer.</p>
<h4>Where (Not) Exists</h4>
<p>This step was the most difficult one. Not because of detecting what exists and what not, but where to place?</p>
<p>If I put it after the extract layer, it would be impossible to do a full load. All existing records are removed from the data set and the history calculation is therefore rubbish. Also in delta load are problems. What about loading data in-between LoadDates? The whole row condensing step will produce wrong results.</p>
<p>What about placing it after the row condensing layer? Seems to me the best place. I could save time calculating the history layer as I&#8217;m not going to update an existing row anyway. But I would need to <code>UPDATE</code> the timeline after the <code>INSERT</code>. Any single <code>UPDATE</code> costs me 10 times more than a proper <code>INSERT</code>.</p>
<p>And where do we have history calculations anyway? Only with satellites and data store. Therefore, it can be placed at the last place possible.</p>
<p>What is new and what is existing? This are the rules for detecting it:</p>
<p>
<table id="tablepress-23" class="tablepress tablepress-id-23">
<thead>
<tr class="row-1">
	<th class="column-1">Target Type</th><th class="column-2">Definition</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Data Store</td><td class="column-2">Business Key, Load Date and Sequence No</td>
</tr>
<tr class="row-3">
	<td class="column-1">Hub</td><td class="column-2">Hub Hash Key and Business Key (the unique constrain will tell if there is a hash collision)</td>
</tr>
<tr class="row-4">
	<td class="column-1">Link</td><td class="column-2">Link Hash Keys, Hub Hash Keys and peg-legged Business Keys (the unique constrain will tell if there is a hash collision)</td>
</tr>
<tr class="row-5">
	<td class="column-1">Satellite</td><td class="column-2">Hub Hash Key or Link Hash Key and LoadDate</td>
</tr>
<tr class="row-6">
	<td class="column-1">Dimension</td><td class="column-2">Business Key</td>
</tr>
<tr class="row-7">
	<td class="column-1">Fact</td><td class="column-2">Business Key</td>
</tr>
</tbody>
</table>
<!-- #tablepress-23 from cache --></p>
<p>Which objects could have an <code>UPDATE</code> statement? Maybe not only in delta but also in full load?</p>
<p>
<table id="tablepress-24" class="tablepress tablepress-id-24">
<thead>
<tr class="row-1">
	<th class="column-1">Target Type</th><th class="column-2">Definition</th>
</tr>
</thead>
<tbody class="row-striping row-hover">
<tr class="row-2">
	<td class="column-1">Data Store</td><td class="column-2">No, there can't be any changes in the data. Otherwise the LoadDate is used wrongly.</td>
</tr>
<tr class="row-3">
	<td class="column-1">Hub</td><td class="column-2">No. If it is of interest, when a business key became available, we can update the LoadDate if there is an earlier one.</td>
</tr>
<tr class="row-4">
	<td class="column-1">Link</td><td class="column-2">No. If it is of interest, when a business key became available, we can update the LoadDate if there is an earlier one.</td>
</tr>
<tr class="row-5">
	<td class="column-1">Satellite</td><td class="column-2">No. If by accident there are different Change Hashes for a given record which already exists, the whole calculation in stage must have changed. Then the whole satellite should be reloaded instead.</td>
</tr>
<tr class="row-6">
	<td class="column-1">Dimension</td><td class="column-2">Yes!</td>
</tr>
<tr class="row-7">
	<td class="column-1">Fact</td><td class="column-2">Yes!</td>
</tr>
</tbody>
</table>
<!-- #tablepress-24 from cache --></p>
<p>For Facts <code>DELETE</code>ing the record could be a faster option than <code>UPDATE</code>ing it.</p>
<p>Everything else is going to be <code>INSERT</code>ed.</p>
<h4>Post-Process Update Timeline</h4>
<p>[themify_box icon=&#8220;highlight&#8220;]Has History Columns for a timeline (StartDate only is not a timeline)[/themify_box]</p>
<p>In that step we are going to update the timelines of our target. By joining to the stage table, we only need to update the changed timelines per Business Key/Driving Key.</p>
<p>In our thinking we had first also a Pre-Process Update step where we would outdate old data. This works, if we run the <code>INSERT</code> batch-by-batch. But as we don&#8217;t know, if there will be any inserts at all after the &#8222;Row Condensing&#8220; layer we must omit that pre-processing step.</p>
<h4>Conclusion</h4>
<p>Above you see 2 images. Actually, in my data flow they are not separated. It is just a huge select statement incorporating every rule. Of course, empty layers are not shown.</p>
<p>Now, I can just throw any data for any target type at it. By designing it in layers, I&#8217;m flexible in adding new rules when I discover them. I&#8217;m also able to take out each layer in a separate temporary table to speed up processing time, if I figure out that this way would be faster.</p>
<p>By writing down my thinking gave clarity to the desired outcome. If you want to discuss this model or want to share your experience with me, please get in touch.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-1017" data-postid="1017" class="themify_builder_content themify_builder_content-1017 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/04/07/universal-loading-pattern/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1017</post-id>	</item>
		<item>
		<title>Pebble Template Release 1.0 for SQL Server</title>
		<link>https://www.zemp.ch/2018/03/03/pebble-template-release-1-0-for-sql-server/</link>
					<comments>https://www.zemp.ch/2018/03/03/pebble-template-release-1-0-for-sql-server/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 03 Mar 2018 12:45:52 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=990</guid>

					<description><![CDATA[In all my previous blog posts you would have had to go to my GitHub repository, loading each file to your environment and trying to get it up and running. Maybe it is time now, to package them all up into an application, which is ready to be loaded into your environment. You get templates [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>In all my previous blog posts you would have had to go to my <a target="_blank" href="https://github.com/EckisWelt/WhereScape" >GitHub</a> repository, loading each file to your environment and trying to get it up and running.</p>
<p>Maybe it is time now, to package them all up into an application, which is ready to be loaded into your environment.</p>
<p>You get templates for the following WhereScape objects</p>
<ul>
<li>Stage</li>
<li>Stage Merge</li>
<li>History (Data Store)</li>
<li>Hub</li>
<li>Link</li>
<li>Satellite</li>
<li>Dimension</li>
<li>Fact</li>
</ul>
<p>which you can create as</p>
<ul>
<li><code>TABLE</code></li>
<li><code>VIEW</code></li>
</ul>
<p>Included is also an extensive list of meta data checks. Modify it to your liking.</p>
<p>It is hopefully also a good resource if you want to learn writing templates for yourself.</p>
<p>Before using, please read the <code>LICENSE</code> and the <code>README</code>.</p>
<p>After that, your first stop is the template <code>nu3_sqlserver_utility_configuration</code>. I moved all &#8222;hard-written&#8220; code into here for modification. Just go through it and modify it to your environment.</p>
<p>If you like it and the templates are helping you, visit the code sponsors website at <a target="_blank" href="http://www.nu3.com/" >http://www.nu3.com/</a> and buy some products.</p>
<p><div class="sdm_download_button_box_default"><div class="sdm_download_link"><a href="https://www.zemp.ch/?sdm_process_download=1&download_id=1011"  class="sdm_download green" title="nu3_template_20180227" >Jetzt herunterladen!</a></div></div></p>
<!--themify_builder_content-->
<div id="themify_builder_content-990" data-postid="990" class="themify_builder_content themify_builder_content-990 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/03/03/pebble-template-release-1-0-for-sql-server/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">990</post-id>	</item>
		<item>
		<title>Virtual Data Warehouse</title>
		<link>https://www.zemp.ch/2018/01/27/virtual-data-warehouse/</link>
					<comments>https://www.zemp.ch/2018/01/27/virtual-data-warehouse/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 27 Jan 2018 10:37:37 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=986</guid>

					<description><![CDATA[So far, we have been discussing the creation of stored procedures to pump data into our hubs, satellites, facts and all the other objects. In an ideal world, I would specify my objects and good is. All data would come into their place automatically. Unfortunately, that doesn&#8217;t happen too often. Currently we create tables and [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>So far, we have been discussing the creation of stored procedures to pump data into our hubs, satellites, facts and all the other objects.</p>
<p>In an ideal world, I would specify my objects and good is. All data would come into their place automatically. Unfortunately, that doesn&#8217;t happen too often.</p>
<p>Currently we create tables and according to the template produce stored procedures to load the data. Then we create jobs to process them. We would need to check that they are in the right order, that we didn&#8217;t forget a task and that we maintain the dependencies. A lot of auxiliary work.</p>
<p>To remove all this auxiliary work, we would need to specify our objects as views instead of tables. So far WhereScape doesn&#8217;t offer this choice. Even not in stage objects. Would that be really achievable?</p>
<p>In our templates, I have written a lot of code and tried to give &#8222;intelligence&#8220; to it. What could I reuse to create views based on them? It&#8217;s the <code>SELECT</code> statement of the <code>INSERT</code> statement. No <code>UPDATES</code> and no delta loading. This pure <code>SELECT</code> statement I placed in a block template which can be reused in a procedure and in a <code>DDL</code> statement.</p>
<p>You will find now in my <a target="_blank" href="https://github.com/EckisWelt/WhereScape" >library</a> all objects for</p>
<ul>
<li>Stage</li>
<li>Stage Merge</li>
<li>Hubs</li>
<li>Links</li>
<li>Satellites</li>
<li>Dimensions</li>
<li>Facts</li>
</ul>
<p>which you can create now as</p>
<ul>
<li><code>TABLE</code> (with a stored procedure) or</li>
<li><code>VIEW</code>.</li>
</ul>
<p>Unfortuately, there is a little inconvenience with it. WhereScape warns currently when we want to recreate an object which has a <code>DDL</code> creating a <code>VIEW</code>. You would need to delete that manually from the database and recreate it with WhereScape.</p>
<p>Hopefully you have a machine which is powerful enough to calculate only views based on our history (DataStore) layer a.k.a. Persistent Staging Area.</p>
<p>In my case we have too much data to only work with views. What I do is using stage views as much as possible. Especially in multi-step stage preparation. No need to fiddle creating nesting jobs for them. And in loading business vault satellites I use stage views only. For the raw vault we would need a stage table to load hubs, links and satellites in parallel. But for all the cleaning and calculation while our only target is another satellite, there is no need to create a table for that. A view would be faster.</p>
<p>I reached now my main goal of having all objects created as tables or views. If you liked so far my contribution, please leave a message.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-986" data-postid="986" class="themify_builder_content themify_builder_content-986 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/01/27/virtual-data-warehouse/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">986</post-id>	</item>
		<item>
		<title>How to ensure development quality in WhereScape</title>
		<link>https://www.zemp.ch/2018/01/10/how-to-ensure-development-quality-in-wherescape/</link>
					<comments>https://www.zemp.ch/2018/01/10/how-to-ensure-development-quality-in-wherescape/#comments</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Wed, 10 Jan 2018 19:22:55 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=965</guid>

					<description><![CDATA[With WhereScape you have the freedom to do whatever you want. With time passing, I figured out, what is important to me and what not. But how to ensure that things are done always the same way? And not only for me but also for my collegues? We started to create meta views for objects, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>With WhereScape you have the freedom to do whatever you want. With time passing, I figured out, what is important to me and what not.</p>
<p>But how to ensure that things are done always the same way? And not only for me but also for my collegues?</p>
<p>We started to create meta views for objects, columns and indexes. On top of them we created &#8222;error views&#8220; to list all issues which were important to us: e.g. all tables and indexes should be compressed, data type for hash columns should be binary, etc.</p>
<p>For some rules we implemented a metadata cleaning service which constantly scanned for issues and fixed them straight away.</p>
<p>But some issues were still views which we needed to check regularly.</p>
<p>Lately when I gave some thoughts to this issue, I came to the conclusion that this is not an efficiant way to do it. When we develop, I want to get feedback straight away. Not checking once a week or every day my error views for issues.</p>
<p>It is out of scope to believe, that we can persuade WhereScape to change their software to our way of thinking. There are too many different views from customers in the market.</p>
<p>How can we achieve to get some feedback in our work? Did we forget something? Do the objects have the correct Key Types? We can&#8217;t change the way, how the system works, but maybe we are able to pass some error messages into our output. And what is our main output? Tables with their corresponding stored procedures.</p>
<p>I created another section in our stored procedures, where we list all meta data checks. If the check is ok, I add the message as a comment. If there is an error, then without the comment dashes. In that way, the compilation of the stored procedure will fail and we can check the messages!</p>
<p>We created 2 templates:</p>
<ul>
<li><code>nu3_utility_metadata</code> (for any platform-independent metadata checks)</li>
<li><code>nu3_sqlserver_utility_metadata</code> (for any SQL Server checks)</li>
</ul>
<p>What could be platform-independent checks:</p>
<ul>
<li>A hub needs one hub hash key</li>
<li>A link needs one link hash key</li>
<li>A satellite needs either hub or link hash key (and only 1)</li>
<li>A satellite needs a change hash key</li>
<li>If the stage tables has hash keys, make sure that all columns belong to a hash key definition</li>
<li>In a satellite, verify that all columns which are hashed in the change hash key on the stage table are also available in the satellite</li>
<li>&#8230;</li>
</ul>
<p>SQL Server checks could be:</p>
<ul>
<li>All hash columns should be of data type &#8218;binary&#8216;</li>
<li>Objects of type DataStore, Satellite, Link, Hub should have a Clustered Columnstore Index</li>
<li>Any index of stage tables should have the feature &#8218;Drop Index before Table Update&#8216; enabled</li>
<li>&#8230;</li>
</ul>
<p>These are just some ideas.</p>
<p>I believe to include such things into the template, abuses the original idea of templating. But it helps to get the meta data quality straight.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-965" data-postid="965" class="themify_builder_content themify_builder_content-965 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/01/10/how-to-ensure-development-quality-in-wherescape/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">965</post-id>	</item>
		<item>
		<title>Template: Fact</title>
		<link>https://www.zemp.ch/2018/01/07/template-fact/</link>
					<comments>https://www.zemp.ch/2018/01/07/template-fact/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sun, 07 Jan 2018 10:55:31 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=913</guid>

					<description><![CDATA[Loading data to a fact table is quite a difficult beast within WhereScape without templates. First, do I set business keys or not? If I set business keys the whole loading procedure is based on a cursor which updates or inserts 1 record at a time. Not good if I&#8217;m going to load 100 mio. [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Loading data to a fact table is quite a difficult beast within WhereScape without templates.</p>
<p>First, do I set business keys or not? If I set business keys the whole loading procedure is based on a cursor which updates or inserts 1 record at a time. Not good if I&#8217;m going to load 100 mio. of records. This will take ages.</p>
<p>Nowadays more and more people like clustered columnstore indexes. Cursors don&#8217;t work with them.</p>
<p>Ok, no business keys. Then I&#8217;m able to tick the &#8222;Set Based Insert&#8220; option. But with incremental loading I would need to delete or update updated records. So, I tick the &#8222;Include Delete Before Insert&#8220;. Now I&#8217;m able to select a column which matches stage and fact table. But only 1 column! This creates the following WHERE clause:</p>
<p><code>WHERE fact.column1 IN ( SELECT DISTINCT stage_fact.column1 FROM [TABLEOWNER].[stage_fact] stage_fact )</code></p>
<p>What about if I have multiple columns as a business key? By the way, we have already decided in the previous step that we don&#8217;t have business keys?!?</p>
<p>This was not satisfactory to work with.</p>
<p>What are the requirements:</p>
<h4>Stage</h4>
<p>In the stage table, we prepare the data set to be loaded. If we have dimensions with surrogate keys, we want to add a <code>dim_key</code> column to the stage table. This will look up the business key in the dimension table and returns the surrogate key. For business keys which don&#8217;t exist we want to add the unknown key.</p>
<p>Maybe we have late arriving dimensions or we are loading dimensions and facts in parallel. In that case, we don&#8217;t want to have the unknown key, but adding missing business keys to the dimension table. But for some dimensions I don&#8217;t want to get missing keys created (e.g. date dimension or dimensions with a fixed set of values).</p>
<h4>Fact</h4>
<p>Replace or insert data as fast as possible. Either use the <code>dss_load_datetime</code> or the <code>dss_change_hash</code> for delta detection.</p>
<h3>Stored Procedure</h3>
<h4>Stage</h4>
<p>There is one thing I want to point out from the standard stage template. How can we add missing business keys to a dimension?</p>
<p>WhereScape by default has another stored procedure to look up dimension keys and update it in the stage table. Again, with a cursor. 1-by-1. Unfortunately, we can&#8217;t create templates for that stored procedure.</p>
<p>Some when I figured out, that I could add another section in the stage template by inserting missing business keys into the dimensions. A really brilliant and fast solution.</p>
<p>I just <code>INSERT</code> a <code>DISTINCT</code> list of business keys for a dimension followed by an <code>UPDATE</code> statement. Both are set based. No cursor. Incredibly fast!</p>
<pre><code>INSERT INTO [TABLEOWNER].[dim_product] WITH ( TABLOCKX )
(
       shop_code
      ,sku
      ,dss_load_datetime
      ,dss_create_datetime
      ,dss_update_datetime
)
SELECT DISTINCT
       shop_code
      ,sku
      ,'0001-01-01'
      ,@v_current_datetime
      ,@v_current_datetime
FROM   [TABLEOWNER].[stage_demo_fact]
WHERE  dim_product_key = 0</code></pre>
<pre><code>UPDATE [TABLEOWNER].[stage_demo_fact] WITH ( TABLOCKX )
SET    dim_product_key = dim_product.dim_product_key
FROM   [TABLEOWNER].[dim_product]
WHERE  stage_demo_fact.dim_product_key = 0
       AND dim_product.shop_code = stage_demo_fact.shop_code
       AND dim_product.sku = stage_demo_fact.sku</code></pre>
<h4>Fact</h4>
<p>The following steps are defined:</p>
<ul>
<li><code>Delete existing records/Update changed records</code></li>
<li><code>Insert new records</code>
<ul>
<li><code>INSERT INTO Fact</code></li>
<li><code>SELECT Result</code><code></code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Extract from Stage Table</code></li>
</ul>
</li>
</ul>
<h4>Delete existing records/Update changed records</h4>
<p>If you have traditional tables, the template will create an <code>UPDATE</code> statement. If you are working with Clustered Columnstore Indexed tables then <code>UPDATE</code> has a really bad performance. Then <code>DELETE</code> and <code>INSERT</code> is much faster. With <code>DELETE</code> it sets only a marker on the record like <code>is_deleted</code> instead of replacing all the values. Please read the following <a target="_blank" href="http://www.nikoport.com/2014/03/17/clustered-columnstore-indexes-part-28-update-vs-delete-insert/" >blog entry</a> for more background information.</p>
<h4>Select Extract from Stage</h4>
<p>Load the data from the stage table.</p>
<h4>Where Not Exists</h4>
<p>Load Data only if the fact doesn’t exist. Records that existed have already been updated or deleted.</p>
<pre><code>WHERE NOT EXISTS (
        SELECT 1
        FROM   [TABLEOWNER].[demo_fact] demo_fact__not_exist
        WHERE  demo_fact__not_exist.sku = demo_fact.sku
               AND demo_fact__not_exist.date = demo_fact.date
)</code></pre>
<h4>Result</h4>
<p>The resulting query looks like this:</p>
<pre><code>INSERT INTO [TABLEOWNER].[demo_fact] WITH ( TABLOCKX )
(
        dim_product_key
       ,dim_date_key
       ,sku
       ,date
       ,purchase_cost
       ,dss_load_datetime
       ,dss_create_datetime
       ,dss_update_datetime
)
SELECT 
       demo_fact.dim_product_key AS dim_product_key 
      ,demo_fact.dim_date_key AS dim_date_key 
      ,demo_fact.sku AS sku 
      ,demo_fact.date AS date 
      ,demo_fact.purchase_cost AS purchase_cost 
      ,demo_fact.dss_load_datetime AS dss_load_datetime 
      ,@v_current_datetime AS dss_create_datetime 
      ,@v_current_datetime AS dss_update_datetime 
FROM   (
  SELECT demo_fact.*
  FROM   (
    SELECT 
           stage_demo_fact.dim_product_key AS dim_product_key 
          ,stage_demo_fact.dim_date_key AS dim_date_key 
          ,stage_demo_fact.sku AS sku 
          ,stage_demo_fact.date AS date 
          ,stage_demo_fact.purchase_cost AS purchase_cost 
          ,stage_demo_fact.dss_load_datetime AS dss_load_datetime 
    FROM [TABLEOWNER].[stage_demo_fact] stage_demo_fact
  ) AS demo_fact
  WHERE NOT EXISTS (
          SELECT 1
          FROM   [TABLEOWNER].[demo_fact] demo_fact__not_exist
          WHERE  demo_fact__not_exist.sku = demo_fact.sku
                 AND demo_fact__not_exist.date = demo_fact.date
  )
) AS demo_fact</code></pre>
<h3>Implementation</h3>
<p>Download from the <a target="_blank" href="https://github.com/EckisWelt/WhereScape/tree/master/Template" >template section</a> the following templates for WhereScape written in Pebble:</p>
<ul>
<li>nu3_sqlserver_procedure_fact</li>
<li>nu3_sqlserver_block_fact</li>
<li>nu3_sqlserver_procedure_stage (updated)</li>
</ul>
<p>Select the template in the properties.</p>
<p>For any dimension, for which we don&#8217;t want to have missing business keys automatically created, modify the extended property on the dimension <code>insert_missing_bk = false</code> (see <a href="https://www.zemp.ch/2018/01/06/template-dimension/" >blog post</a>).</p>
<p>That&#8217;s it.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-913" data-postid="913" class="themify_builder_content themify_builder_content-913 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/01/07/template-fact/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">913</post-id>	</item>
		<item>
		<title>Template: Dimension</title>
		<link>https://www.zemp.ch/2018/01/06/template-dimension/</link>
					<comments>https://www.zemp.ch/2018/01/06/template-dimension/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 06 Jan 2018 14:39:44 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=912</guid>

					<description><![CDATA[Loading data to a dimension is straight forward. Prepare the data in a stage table and load it to dimension. The interesting stuff is how to define and load the dimension. What are the requirements: Stage Nothing special. Just make sure that the business key is unique. Dimensions This has taken me some time to [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Loading data to a dimension is straight forward. Prepare the data in a stage table and load it to dimension. The interesting stuff is how to define and load the dimension.</p>
<p>What are the requirements:</p>
<h4>Stage</h4>
<p>Nothing special. Just make sure that the business key is unique.</p>
<h4>Dimensions</h4>
<p>This has taken me some time to figure out all rules how to build a dimension fast and properly.</p>
<p>The primary key of a dimension is the surrogate key. It is usually an integer identity column start counting from 1 upwards.</p>
<p>But when working with it, I found a lot of exceptions. Especially around zero keys. What is the zero key? It is the record used when no value is defined (aka NULL) in the fact table.</p>
<p>I got confused because the feature is called zero key. It implies that this record is always of the value 0. But in Data Vault, we define missing values as -1. Should I use here also -1 as unknown value? I decided to go with 0 because maybe I need to revert back to WhereScape standard functionality with SCD stuff and so on and WhereScape defines it as 0.</p>
<p>But then I hit dimensions where 0 couldn&#8217;t be the zero key. Then the argument came that we shouldn&#8217;t give meaning to the surrogate key. But people do. E.g. the key in the date dimension is usually in the form of yyyymmdd. And then I read that the zero key of the date dimension should be something in the future because otherwise the data would be in the same partition as old data.</p>
<p>This leads to the following requirements:</p>
<ul>
<li>I want to control the id generated. I need to map a column from stage to the dimension table</li>
<li>I want to be able to set the zero key to a user-defined value</li>
<li>I want to suppress the zero key generated</li>
<li>I want to suppress that missing business keys are getting inserted by the fact table</li>
</ul>
<p>For that to work I used the recently introduced feature of extended properties. I have the following extended properties defined on the dimension:</p>
<p>
<table id="tablepress-21" class="tablepress tablepress-id-21">
<tbody class="row-striping row-hover">
<tr class="row-1">
	<td class="column-1">zero_key_id</td><td class="column-2">Optional: Define another zero key value</td>
</tr>
<tr class="row-2">
	<td class="column-1">insert_zero_key</td><td class="column-2">Optional: Set it to false if no zero key should be added.</td>
</tr>
<tr class="row-3">
	<td class="column-1">insert_missing_bk</td><td class="column-2">Optional: Set it to false if no missing business keys from a fact table should be added</td>
</tr>
</tbody>
</table>
<!-- #tablepress-21 from cache --></p>
<h3>Stored Procedure</h3>
<p>The following steps are defined:</p>
<ul>
<li><code>Insert Zero Key</code>
<ul>
<li><code>SELECT Result</code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Zero Key Record</code></li>
</ul>
</li>
<li><code>Update existing records</code></li>
<li><code>Insert new records</code>
<ul>
<li><code>INSERT INTO Dimension</code></li>
<li><code>SELECT Result</code><code></code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Extract from Stage Table</code></li>
</ul>
</li>
</ul>
<h4>Insert Zero Key</h4>
<p>As already mentioned, I had some difficulties to understand, how to set up the zero key properly.</p>
<p>By default, WhereScape creates the artificial key, business key and <code>dss_create_datetime</code> and <code>dss_update_datetime</code>. Any other column for which you can define zero key values, are not considered. They are just <code>NULL</code>.</p>
<p>But I wanted to add <code>dss_load_datetime</code> as a system column. This is not supported.</p>
<p>For our cube, I don&#8217;t want to have <code>NULL</code> values. I came up with a view template, which replaces any <code>NULL</code> values from a dimension column based on the zero key value specified. For normal SQL queries on the table, <code>NULL</code> will still be shown.</p>
<h4>Update existing records</h4>
<p>The existing solution was just too slow. Comparing all columns for a change takes ages if the dimension table goes into the millions and having tons of columns.</p>
<p>We learnt already when loading data to a history table, that comparing only one column is much faster. The option here is to define a <code>dss_change_hash</code>. This will speed up the update process compared to default WhereScape behavior.</p>
<p>But there is something faster. Calculating <code>dss_change_hash</code> takes up also some processing time. With what can we replace it?</p>
<p>Until now we have taken a lot of energy to pump <code>dss_load_datetime</code> throughout the system. The idea is now, to just update columns if there is a change in <code>dss_load_datetime</code>. If there is a change let&#8217;s just update it. No need to compare if there is REALLY a change. This would kill the performance again.</p>
<p>How can we make sure, that there is always the most recent <code>dss_load_datetime</code>? Especially if we are joining data from dozens of satellites? I have modified the stage template slightly to calculate the most recent datetime by adding a transformation on <code>dss_load_datetime</code>.</p>
<pre><code>(
  SELECT MAX(v) FROM (VALUES 
    (first.dss_load_datetime)
   ,(second.dss_load_datetime)
   ,(....dss_load_datetime)
  ) AS VALUE(v)
)</code></pre>
<p>Is that not brilliant? In that way, we get always the most recent data. If somebody updates data on an existing record it will fail of course. But then he doesn&#8217;t do Data Vault.</p>
<p>Here the update query:</p>
<pre><code>UPDATE [TABLEOWNER].[dim_demo] WITH ( TABLOCKX )
SET     column1 = stage_dim_demo.column1
       ,column... = stage_dim_demo.column...
       ,dss_load_datetime = stage_dim_demo.dss_load_datetime
       ,dss_update_datetime = @v_current_datetime
FROM [TABLEOWNER].[stage_dim_demo] stage_dim_demo
WHERE  dim_demo.shop_code = stage_dim_demo.shop_code
       AND dim_demo.sku = stage_dim_demo.sku
       AND dim_demo.dss_load_datetime &lt;&gt; stage_dim_demo.dss_load_datetime</code></pre>
<h4>Select Extract from Stage</h4>
<p>Load the data from the stage table.</p>
<h4>Where Not Exists</h4>
<p>Load Data only if the dimension doesn&#8217;t exist. Records that does exist have already been updated.</p>
<pre><code>WHERE NOT EXISTS (
        SELECT 1
        FROM   [TABLEOWNER].[dim_demo] dim_demo__not_exist
        WHERE  dim_demo__not_exist.shop_code = dim_demo.shop_code
)</code></pre>
<h4>Result</h4>
<p>The resulting query looks like this:</p>
<pre><code>INSERT INTO [TABLEOWNER].[dim_demo] WITH ( TABLOCKX )
(
        sku
       ,product_group_l1
       ,product_group_l2
       ,product_family
       ,dss_load_datetime
       ,dss_create_datetime
       ,dss_update_datetime
)
SELECT 
       dim_demo.sku AS sku 
      ,dim_demo.product_group_l1 AS product_group_l1 
      ,dim_demo.product_group_l2 AS product_group_l2 
      ,dim_demo.product_family AS product_family 
      ,dim_demo.dss_load_datetime AS dss_load_datetime 
      ,@v_current_datetime AS dss_create_datetime 
      ,@v_current_datetime AS dss_update_datetime 
FROM   (
  SELECT dim_demo.*
  FROM   (
    SELECT 
           stage_dim_demo.sku AS sku 
          ,stage_dim_demo.product_group_l1 AS product_group_l1 
          ,stage_dim_demo.product_group_l2 AS product_group_l2 
          ,stage_dim_demo.product_family AS product_family 
          ,stage_dim_demo.dss_load_datetime AS dss_load_datetime 
    FROM [TABLEOWNER].[stage_dim_demo] stage_dim_demo
  ) AS dim_demo
  WHERE NOT EXISTS (
          SELECT 1
          FROM   [TABLEOWNER].[dim_demo] dim_demo__not_exist
          WHERE  dim_demo__not_exist.shop_code = dim_demo.shop_code
                 AND dim_demo__not_exist.sku = dim_demo.sku
  )
) AS dim_demo</code></pre>
<h3>Implementation</h3>
<p>Just download from the <a target="_blank" href="https://github.com/EckisWelt/WhereScape/tree/master/Template" >template section</a> the following templates for WhereScape written in Pebble:</p>
<ul>
<li>nu3_sqlserver_procedure_dim</li>
<li>nu3_sqlserver_block_dim</li>
</ul>
<p>Select it from the properties. That&#8217;s it.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-912" data-postid="912" class="themify_builder_content themify_builder_content-912 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2018/01/06/template-dimension/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">912</post-id>	</item>
		<item>
		<title>Template: Hub/Link</title>
		<link>https://www.zemp.ch/2017/12/17/template-hub-link/</link>
					<comments>https://www.zemp.ch/2017/12/17/template-hub-link/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sun, 17 Dec 2017 13:36:49 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=890</guid>

					<description><![CDATA[When writing about satellite loading, we need also to talk about hub and link loading. They happen at the same time. Before Data Vault 2.0 a hub or link surrogate key was an integer. Nowadays modern data vaulters would use hash keys instead to allow parallel loading. Others are going back to the roots and [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>When writing about satellite loading, we need also to talk about hub and link loading. They happen at the same time.</p>
<p>Before Data Vault 2.0 a hub or link surrogate key was an integer. Nowadays modern data vaulters would use hash keys instead to allow parallel loading. Others are going back to the roots and are using natural business keys. I dropped that idea because there was a reason to define something else instead. In our case it is about the handling of strings. If I want to be sure that something is unique, I would have to specify a binary collation for the business key column. Too much work and I wanted to have a properly working data warehouse first before going into experimental work with all the obstacles involved.</p>
<p>The following pictures shows the data flow for all data vault objects:</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite.jpg" ><img loading="lazy" decoding="async" class="aligncenter wp-image-860" src="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-1024x433.jpg" alt="" width="601" height="254" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-1024x433.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-200x85.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-768x325.jpg 768w" sizes="(max-width: 601px) 100vw, 601px" /></a>From the stage table, we load data to satellite, hubs and links. As you can imagine, there might be multiple satellites for 1 hub. Unfortunately, WhereScape doesn&#8217;t allow multi-to-one mapping. What exists is a kind of merge stage table.</p>
<p>This leaves us with the following data pipeline:</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_hub-e1512746432243.jpg" ><img loading="lazy" decoding="async" class="aligncenter wp-image-907" src="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_hub-e1512746432243-1024x532.jpg" alt="" width="451" height="234" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_hub-e1512746432243-1024x532.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_hub-e1512746432243-200x104.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_hub-e1512746432243-768x399.jpg 768w" sizes="(max-width: 451px) 100vw, 451px" /></a></p>
<p>From the stage table (which loads also data to the satellite) we create a stage view with columns like they consist in the hub. Then we would add a stage merge to combine multiple views into one. The hub WOULD then load data from the stage merge (but doesn&#8217;t).</p>
<p>This pipeline is for lineage only. We can track columns and tables forth and back without breaking the lineage.</p>
<p>What we do is to &#8222;recycle&#8220; the stored procedure which loads data from the merge stage table to the hub/link and replace the source table with the stage view.</p>
<p>In that way, we can load anything in parallel.</p>
<p>What are the requirements:</p>
<h4>Stage</h4>
<p>For the hub or link we need to create a hash key. The hash key for a hub can be calculated from multiple business keys. And the link hash key from multiple hub business keys. I add also business keys to the link which have no hubs. A peg-legged hub.</p>
<p>That&#8217;s it what we need from the stage table.</p>
<h4>Hub</h4>
<p>The primary key of the hub is the hash key. The business keys form a unique constrain.</p>
<h4>Link</h4>
<p>The primary key of the link is the hash key. The hub hash keys and added peg-legged hub columns should form a unique constrain. Unfortunately, WhereScape doesn&#8217;t support that yet. This is caused by the key type. Usually the key type &#8218;A&#8216; is the business key. Hub columns have the key type &#8218;h&#8216;, so we can&#8217;t assign &#8218;A&#8216; to it. Maybe they come up with a solution some when.</p>
<h3>Stored Procedure</h3>
<p>Before going into the detail, I want to stress one major difference to other templates I have seen. Usually they insert new records where the business keys doesn&#8217;t exist. I changed it to insert new records where the hash key don&#8217;t exist. Why so? First, the hash key is the primary key and has usually an index on it. Much faster to search in. If I would scan for the business keys they can consist of multiple columns which is slower again.</p>
<p>The main objective against using business keys is, that SQL Server might not find a difference in the value due to the collation used. You would have to set the business key to a binary collation to capture every change. Without a binary collation, it would lead to satellite records not having a hub record and therefore a broken foreign-key relationship. Happened to us.</p>
<p>What about hash key collisions? A lot is talked about. To get these collisions, I check for the existence of both, hash key and business keys. The unique constrain on the hub or link (when it gets supported) will give us a crash, if I get duplicate records.</p>
<p>What are the steps:</p>
<ul>
<li><code>Insert new records</code>
<ul>
<li><code>INSERT INTO Hub</code></li>
<li><code>SELECT Result</code><code></code></li>
<li><code>SUB-SELECT Row Condensing</code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Extract from Stage Table</code></li>
</ul>
</li>
</ul>
<h4>Extract from Stage Table</h4>
<p>Get the data from the stage table.</p>
<h4>Where Not Exists</h4>
<p>We want to load only new records.</p>
<pre style="padding-left: 30px;"><code>SELECT *
FROM (sub_query) AS hub
WHERE NOT EXISTS (
  SELECT 1
  FROM   hub AS hub__not_exist
  WHERE  &lt;hub/link_key&gt; = &lt;hub/link_key&gt;
         AND &lt;business key columns&gt; = &lt;business key columns&gt;</code></pre>
<p>If you want to get faster performance and remove collisions, delete the check for <code>&lt;business key columns&gt;</code>.</p>
<p>Very easy to make sure that it crashes when you hit one. Tell me about it, when you have one (really in your work, not by googling for a collision). Maybe I&#8217;m dead until this happens.</p>
<h4>Row Condensing</h4>
<p>It is possible, that we get multiple rows for a hub, but having many different <code>dss_load_datetime</code> values. We want to get the first used.</p>
<p>We must make also sure that we don&#8217;t suppress hash collisions. Meaning we could have same hash key for different business keys.</p>
<p style="padding-left: 30px;"><code>ROW_NUMBER() OVER (PARTITION BY &lt;hub/link_hash_key&gt;, &lt;business key columns&gt; ORDER BY dss_load_datetime ASC AS dss_row_no</code></p>
<p>followed by</p>
<p style="padding-left: 30px;"><code>WHERE dss_row_no = 1</code></p>
<h4>Result</h4>
<p>As we load data from stage views instead of the merge stage, we need to replace the merge stage table name with the stage view name.</p>
<p>The result looks like this:</p>
<pre><code>SET @v_source_table = CASE WHEN @p_task_name = 'hub_demo' THEN 'hub_demo' ELSE @p_task_name END
SET @v_sql = N'
INSERT INTO [TABLEOWNER].[hub_demo] WITH ( TABLOCKX )
(
       hub_product_key
      ,shop_code
      ,sku
      ,dss_load_datetime
      ,dss_record_source
      ,dss_create_datetime
)
SELECT 
       hub_demo.hub_product_key AS hub_product_key 
      ,hub_demo.shop_code AS shop_code 
      ,hub_demo.sku AS sku 
      ,hub_demo.dss_load_datetime AS dss_load_datetime 
      ,hub_demo.dss_record_source AS dss_record_source 
      ,@v_current_datetime AS dss_create_datetime 
FROM   (
  SELECT *
        ,ROW_NUMBER() OVER (PARTITION BY hub_product_key , shop_code , sku ORDER BY dss_load_datetime ASC AS dss_row_no
  FROM   (
    SELECT *
    FROM   (
      SELECT 
             stage_hub_demo.hub_product_key AS hub_product_key 
            ,stage_hub_demo.shop_code AS shop_code 
            ,stage_hub_demo.sku AS sku 
            ,stage_hub_demo.dss_load_datetime AS dss_load_datetime 
            ,stage_hub_demo.dss_record_source AS dss_record_source 
      FROM [TABLEOWNER].[stage_hub_demo] stage_hub_demo
    ) AS hub_demo
    WHERE NOT EXISTS (
            SELECT 1
            FROM   [TABLEOWNER].[hub_demo] hub_demo__not_exist
            WHERE  hub_demo__not_exist.hub_product_key = hub_demo.hub_product_key
    )
  ) AS hub_demo
) AS hub_demo
WHERE dss_row_no = 1
'
SET @v_sql = REPLACE(@v_sql,'@v_current_datetime','CAST(''' + CAST(@v_current_datetime AS NVARCHAR) + ''' AS DATETIME2(7))')
SET @v_sql = REPLACE(@v_sql,'stage_hub_demo',@v_source_table)</code></pre>
<h3>Implementation</h3>
<p>Download from the <a target="_blank" href="https://github.com/EckisWelt/WhereScape/tree/master/Template" >template section</a> the following templates for WhereScape written in Pebble:</p>
<ul>
<li>nu3_sqlserver_procedure_hub_link</li>
<li>nu3_sqlserver_block_select_hub_link</li>
<li>nu3_sqlserver_ddl_view_stage (if you want to configure stage objects as stage views)</li>
</ul>
<p>For the hub just select the template. This will generate the stored procedure to load data from the merge stage table.</p>
<p>Now comes the tricky (or dirty) part. Open the properties of the stage view and select this generated stored procedure in the &#8222;Custom Procedure&#8220; option field. With the replace function in place, we can call now on the stage view &#8222;Execute Custom Procedure&#8220; and it will load from the view instead of the default merge stage object.</p>
<p>That&#8217;s it.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-890" data-postid="890" class="themify_builder_content themify_builder_content-890 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2017/12/17/template-hub-link/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">890</post-id>	</item>
		<item>
		<title>Template: Satellite</title>
		<link>https://www.zemp.ch/2017/12/16/template-satellite/</link>
					<comments>https://www.zemp.ch/2017/12/16/template-satellite/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 16 Dec 2017 15:14:32 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=859</guid>

					<description><![CDATA[Finally, we reach our goal of talking about Data Vault Objects. This is the new area which I&#8217;m working and learning from and probably you might be interested as well. Data Vault consists of mainly 3 objects: Hub Link Satellite I&#8217;m not going to discuss what Data Vault is. There are lots of resources in [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Finally, we reach our goal of talking about Data Vault Objects. This is the new area which I&#8217;m working and learning from and probably you might be interested as well. Data Vault consists of mainly 3 objects:</p>
<ul>
<li>Hub</li>
<li>Link</li>
<li>Satellite</li>
</ul>
<p>I&#8217;m not going to discuss what Data Vault is. There are lots of resources in the Internet from people who are much smarter in teaching what Data Vault is than I am.</p>
<p>How does a loading procedure look like in WhereScape?</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite.jpg" ><img loading="lazy" decoding="async" class="wp-image-860 aligncenter" src="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-1024x433.jpg" alt="" width="601" height="254" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-1024x433.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-200x85.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/12/pipeline_satellite-768x325.jpg 768w" sizes="(max-width: 601px) 100vw, 601px" /></a></p>
<p>In this post, I&#8217;m going to talk about loading to a satellite. The next one will be about hubs and links.</p>
<p>First, a satellite is a system of record like a history table. The difference is that the data is collected around a HashKey of a hub or link (which represent business keys of a business concept) instead of the business key from the source table.</p>
<p>What are the requirements to load data into a satellite?</p>
<h4>Stage</h4>
<p>The creation of the stage table is discussed in-depth in another <a href="https://www.zemp.ch/2017/12/02/template-stage/" >blog post</a>. The main goal is to prepare the date to be loaded to a satellite.</p>
<p>Data from a history table is loaded first to a Raw Vault Satellite. There should be no transformations except hard business rules like data type changes.</p>
<p>If we need to clean the data, do some calculations or derive values from, we load data from existing satellites into a Business Vault satellite. Do whatever you want in the stage table.</p>
<p>After the data set is like we want it to be, we need to define the  <code>hub/link_keys</code> and the <code>dss_change_hash</code>. The cleaning of the business keys for the hub or link hash key is taken care of by the template.</p>
<p>Until the history level we had the following system columns:</p>
<ul>
<li><code>dss_load_datetime</code></li>
<li><code>dss_sequence_no</code></li>
<li><code>dss_record_source</code></li>
</ul>
<p>For satellite loading we are dropping the <code>dss_sequence_no</code>. For the first load from a history table we need to do also a little transformation by using the <code>dss_sequence_no</code> to make sure that we reduce the chance of hitting a duplicate row. No need to remove any rows from the source. Take them all!</p>
<p style="padding-left: 30px;"><code>DATEADD( NS , 100 * dss_sequence_no , dss_load_datetime )</code></p>
<h4>Satellite</h4>
<p>A table of records for a given HashKey. The Primary Key of a satellite is:</p>
<ul>
<li><code>hub/link_key</code></li>
<li><code>dss_start_datetime</code></li>
</ul>
<p>For a multi-active satellite add another business key to the table. Refrain as much as possible of doing that. As WhereScape doesn&#8217;t support multi-active satellite yet, I was forced to do it differently and this made my life easier by accident.</p>
<p>The <code>dss_start_datetime</code> is loaded by <code>dss_load_datetime</code>.</p>
<p>As I have an insert only system we move the calculation of the <code>dss_end_datetime</code> further down to a current or timeline view. All our satellites are Clustered Columnstored Indexed tables. Great performance for <code>INSERT</code> and <code>SELECT</code>.</p>
<h4>View</h4>
<p>To make the satellite more readable and not to write too many <code>WHERE</code> clauses in other objects we need to create 2 views:</p>
<ul>
<li>Current</li>
<li>Timeline</li>
</ul>
<p>The current view retrieves the last valid record for a <code>hub/link_key</code> while the timeline gives back a whole timeline. Currently I&#8217;m not sure what the first datetime of the timeline should be: <code>'0001-01-01'</code> or the first <code>dss_start_datetime</code>? The templates support both options.</p>
<h3>Stored Procedure</h3>
<p>Which steps do I take to load data from a stage table to a satellite:</p>
<ul>
<li><code>Insert new records</code>
<ul>
<li><code>INSERT INTO Satellite</code></li>
<li><code>SELECT Result</code></li>
<li><code>SUB-SELECT History Columns</code></li>
<li><code>SUB-SELECT Row Condensing</code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Extract from Stage Table</code></li>
</ul>
</li>
</ul>
<h4>Extract from Stage Table</h4>
<p>All the cleaning and transforming is done in stage. Now it’s time to load new data.</p>
<h4>Where Not Exists</h4>
<p>We have now 2 options. If you have a <code>dss_version</code> column we must make sure that the order of records inserted is correct. No old records are allowed.</p>
<p>If you delete this column like me, loading old and new data is possible.</p>
<pre style="padding-left: 30px;"><code>SELECT *
FROM (sub_query) AS sat_demo
WHERE NOT EXISTS (
  SELECT 1
  FROM   sat_demo AS sat_demo__not_exist
  WHERE  &lt;business key columns&gt; = &lt;business key columns&gt;
         AND dss_load_datetime = dss_load_datetime</code></pre>
<h4>Row Condensing</h4>
<p>This process is derived of the great work from <a target="_blank" href="http://roelantvos.com/" >Roeland Vos</a> on this <a target="_blank" href="http://roelantvos.com/blog/?p=1698" >topic</a>.</p>
<p>We could save all records in a satellite. But that would be a waste of space. So, we partition the data by <code>hub/link_keys</code> and order it by <code>dss_load_datetime</code>. What can we check the data for?</p>
<ul>
<li>Is there a difference between <code>dss_change_hash</code> from one row to the other?</li>
<li>Is the <code>dss_load_datetime</code> different from one row to the other? This is a hard rule. We circumvent a Primary Key constrain warning by eliminating duplicates which is dangerous. BUT&#8230; we have already taken care of. We added in the stage table loading process a little time variance. The chance of hitting a duplicate now is nearly zero.</li>
</ul>
<p>Following the snippets to calculate both:</p>
<pre style="padding-left: 30px;"><code>CASE WHEN LAG( dss_change_hash , 1 , CONVERT(BINARY(16),'') ) OVER ( PARTITION BY hub_product_key ORDER BY dss_load_datetime ) = dss_change_hash THEN 0
     ELSE 1
END AS is_hash_change</code></pre>
<pre style="padding-left: 30px;"><code>CASE WHEN LAG( dss_load_datetime , 1 , '0001-01-01' ) OVER ( PARTITION BY hub_product_key ORDER BY dss_load_datetime ) = dss_load_datetime THEN 0
     ELSE 1
END AS is_load_datetime_change</code></pre>
<h4>History Column</h4>
<p>The template offers currently the following history columns:</p>
<ul>
<li><code>dss_start_datetime</code></li>
<li><code>dss_version</code></li>
</ul>
<p>The <code>dss_start_datetime</code> is taken from <code>dss_load_datetime</code>.</p>
<p>If the first <code>dss_start_datetime</code> should be <code>'0001-01-01'</code> or not is decided in the timeline view.</p>
<h4>Result</h4>
<p>Here is the result including the <code>dss_version</code> column:</p>
<pre><code>SELECT 
       sat_demo.hub_product_key AS hub_product_key 
      ,sat_demo.displayable_quantity AS displayable_quantity 
      ,sat_demo.dss_event_datetime AS dss_event_datetime 
      ,sat_demo.dss_load_datetime AS dss_load_datetime 
      ,sat_demo.dss_record_source AS dss_record_source 
      ,sat_demo.dss_change_hash AS dss_change_hash 
      ,sat_demo.dss_start_datetime AS dss_start_datetime 
      ,sat_demo.dss_version AS dss_version 
      ,@v_current_datetime AS dss_create_datetime 
FROM   (
  SELECT sat_demo.*
        ,dss_load_datetime AS dss_start_datetime
        ,CASE WHEN current_rows.hub_product_key IS NULL
              THEN 1
              ELSE current_rows.dss_version + dss_row_no
         END AS dss_version
  FROM   (
    SELECT sat_demo.*
          ,ROW_NUMBER() OVER ( PARTITION BY hub_product_key ORDER BY dss_load_datetime ) AS dss_row_no
    FROM   (
      SELECT sat_demo.*
            ,CASE WHEN LAG( dss_change_hash , 1 , CONVERT(BINARY(16),'') ) OVER ( PARTITION BY hub_product_key ORDER BY dss_load_datetime ) = dss_change_hash THEN 0
                  ELSE 1
             END AS is_hash_change
            ,CASE WHEN LAG( dss_load_datetime , 1 , '0001-01-01' ) OVER ( PARTITION BY hub_product_key ORDER BY dss_load_datetime ) = dss_load_datetime THEN 0
                  ELSE 1
             END AS is_load_datetime_change
      FROM   (
        SELECT sat_demo.*
        FROM   (
          SELECT 
                 stage_sat_demo.hub_product_key AS hub_product_key 
                ,stage_sat_demo.displayable_quantity AS displayable_quantity 
                ,stage_sat_demo.dss_event_datetime AS dss_event_datetime 
                ,stage_sat_demo.dss_load_datetime AS dss_load_datetime 
                ,stage_sat_demo.dss_record_source AS dss_record_source 
                ,stage_sat_demo.dss_change_hash AS dss_change_hash 
          FROM [TABLEOWNER].[stage_sat_demo] stage_sat_demo
        ) AS sat_demo
        WHERE NOT EXISTS (
                SELECT 1
                FROM   [TABLEOWNER].[sat_demo] sat_demo__not_exist
                WHERE  sat_demo__not_exist.hub_product_key = sat_demo.hub_product_key
                       AND sat_demo__not_exist.dss_load_datetime &lt; sat_demo.dss_load_datetime
        )
      ) AS sat_demo
    ) AS sat_demo
    WHERE  is_hash_change = 1
    AND    is_load_datetime_change = 1
  ) AS sat_demo
  LEFT OUTER JOIN (
          SELECT
                 sat_demo__current.hub_product_key AS hub_product_key
                ,MAX(sat_demo__current.dss_start_datetime) AS dss_start_datetime
                ,MAX(sat_demo__current.dss_version) AS dss_version
          FROM   [TABLEOWNER].[sat_demo] sat_demo__current
          GROUP BY
                 sat_demo__current.hub_product_key
  ) AS current_rows
      ON  current_rows.hub_product_key = sat_demo.hub_product_key
) AS sat_demo</code></pre>
<p>See the sql example in full <a target="_blank" href="https://github.com/EckisWelt/WhereScape/blob/master/Template/nu3_sqlserver_procedure_sat_example.sql" >here</a>.</p>
<h3>Views</h3>
<h4>Current View</h4>
<p>To identify the most current record we must find out the most recent <code>dss_start_datetime</code> for a <code>hub/link_key</code>.</p>
<pre><code>SELECT *
FROM  sat_demo
INNER JOIN (
          SELECT
                 hub_product_key AS hub_product_key
                ,MAX(dss_start_datetime) AS dss_start_datetime
          FROM   [sat_demo] AS sat_demo
          GROUP BY
                 hub_product_key
  ) AS current_rows
      ON  current_rows.hub_product_key = sat.hub_product_key
      AND current_rows.dss_start_datetime = sat.dss_start_datetime</code></pre>
<h4> Timeline View</h4>
<p>Currently there are 4 columns supported:</p>
<ul>
<li><code>dss_start_datetime</code></li>
<li><code>dss_end_datetime</code></li>
<li><code>dss_effectivity_datetime</code></li>
<li><code>dss_expiry_datetime</code></li>
</ul>
<p>Currently I&#8217;m not sure what will the first <code>dss_start_datetime</code> be. I added an extended property to define it. It will calculate the first <code>dss_start_datetime</code> with the following query:</p>
<p style="padding-left: 30px;"><code> CASE WHEN LAG(dss_start_datetime,1,NULL) OVER (PARTITION BY hub/link_key ORDER BY dss_start_datetime) IS NULL THEN '0001-01-01 ELSE dss_start_datetime END</code></p>
<p>Add <code>dss_end_datetime</code> as a column without a source column definition.</p>
<p>The template will calculate it with the following query:</p>
<p style="padding-left: 30px;"><code>LEAD(DATEADD(ns,-100,dss_start_datetime),1,'9999-12-31') OVER ( PARTITION BY hub/link_key ORDER BY dss_start_datetime )</code></p>
<p>The other 2 columns exist, but are not perfect yet. I need to learn more about bi-temporal stuff to get it right.</p>
<h3>Implementation</h3>
<p>Download from the <a target="_blank" href="https://github.com/EckisWelt/WhereScape/tree/master/Template" >template section</a> the following templates for WhereScape written in Pebble:</p>
<ul>
<li>nu3_sqlserver_procedure_sat</li>
<li>nu3_sqlserver_block_select_sat</li>
<li>nu3_sqlserver_ddl_view_sat_current</li>
<li>nu3_sqlserver_ddl_view_sat_timeline</li>
</ul>
<p>There is not much to do to set it up. Just select the template. That&#8217;s it.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-859" data-postid="859" class="themify_builder_content themify_builder_content-859 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2017/12/16/template-satellite/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">859</post-id>	</item>
		<item>
		<title>Template: History (DataStore)</title>
		<link>https://www.zemp.ch/2017/12/09/template-history/</link>
					<comments>https://www.zemp.ch/2017/12/09/template-history/#respond</comments>
		
		<dc:creator><![CDATA[ecki]]></dc:creator>
		<pubDate>Sat, 09 Dec 2017 14:30:33 +0000</pubDate>
				<category><![CDATA[Business Intelligence]]></category>
		<guid isPermaLink="false">https://www.zemp.ch/?p=813</guid>

					<description><![CDATA[Creating a history layer (or Persistent Staging Area from Roeland Vos) is a safeguarding layer. All data arrives unmodified from the source. People in the Data Vault Space would probably suggest creating a Source Vault if I don&#8217;t want to build a proper Raw Vault with good Business Keys. I considered that option too. I [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Creating a history layer (or Persistent Staging Area from <a target="_blank" href="http://roelantvos.com/" >Roeland Vos</a>) is a safeguarding layer. All data arrives unmodified from the source.</p>
<p>People in the Data Vault Space would probably suggest creating a Source Vault if I don&#8217;t want to build a proper Raw Vault with good Business Keys. I considered that option too. I would have probably built it also that way. But I thought, if WhereScape has a history layer already built in, why not using it? And I used that kind of history already in the past.</p>
<p>So, how do I ingest data to the history layer?</p>
<p><a href="https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history.jpg" ><img src="data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width='1024'%20height='211'%20viewBox=%270%200%201024%20211%27%3E%3C/svg%3E" loading="lazy" data-lazy="1" style="background:linear-gradient(to right,#ffffff 25%,#ffffff 25% 50%,#ffffff 50% 75%,#f1f1f1 75%),linear-gradient(to right,#ffffff 25%,#fdfdc7 25% 50%,#e8e8e8 50% 75%,#ffffff 75%),linear-gradient(to right,#ffffff 25%,#fdfdc9 25% 50%,#dedede 50% 75%,#ffffff 75%),linear-gradient(to right,#ffffff 25%,#ffffff 25% 50%,#ffffff 50% 75%,#ffffff 75%)" decoding="async" class="tf_svg_lazy size-large wp-image-816 aligncenter" data-tf-src="https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-1024x211.jpg" alt="" width="1024" height="211" data-tf-srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-1024x211.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-200x41.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-768x158.jpg 768w" data-tf-sizes="(max-width: 1024px) 100vw, 1024px" /><noscript><img decoding="async" class="size-large wp-image-816 aligncenter" data-tf-not-load src="https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-1024x211.jpg" alt="" width="1024" height="211" srcset="https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-1024x211.jpg 1024w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-200x41.jpg 200w, https://www.zemp.ch/wordpress/wp-content/uploads/2017/11/pipeline_history-768x158.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></noscript></a></p>
<p>What are the requirements:</p>
<h4>Load</h4>
<p>We have any kind of source type and source system. Loading the external data to the landing table is the first step. Unmodified, as original as possible.</p>
<p>To the loaded data, 3 system columns are added:</p>
<ul>
<li><code>dss_load_datetime</code></li>
<li><code>dss_sequence_no</code></li>
<li><code>dss_record_source</code></li>
</ul>
<p>What is the <code>dss_load_datetime</code>? It is the datetime of that instance when the batch is inserted into the data warehouse. I give the whole batch the same datetime although it might load for minutes. Very important, it is not the extraction date. It is rather the insert date. See it as a human readable <a target="_blank" href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql" >rowversion</a>. In my language, it is also a batch. And there can be many batches in the load table. E.g. looping through files or looping through similar databases having the same data structure.</p>
<p>Each row in a batch is sequenced. I have seen that in the book from Dan Linstedt and in other designs. He doesn&#8217;t persist it, as it is not needed in satellites. But as I have a history layer I want to keep it for later use in satellites (I describe it later). If I can&#8217;t set e.g. the <code>dss_sequence_no</code> right in the load table, I add it in the stage step. Having it auto-increment in the load table is not possible, because I can have multiple batches.</p>
<p>The <code>dss_record_source</code> belongs to the Data Vault Standard. Just add it the way you want. We have the following rules:</p>
<ul>
<li>{source_name}.{source_database}.{source_schema}.{source_table}</li>
<li>{source_name}.api.{api}</li>
<li>{filename} (which contains the subdirectory structure including the source_name)</li>
</ul>
<h4>Stage</h4>
<p>The creation of the stage table is discussed in-depth in another <a href="https://www.zemp.ch/2017/12/02/template-stage/" >blog post</a>.</p>
<p>The goal is to add any missing system column which are not yet defined in the load table and adding a hash value for <code>dss_change_hash</code>.</p>
<p>If you have many batches, you can speed up the next step by adding a non-clustered index on <code>dss_load_datetime</code>.</p>
<h4>History</h4>
<p>A system of records for a given business key at a specific time. Only changes from one batch to another are persisted. How do we identify a batch? It&#8217;s the <code>dss_load_datetime</code> and this is always counting towards the future. Only in the first load you can load historical data. After that only new data arrives.</p>
<p>Persisting data over time needs at least the following system columns</p>
<ul>
<li><code>dss_start_datetime</code></li>
<li><code>dss_end_datetime</code></li>
</ul>
<p>WhereScape has also these other system columns on offer:</p>
<ul>
<li><code>dss_current_flag</code></li>
<li><code>dss_version</code></li>
</ul>
<p>Someone might think that the <code>dss_start_datetime</code> is the same as <code>dss_load_datetime</code>. I thought about that at the beginning too. But I had troubles later joining different datasets together.</p>
<p>I concluded that I start with the first batch at 0001-01-01 and having an end date of 9999-12-31. With the 2nd historization and following, <code>dss_load_datetime</code> matches <code>dss_start_datetime</code>. I still have the <code>dss_load_datetime</code> in my history table. This is used throughout the system.</p>
<h3>Views</h3>
<p>To have a current view could speed up the development later. No need to write <code>WHERE</code> clauses you might forget. And you can create in WhereScape an alias to another database, e.g. raw_mart.</p>
<p>The timeline is not needed as an extra view. It is already inside the history table. But to be consistent with later stuff, I added it here.</p>
<p>The error view is a nice add-on if you load data from users which tend to write anything into Excel and you want to be sure that it matches the later process. It doesn&#8217;t prevent (aka breaking the load), but informs you.</p>
<h3>Stored Procedure</h3>
<p>Which steps do I take:</p>
<ul>
<li><code>Create Cursor from dss_load_datetime</code></li>
<li><code>Update expiring records</code></li>
<li><code>Insert new records</code>
<ul>
<li><code>INSERT INTO History Table</code></li>
<li><code>SELECT Result</code></li>
<li><code>SUB-SELECT History Columns</code></li>
<li><code>SUB-SELECT Where Not Exists</code></li>
<li><code>SUB-SELECT Extract from Stage Table</code></li>
</ul>
</li>
</ul>
<h4>Cursor</h4>
<p>First, extract the last saved <code>dss_load_datetime</code> from the history table.</p>
<p>The cursor consists of a distinct and sorted list of <code>dss_load_datetime</code> which are equal or greater than the last <code>dss_load_datetime</code>. Why equal and not just greater than? If something breaks, I want to start over where it ended. And if it just takes another second to check, it is a safety check and no duplicate data is inserted.</p>
<p>Especially while migrating old data with thousands of batches, sometimes loading breaks. So, it is a good way to restart where it ended and not just do all over again. This feature is not available by default in WhereScape, so I used views before templates existed to just load not yet historized data.</p>
<h4>Update expiring records</h4>
<p>It sets for the expiring record a <code>dss_end_datetime</code> as 100 nanoseconds before the next <code>dss_start_datetime</code>. In that way, we can use <code>BETWEEN</code> in SQL statements. There is no time tick available 2 times. I recently learned another <a target="_blank" href="https://tedamoh.com/konferenzen/95-data-modeling-zone-17" >view</a> on that topic from <a target="_blank" href="https://twitter.com/dv_modeling" >Dirk Lerner</a>, but this is for another blog post. This is currently our rule set.</p>
<h4>Extract from Stage Table</h4>
<p>All the cleaning and transforming is done in stage. Now it&#8217;s time to load new data.</p>
<p style="padding-left: 30px;"><code>WHERE dss_load_datetime = cursor</code></p>
<p>We don&#8217;t clean for duplicates. The indexes will make sure that it will crash if you hit one.</p>
<h4>Where Not Exists</h4>
<p>To find out if a record has changed we need to get the latest record from the history table and check if the <code>dss_change_hash</code> differs.</p>
<pre><code>SELECT *
FROM (sub_query) AS history_table
WHERE NOT EXISTS (
  SELECT 1
  FROM   history_table AS history_table_not_exist
  WHERE  &lt;business key columns&gt; = &lt;business key columns&gt;
         AND dss_change_hash = dss_change_hash
         AND dss_current_flag = 'Y'
)</code></pre>
<h4>History Columns</h4>
<p>WhereScape has 4 history columns on offer. If you don&#8217;t need <code>dss_version</code> or <code>dss_current_flag</code>, delete them.</p>
<p>
<table id="tablepress-20" class="tablepress tablepress-id-20">
<tbody class="row-striping row-hover">
<tr class="row-1">
	<td class="column-1">dss_start_datetime</td><td class="column-2"><code>CASE WHEN current_rows.shop_code IS NULL<br />
  THEN CAST('0001-01-01' AS DATETIME2(7))<br />
  ELSE hist_demo.dss_load_datetime<br />
END</code></td>
</tr>
<tr class="row-2">
	<td class="column-1">dss_end_datetime</td><td class="column-2"><code>CAST('9999-12-31' AS DATETIME2(7))</code></td>
</tr>
<tr class="row-3">
	<td class="column-1">dss_current_flag</td><td class="column-2"><code>'Y'</code></td>
</tr>
<tr class="row-4">
	<td class="column-1">dss_version</td><td class="column-2"><code>CASE WHEN current_rows.shop_code IS NULL<br />
  THEN 1<br />
  ELSE current_rows.dss_version + 1<br />
END</code></td>
</tr>
</tbody>
</table>
<!-- #tablepress-20 from cache --></p>
<p>To get the last version we need to add a <code>LEFT OUTER JOIN</code> to extract the maximum version for a business key. See below.</p>
<h4>Add-On</h4>
<p>There is also a performance booster available if you insert history data with 1000s of batches. I experienced, when I refresh the indexes every 100 batches the insert performance is accelerated. But only if the table is not too big. Delete it or use it.</p>
<h4>Future</h4>
<p>Instead of looping through a cursor I want to load it in one go. I didn&#8217;t had time yet to explore and define a template, so you will have to wait. And it needs to be bullet-proof, because the history layer is the foundation for everything afterwards.</p>
<h4>Result</h4>
<p>Here is the query to <code>UPDATE</code>:</p>
<pre><code>UPDATE [TABLEOWNER].[hist_demo] WITH ( TABLOCKX )
SET    dss_end_datetime = DATEADD(NS,-100,stage_hist_demo.dss_load_datetime)
      ,dss_current_flag = 'N'
      ,dss_update_datetime = @v_current_datetime
FROM [TABLEOWNER].[stage_hist_demo] stage_hist_demo
WHERE  stage_hist_demo.dss_load_datetime = @v_batch_start
       AND hist_demo.dss_current_flag = 'Y'
       AND hist_demo.shop_code = stage_hist_demo.shop_code
       AND hist_demo.sid = stage_hist_demo.sid
       AND hist_demo.request_id = stage_hist_demo.request_id
       AND (
              hist_demo.dss_change_hash &lt;&gt; stage_hist_demo.dss_change_hash
           )</code></pre>
<p>Here is the query to <code>INSERT</code>:</p>
<pre><code>INSERT INTO [TABLEOWNER].[hist_demo] WITH ( TABLOCKX )
(
       shop_code
      ,sid
      ,request_id
      ,times
      ,order_id
      ,total_price
      ,dss_load_datetime
      ,dss_sequence_no
      ,dss_record_source
      ,dss_change_hash
      ,dss_start_datetime
      ,dss_end_datetime
      ,dss_current_flag
      ,dss_version
      ,dss_create_datetime
      ,dss_update_datetime
)
SELECT 
       hist_demo.shop_code AS shop_code 
      ,hist_demo.sid AS sid 
      ,hist_demo.request_id AS request_id 
      ,hist_demo.times AS times 
      ,hist_demo.order_id AS order_id 
      ,hist_demo.total_price AS total_price 
      ,hist_demo.dss_load_datetime AS dss_load_datetime 
      ,hist_demo.dss_sequence_no AS dss_sequence_no 
      ,hist_demo.dss_record_source AS dss_record_source 
      ,hist_demo.dss_change_hash AS dss_change_hash 
      ,hist_demo.dss_start_datetime AS dss_start_datetime 
      ,hist_demo.dss_end_datetime AS dss_end_datetime 
      ,hist_demo.dss_current_flag AS dss_current_flag 
      ,hist_demo.dss_version AS dss_version 
      ,@v_current_datetime AS dss_create_datetime 
      ,@v_current_datetime AS dss_update_datetime 
FROM   (
  SELECT hist_demo.*
        ,CASE WHEN current_rows.shop_code IS NULL
              THEN CAST('0001-01-01' AS DATETIME2(7))
              ELSE hist_demo.dss_load_datetime
         END AS dss_start_datetime 
        ,CAST('9999-12-31' AS DATETIME2(7)) AS dss_end_datetime 
        ,'Y' AS dss_current_flag 
        ,CASE WHEN current_rows.shop_code IS NULL
              THEN 1
              ELSE current_rows.dss_version + 1
         END AS dss_version 
  FROM   (
    SELECT hist_demo.*
    FROM   (
      SELECT 
             stage_hist_demo.shop_code AS shop_code 
            ,stage_hist_demo.sid AS sid 
            ,stage_hist_demo.request_id AS request_id 
            ,stage_hist_demo.times AS times 
            ,stage_hist_demo.order_id AS order_id 
            ,stage_hist_demo.total_price AS total_price 
            ,stage_hist_demo.dss_load_datetime AS dss_load_datetime 
            ,stage_hist_demo.dss_sequence_no AS dss_sequence_no 
            ,stage_hist_demo.dss_record_source AS dss_record_source 
            ,stage_hist_demo.dss_change_hash AS dss_change_hash 
      FROM [TABLEOWNER].[stage_hist_demo] stage_hist_demo
      WHERE  stage_hist_demo.dss_load_datetime = @v_batch_start
    ) AS hist_demo
    WHERE NOT EXISTS (
            SELECT 1
            FROM   [TABLEOWNER].[hist_demo] hist_demo__not_exist
            WHERE  hist_demo__not_exist.shop_code = hist_demo.shop_code
                   AND hist_demo__not_exist.sid = hist_demo.sid
                   AND hist_demo__not_exist.request_id = hist_demo.request_id
                   AND hist_demo__not_exist.dss_change_hash = hist_demo.dss_change_hash
                   AND hist_demo__not_exist.dss_current_flag = 'Y'
    )
  ) AS hist_demo
  LEFT OUTER JOIN (
          SELECT
                 hist_demo__current.shop_code AS shop_code
                ,hist_demo__current.sid AS sid
                ,hist_demo__current.request_id AS request_id
                ,MAX(hist_demo__current.dss_version) AS dss_version
          FROM  [TABLEOWNER].[hist_demo] hist_demo__current
          GROUP BY
                 hist_demo__current.shop_code
                ,hist_demo__current.sid
                ,hist_demo__current.request_id
  ) AS current_rows
      ON  current_rows.shop_code = hist_demo.shop_code
      AND current_rows.sid = hist_demo.sid
      AND current_rows.request_id = hist_demo.request_id
) AS hist_demo</code></pre>
<p>Of course, it would be possible to replace both steps with a <code>MERGE</code> statement. My experience with that is not the best. With huge tables or data sets it just takes time and sometimes SQL Server &#8222;got irritated&#8220; about what to do.</p>
<h3>Current View</h3>
<p>Often, we use only the current valid record. Instead of writing everywhere <code>WHERE</code> clauses, I just create a view from the history table and select the appropriate template. That&#8217;s it. What will it do?</p>
<p>It adds a simple <code>WHERE dss_current_flag = 'Y'</code>. Not that difficult. But it will get more complex when we are coming to Satellites and Link Satellites.</p>
<p>If this view is business critical, I added also a template for an indexed view.</p>
<h3>Implementation</h3>
<p>Download from the <a target="_blank" href="https://github.com/EckisWelt/WhereScape/tree/master/Template" >template section</a> the following templates for WhereScape written in Pebble:</p>
<ul>
<li>nu3_sqlserver_procedure_hist</li>
<li>nu3_sqlserver_view_hist_current</li>
</ul>
<p>To set up:</p>
<ul>
<li>Select the template &#8222;nu3_sqlserver_procedure_history&#8220;</li>
<li>Set the Business Key Columns</li>
<li>Set the Change Detection Fields to &#8222;dss_change_hash&#8220;</li>
</ul>
<p>That&#8217;s it.</p>
<!--themify_builder_content-->
<div id="themify_builder_content-813" data-postid="813" class="themify_builder_content themify_builder_content-813 themify_builder tf_clear">
    </div>
<!--/themify_builder_content-->
]]></content:encoded>
					
					<wfw:commentRss>https://www.zemp.ch/2017/12/09/template-history/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">813</post-id>	</item>
	</channel>
</rss>
