<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry</title>
    <description>The latest articles on DEV Community by Dmitry (@dyacc11).</description>
    <link>https://dev.to/dyacc11</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4206778%2F529daa1d-b25d-4fc9-801f-502472febd12.jpg</url>
      <title>DEV Community: Dmitry</title>
      <link>https://dev.to/dyacc11</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dyacc11"/>
    <language>en</language>
    <item>
      <title>Starting Small</title>
      <dc:creator>Dmitry</dc:creator>
      <pubDate>Sun, 08 Nov 2026 20:42:00 +0000</pubDate>
      <link>https://dev.to/dyacc11/starting-small-3na9</link>
      <guid>https://dev.to/dyacc11/starting-small-3na9</guid>
      <description>&lt;p&gt;
"A problem well stated is a problem half solved."
&lt;br&gt;- Charles Kettering
&lt;/p&gt;

&lt;h2&gt;
  
  
  How Much Data Do We Need?
&lt;/h2&gt;

&lt;p&gt;Earlier, we examined a small but fairly substantial dataset. It's substantial in the sense that visually verifying even 177 products is a tedious task.&lt;/p&gt;

&lt;p&gt;When debugging data pipeline logic, less than two dozen elements is more than enough, and the fewer the better. However, we shouldn't go too far in reducing data at the expense of functionality testing.&lt;/p&gt;

&lt;p&gt;Let's consider how much data might be required using the "Customer" dimension as an example.&lt;/p&gt;

&lt;p&gt;This dimension is implemented as a Slowly Changing Dimension Type 1 (SCD 1): it doesn't store history but simply overwrites attributes, keeping only their current state. And we need to verify several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That data loads as such. For the most basic check, one record is enough.&lt;/li&gt;
&lt;li&gt;That if there are multiple records for a single customer, the most recent one by modification date is selected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So minimally, we need three records: one for the general check and a second for verifying the selection of the most recent record within a group with a single key. In practice, it's better to have an additional pair of records for the second check, so we can verify that in a dataset with multiple groups, the algorithm can identify groups and select the most recent record from each group.&lt;/p&gt;

&lt;p&gt;In total, we'll need a minimum of five customer records across three unique customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Pipelines
&lt;/h2&gt;

&lt;p&gt;The original DWH from which the data was taken is designed only for a single full load. This mode is typical for data analysts' approach. However, in real-world data pipelines for organizational DWHs, updated data is loaded continuously, and the requirements for organizing fault-tolerant pipelines are different. In general, two main modes are typically implemented:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5yc0zl4u85hvo1xsqfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5yc0zl4u85hvo1xsqfv.png" alt="Diagram 1" width="729" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the first mode of initial load, the DWH, which at that moment contains no data, is populated with available data. The initial load is performed once.&lt;/p&gt;

&lt;p&gt;In the second mode, data is loaded as it changes in the sources.&lt;/p&gt;

&lt;p&gt;In a specific case, the data source can be the same for both modes: for example, we have a &lt;code&gt;csv&lt;/code&gt; file with data that is stored in some shared location and is updated over time. For instance, some records in it may be updated, while new ones are added. Schematically, this can be represented as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvgdb0vayccrujsn5esrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvgdb0vayccrujsn5esrp.png" alt="Diagram 2" width="798" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On one side, there is a data creator who updates the file over time. On the other side, there is a data department with a DWH that needs to account for changes in the warehouse. This is exactly the variant with a file in a shared location that we will consider for the "Customer" dimension.&lt;/p&gt;

&lt;p&gt;However, much more often data updates are built according to a different scheme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxomnhnst381lpxt2vv5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxomnhnst381lpxt2vv5j.png" alt="Diagram 3" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, only those changes that occurred over a certain period of time fall into the incremental changes.&lt;/p&gt;

&lt;p&gt;These changes reach the DWH either directly upon its request or through an intermediary in the form of a message broker, which buffers and stores data, coordinating the mutual work.&lt;/p&gt;

&lt;p&gt;We will use the incremental load variant for updates for the "Product" dimension and for the "Sales" fact.&lt;/p&gt;

&lt;p&gt;Thus, our DWH, unlike the original one, will support a hybrid architecture for continuously receiving updated data from sources. This is precisely why we reduced the number of tables to three and removed extra elements for the "snowflake" schema to simplify the elements for implementing more complex data pipeline logic.&lt;/p&gt;

&lt;p&gt;Let's draw another diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz08t7szrqqwjwwsdk645.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz08t7szrqqwjwwsdk645.png" alt="Diagram 4" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have indicated that customer data comes from a file, while product and sales data is taken from a database.&lt;/p&gt;

&lt;p&gt;We have also designated the entry points for this data for each object as &lt;code&gt;Endpoints&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This abstraction of the input layer will allow us to simplify the task of combining initial and incremental loads: if the data format remains the same for both cases, then we only need to switch the sources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlmlfsrvweb8zfj2uczi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlmlfsrvweb8zfj2uczi.png" alt="Diagram 5" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selectors simply switch data flows, leaving their content as is. The solid line represents the data flow for incremental load, while the dashed line shows the data flows for initial load. This gives us the ability to switch between initial and incremental loads without changing the processing algorithms. These are exactly the algorithms we are going to implement and debug.&lt;/p&gt;

&lt;p&gt;Small data is needed for debugging the logic functionality, as it allows us to verify the work visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Simulator
&lt;/h2&gt;

&lt;p&gt;To be able to work with small datasets, let's modify the diagram above and slightly rearrange the functional blocks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffg715rn26znzpzosbfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffg715rn26znzpzosbfq.png" alt="Diagram 6" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Data Simulator contains selectors and controls their logic. It also provides data to the input endpoints and allows simulating both initial and incremental loads.&lt;/p&gt;

&lt;p&gt;The simulator will help us debug the pipeline algorithms by providing small datasets for convenient visual debugging.&lt;/p&gt;

&lt;p&gt;But to understand how to implement it, we first need to consider what data we need to simulate and how.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Simulation
&lt;/h3&gt;

&lt;p&gt;The data loading process into DWH, as mentioned above, can be described with the following diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxeo69k3b9mh3zpj67n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxeo69k3b9mh3zpj67n1.png" alt="Diagram 7" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, there is an initial load, which is performed once.&lt;/li&gt;
&lt;li&gt;Then, multiple incremental loads follow, which update data changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, the Data Manipulation Language (DML) provides three operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;INSERT&lt;/code&gt; - inserting new data that is not present in the DWH.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UPDATE&lt;/code&gt; - updating data that already exists in the DWH.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; - deleting data from the DWH.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last operation, namely &lt;code&gt;DELETE&lt;/code&gt;, is often not applied directly to the DWH used for analytics, as historical data is also of interest for analysis. We will not consider this operation in the context of our DWH, but note that to keep data in the database and have the ability to determine whether the data is current or not, an additional flag can be introduced for each record. This flag can be used to determine if the data has been deleted. Such a flag implements "soft" deletion, marking records as deleted without physically removing them.&lt;/p&gt;

&lt;p&gt;The remaining two operations are often referred to by a single word: &lt;code&gt;UPSERT&lt;/code&gt;. In the case of incremental loading of changing data, &lt;code&gt;UPSERT&lt;/code&gt; is used specifically. During the initial load, when there is no data in the database yet, using &lt;code&gt;INSERT&lt;/code&gt; will be much faster, but &lt;code&gt;UPSERT&lt;/code&gt; will also correctly load the data.&lt;/p&gt;

&lt;p&gt;Thus, test data in general should work with the &lt;code&gt;UPSERT&lt;/code&gt; operation. This will be the same data format as for the import in the previous part from a &lt;code&gt;csv&lt;/code&gt; file, and if there are already rows with the same key in the table, the new data should update them.&lt;/p&gt;

&lt;p&gt;If parts of the test data are equivalent to files, then they can be placed in files and selected as needed, for example for a product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnge8q70sf5c1fo6p2pn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnge8q70sf5c1fo6p2pn8.png" alt="Diagram 8" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, with a dozen test scenarios and three tables, the number of files would amount to three dozen, and managing them would be difficult, as would checking what changed in the data from one test scenario to another.&lt;/p&gt;

&lt;p&gt;It is much more convenient to place all of this in a table with an additional field for the test scenario number, organize the output point as a &lt;code&gt;VIEW&lt;/code&gt;, and implement the selection logic as a &lt;code&gt;WHERE&lt;/code&gt; condition:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0how61kyzdp55ra844i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0how61kyzdp55ra844i.png" alt="Diagram 9" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, with a condition like &lt;code&gt;WHERE id = 1&lt;/code&gt;, the view will return data for the initial load, and with &lt;code&gt;WHERE id = 2&lt;/code&gt; it will return data for the first incremental load.&lt;/p&gt;

&lt;p&gt;To control data portions for multiple sources at once, a separate table &lt;code&gt;simTestLoadScenario&lt;/code&gt; can be organized with the following columns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tls_id&lt;/td&gt;
&lt;td&gt;scenario sequence number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls_name&lt;/td&gt;
&lt;td&gt;scenario name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls_description&lt;/td&gt;
&lt;td&gt;scenario description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls_Customer_load_id&lt;/td&gt;
&lt;td&gt;load part number for customers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls_Product_load_id&lt;/td&gt;
&lt;td&gt;load part number for products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tls_Sales_load_id&lt;/td&gt;
&lt;td&gt;load part number for sales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By specifying &lt;code&gt;load_id&lt;/code&gt; for different sources, you can combine different loads in a scenario. The initial load is considered &lt;code&gt;load_id = 1&lt;/code&gt;. With &lt;code&gt;load_id = 0&lt;/code&gt;, nothing will be loaded for a specific source, as such a part will be absent from the data table.&lt;/p&gt;

&lt;p&gt;To select the current scenario, a second table &lt;code&gt;simCurrentLoadScenario&lt;/code&gt; can be created with a strict constraint that it can only contain one row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;simCurrentLoadScenario&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;cls_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;tls_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; 
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;simCurrentLoadScenario&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tls_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, by updating the &lt;code&gt;tls_id&lt;/code&gt; field in this table, you can switch scenarios, and the views for sources will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;simProduct&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_nm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_start_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;productData&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;simTestLoadScenario&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls_Product_load_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;simCurrentLoadScenario&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, views are created for other data, where the &lt;code&gt;load_id&lt;/code&gt; field needs to be added to select a particular load portion.&lt;/p&gt;

&lt;p&gt;This will allow us to switch scenarios and data for loading with a simple query like: &lt;code&gt;UPDATE CurrentLoadScenario SET tls_id = 2&lt;/code&gt;. The &lt;code&gt;sim&lt;/code&gt; prefix is added to all objects to group them by functional attribute, as SQLite does not support schemas.&lt;/p&gt;

&lt;p&gt;Now that we have determined the implementation of the data simulator, we can create a basic set of scenarios for testing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pzn7oqvgduy6mt7xius.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pzn7oqvgduy6mt7xius.png" alt="Diagram 10" width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we test the "Customer" dimension. First the initial load, and then one or two incremental loads. The other dimensions and facts do not return any data from the sources at this point. Next, we test the "Product" dimension in a similar way. After both dimensions have been tested and loaded, we test the "Sales" fact.&lt;/p&gt;

&lt;p&gt;After completing the detailed testing by objects, we clear the target database and re-test the initial and incremental loads with data present in all sources to verify the integrity of such a load.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;tls_id&lt;/code&gt; field, not numbers are specified, but some sequentially ordered labels, as each object needs to be considered using an example of what data can be created for it.&lt;/p&gt;

&lt;p&gt;Let's consider this in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Customer" Dimension (the &lt;code&gt;customerData&lt;/code&gt; table)
&lt;/h3&gt;

&lt;p&gt;As we determined above, this dimension will behave as if it resided in an external file that is periodically updated according to the assumed model, where a separate department is responsible for this. Therefore, its data arrives in full, and the incremental load is effectively a complete reload on top of the existing data, which must be able to detect changes and apply them on its own. This dimension is also a Slowly Changing Dimension Type 1 (SCD 1) and does not preserve history: a new version overwrites the attributes by key.&lt;/p&gt;

&lt;p&gt;If we look at the data from the previous part, we will see that the table contains a total of 18,483 records (including duplicates by key), and the business key falls within the range &lt;code&gt;AW00011000&lt;/code&gt; - &lt;code&gt;AW00029483&lt;/code&gt;. The surrogate key corresponds to the number in the business key and has a value range of &lt;code&gt;11000&lt;/code&gt; - &lt;code&gt;29483&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's create five rows of data for the initial load for this table, using numbers starting from &lt;code&gt;1001&lt;/code&gt; with the prefix &lt;code&gt;TV&lt;/code&gt;, which stands for &lt;code&gt;Test Valid&lt;/code&gt;, that is, valid test data. For the first and last names, we will also choose values that start with &lt;code&gt;T&lt;/code&gt; for the last name and &lt;code&gt;V&lt;/code&gt; for the first name in all records, so that there are no matching first and last names in the original data. We will take dates within January 2025.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;TV00001001&lt;/td&gt;
&lt;td&gt;Verity&lt;/td&gt;
&lt;td&gt;Terry&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;simple single record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;Vivian&lt;/td&gt;
&lt;td&gt;Turner&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-04&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;second pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-05&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;second pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From this data, the following should remain as the final result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first row with &lt;code&gt;id=1001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the third row with &lt;code&gt;id=1002&lt;/code&gt; and the date &lt;code&gt;2025-01-02&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the fifth row with &lt;code&gt;id=1003&lt;/code&gt; and the date &lt;code&gt;2025-01-05&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the next load, we are going to make the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove row 4 with the date &lt;code&gt;2025-01-04&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add the next refinement for customer &lt;code&gt;1003&lt;/code&gt; and set their full first name to "Vishal"&lt;/li&gt;
&lt;li&gt;add one more customer&lt;/li&gt;
&lt;li&gt;at the same time, the rest of the data must remain, since we are reloading the data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How can this be implemented? This can be done using bitmasks and the bitwise AND operation. For example, load blocks can be implemented as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block number&lt;/th&gt;
&lt;th&gt;Block bitmask (value)&lt;/th&gt;
&lt;th&gt;Load bitmask (value)&lt;/th&gt;
&lt;th&gt;Load by mask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001(1)&lt;/td&gt;
&lt;td&gt;001(1)&lt;/td&gt;
&lt;td&gt;will load block 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010(2)&lt;/td&gt;
&lt;td&gt;011(3)&lt;/td&gt;
&lt;td&gt;will load blocks 1 and 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;100(4)&lt;/td&gt;
&lt;td&gt;111(7)&lt;/td&gt;
&lt;td&gt;will load blocks 1, 2 and 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the bitmask, the binary &lt;code&gt;1&lt;/code&gt; shifts from block to block from right to left: in the first block it is in the least significant bit, in the second - in the middle bit, and in the third - in the most significant bit. Then, if we apply the mask &lt;code&gt;011&lt;/code&gt;, both the first and the second blocks will be included, and with the mask &lt;code&gt;111&lt;/code&gt; - all three blocks.&lt;/p&gt;

&lt;p&gt;Indeed: &lt;code&gt;011 &amp;amp; 001 = 001 &amp;gt; 0&lt;/code&gt; and &lt;code&gt;011 &amp;amp; 010 = 010 &amp;gt; 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we need a block to be loaded only for one specific load, we can introduce an additional field &lt;code&gt;for_mask&lt;/code&gt; and specify in it a value for an exact comparison operation with the mask, when the value of this field is not zero:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;th&gt;for_mask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then, when loading the first block, where the mask value used is 1, rows 1 and 2 will be loaded.&lt;/p&gt;

&lt;p&gt;When loading the second block, where the mask value is 3, rows 1, 3, and 4 will be loaded. The second row will not be loaded, since 1 is not equal to 3, while the fourth row is loaded, since 3 equals 3.&lt;/p&gt;

&lt;p&gt;When loading the third block, the fourth row will be skipped, since 3 is not equal to 7. The second row will also be skipped, and rows 1, 3, and 5 will be loaded.&lt;/p&gt;

&lt;p&gt;The complete condition for both fields is: &lt;code&gt;(load_id &amp;amp; mask) &amp;gt; 0 AND (for_mask = 0 OR for_mask = mask)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can extend the scenario for the second load:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;th&gt;for_mask&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;TV00001001&lt;/td&gt;
&lt;td&gt;Verity&lt;/td&gt;
&lt;td&gt;Terry&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;simple single record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;Vivian&lt;/td&gt;
&lt;td&gt;Turner&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-04&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;removed in the second load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-05&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;second pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;Vishal&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;second load update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;TV00001004&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;2025-01-14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;new record of the second load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data we will obtain in the second load will look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;TV00001001&lt;/td&gt;
&lt;td&gt;Verity&lt;/td&gt;
&lt;td&gt;Terry&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;Vivian&lt;/td&gt;
&lt;td&gt;Turner&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;Vishal&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;TV00001004&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;2025-01-14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is, customer &lt;code&gt;1003&lt;/code&gt; with the date &lt;code&gt;2025-01-04&lt;/code&gt; will be absent from it.&lt;/p&gt;

&lt;p&gt;In the third load, we will additionally update customer &lt;code&gt;1004&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;th&gt;for_mask&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;TV00001001&lt;/td&gt;
&lt;td&gt;Verity&lt;/td&gt;
&lt;td&gt;Terry&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;simple single record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;TV00001002&lt;/td&gt;
&lt;td&gt;Vivian&lt;/td&gt;
&lt;td&gt;Turner&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;first pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-04&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;removed in the second and third loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-05&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;second pair of duplicates by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;TV00001003&lt;/td&gt;
&lt;td&gt;Vishal&lt;/td&gt;
&lt;td&gt;Tucker&lt;/td&gt;
&lt;td&gt;2025-01-13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;second load update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;TV00001004&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;2025-01-14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;new record of the second load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;TV00001004&lt;/td&gt;
&lt;td&gt;Vivek&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;2025-02-15&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;third load update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;TV00001004&lt;/td&gt;
&lt;td&gt;Vivek&lt;/td&gt;
&lt;td&gt;Tripp&lt;/td&gt;
&lt;td&gt;2025-02-16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;third load update&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the complete content of the table for all three loads.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;simTestLoadScenario&lt;/code&gt; table, the &lt;code&gt;tls_Customer_load_id&lt;/code&gt; field will use exactly the load bitmask:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tls_id&lt;/th&gt;
&lt;th&gt;tls_name&lt;/th&gt;
&lt;th&gt;tls_description&lt;/th&gt;
&lt;th&gt;tls_Customer_load_id&lt;/th&gt;
&lt;th&gt;tls_Product_load_id&lt;/th&gt;
&lt;th&gt;tls_Sales_load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;C-VT01&lt;/td&gt;
&lt;td&gt;Customer valid test initial load&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C-VT02&lt;/td&gt;
&lt;td&gt;Customer valid test incremental load 1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;C-VT03&lt;/td&gt;
&lt;td&gt;Customer valid test incremental load 2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The "Product" Dimension (the &lt;code&gt;productData&lt;/code&gt; table)
&lt;/h3&gt;

&lt;p&gt;The surrogate key values in the product table start from &lt;code&gt;212&lt;/code&gt;. We will use values starting from &lt;code&gt;101&lt;/code&gt;. The product code is formed from the brand name, product type, some digits, and symbols indicating the size.&lt;/p&gt;

&lt;p&gt;Let's come up with three brands: "SporticsDog", "Mike and Cat", and "JJ Run", and name the product "Track Vest" (using the prefix &lt;code&gt;TV&lt;/code&gt; based on the first letters).&lt;/p&gt;

&lt;p&gt;Then, for the first load, we will create one product with a closed validity interval, a second one - with an open interval, and a product with two intervals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prd_id&lt;/th&gt;
&lt;th&gt;prd_key&lt;/th&gt;
&lt;th&gt;prd_nm&lt;/th&gt;
&lt;th&gt;prd_cost&lt;/th&gt;
&lt;th&gt;prd_start_dt&lt;/th&gt;
&lt;th&gt;prd_end_dt&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;TV-SD10-L&lt;/td&gt;
&lt;td&gt;Track Vest SporticsDog - L&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2025-01-01&lt;/td&gt;
&lt;td&gt;2025-03-01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;Track Vest Mike and Cat - S&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XS&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2025-01-03&lt;/td&gt;
&lt;td&gt;2025-02-05&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XS&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2025-02-05&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the product dimension, changes are incremental, so bitmasks are not required for incremental loads. For the first incremental load, we will make the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a new validity interval for the product &lt;code&gt;TV-MC70-S&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add one more product.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prd_id&lt;/th&gt;
&lt;th&gt;prd_key&lt;/th&gt;
&lt;th&gt;prd_nm&lt;/th&gt;
&lt;th&gt;prd_cost&lt;/th&gt;
&lt;th&gt;prd_start_dt&lt;/th&gt;
&lt;th&gt;prd_end_dt&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;Track Vest Mike and Cat - S&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2025-01-02&lt;/td&gt;
&lt;td&gt;2025-02-05&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;105&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;Track Vest Mike and Cat - S&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2025-02-05&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XL&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2025-02-03&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As we can see, to close the previous interval, we use the existing surrogate key &lt;code&gt;102&lt;/code&gt;, setting a new end date for it, and for the next interval, we use a new surrogate key &lt;code&gt;105&lt;/code&gt;. For a completely new row, we use the next sequential surrogate key &lt;code&gt;106&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the second incremental load, we will update the intervals for two products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open a new interval for &lt;code&gt;TV-JJ17-XS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;close the interval for &lt;code&gt;TV-JJ17-XL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prd_id&lt;/th&gt;
&lt;th&gt;prd_key&lt;/th&gt;
&lt;th&gt;prd_nm&lt;/th&gt;
&lt;th&gt;prd_cost&lt;/th&gt;
&lt;th&gt;prd_start_dt&lt;/th&gt;
&lt;th&gt;prd_end_dt&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XS&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2025-02-05&lt;/td&gt;
&lt;td&gt;2025-03-03&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XS&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2025-03-03&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;Track Vest JJ Run - XL&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2025-02-03&lt;/td&gt;
&lt;td&gt;2025-04-03&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Overall, this dataset is quite sufficient to test this dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Sales" Fact Table (the &lt;code&gt;salesData&lt;/code&gt; table)
&lt;/h3&gt;

&lt;p&gt;The business key of an order in this table starts with &lt;code&gt;SO&lt;/code&gt; (&lt;code&gt;Sales Order&lt;/code&gt;) and falls within the range &lt;code&gt;SO43697&lt;/code&gt; - &lt;code&gt;SO75123&lt;/code&gt;.&lt;br&gt;
We will use the prefix &lt;code&gt;TVSO&lt;/code&gt; and start from &lt;code&gt;101&lt;/code&gt;, for example, &lt;code&gt;TVSO101&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This table is the simplest in terms of loading, since data is only appended to it. It is only necessary to take the product validity interval into account. For the customer identifier, we can use just a few values covering both records without duplicates and records with duplicates.&lt;/p&gt;

&lt;p&gt;For the initial load, we will take two orders, each containing two products, from two customers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TVSO101&lt;/td&gt;
&lt;td&gt;TV-SD10-L&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;20250104&lt;/td&gt;
&lt;td&gt;20250106&lt;/td&gt;
&lt;td&gt;20250109&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO101&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;20250104&lt;/td&gt;
&lt;td&gt;20250106&lt;/td&gt;
&lt;td&gt;20250109&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO102&lt;/td&gt;
&lt;td&gt;TV-SD10-L&lt;/td&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;20250105&lt;/td&gt;
&lt;td&gt;20250109&lt;/td&gt;
&lt;td&gt;20250111&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO102&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;20250105&lt;/td&gt;
&lt;td&gt;20250109&lt;/td&gt;
&lt;td&gt;20250111&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the first incremental load, we will add orders for products whose prices changed in February, for the same customers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TVSO103&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;20250206&lt;/td&gt;
&lt;td&gt;20250208&lt;/td&gt;
&lt;td&gt;20250209&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO103&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;20250206&lt;/td&gt;
&lt;td&gt;20250208&lt;/td&gt;
&lt;td&gt;20250209&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO104&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;20250207&lt;/td&gt;
&lt;td&gt;20250209&lt;/td&gt;
&lt;td&gt;20250211&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO104&lt;/td&gt;
&lt;td&gt;TV-MC70-S&lt;/td&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;20250207&lt;/td&gt;
&lt;td&gt;20250209&lt;/td&gt;
&lt;td&gt;20250211&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the second incremental load, we will take products updated in March and use a couple of other customers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;th&gt;load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TVSO105&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;20250306&lt;/td&gt;
&lt;td&gt;20250308&lt;/td&gt;
&lt;td&gt;20250309&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO105&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;20250306&lt;/td&gt;
&lt;td&gt;20250308&lt;/td&gt;
&lt;td&gt;20250309&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO106&lt;/td&gt;
&lt;td&gt;TV-JJ17-XL&lt;/td&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;20250307&lt;/td&gt;
&lt;td&gt;20250309&lt;/td&gt;
&lt;td&gt;20250311&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVSO106&lt;/td&gt;
&lt;td&gt;TV-JJ17-XS&lt;/td&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;20250307&lt;/td&gt;
&lt;td&gt;20250309&lt;/td&gt;
&lt;td&gt;20250311&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  All Data Together
&lt;/h3&gt;

&lt;p&gt;A small dataset is convenient because it allows for easy visual verification of data integrity: it can fit on a single spreadsheet page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpsjedsnkt15fvjleecv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpsjedsnkt15fvjleecv.png" alt="Diagram 11" width="799" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As clearly shown in the image, it is easy to highlight logically related cells with different colors and verify the correctness of the dataset. Additionally, three dates are highlighted in different colors (green, purple, blue) following the &lt;code&gt;ETL loads&lt;/code&gt; marker.&lt;br&gt;
These dates represent simulated ETL load dates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During the first (initial) load, data up to &lt;code&gt;2025-01-08&lt;/code&gt; is added (highlighted in green).&lt;/li&gt;
&lt;li&gt;During the second (first incremental) load, data with dates &lt;code&gt;2025-01-09&lt;/code&gt; - &lt;code&gt;2025-02-08&lt;/code&gt; is added (highlighted in purple).&lt;/li&gt;
&lt;li&gt;During the third (second incremental) load, data with dates &lt;code&gt;2025-02-09&lt;/code&gt; - &lt;code&gt;2025-03-08&lt;/code&gt; is added (highlighted in blue).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, the sales dates follow the load dates for both the product and the customer. The dates are chosen purely for visual clarity, with a one-month interval.&lt;/p&gt;

&lt;p&gt;The complete &lt;code&gt;simTestLoadScenario&lt;/code&gt; table looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tls_id&lt;/th&gt;
&lt;th&gt;tls_name&lt;/th&gt;
&lt;th&gt;tls_description&lt;/th&gt;
&lt;th&gt;tls_Customer_load_id&lt;/th&gt;
&lt;th&gt;tls_Product_load_id&lt;/th&gt;
&lt;th&gt;tls_Sales_load_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;C-VT01&lt;/td&gt;
&lt;td&gt;Customer valid test initial load&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C-VT02&lt;/td&gt;
&lt;td&gt;Customer valid test incremental load 1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;C-VT03&lt;/td&gt;
&lt;td&gt;Customer valid test incremental load 2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;P-VT01&lt;/td&gt;
&lt;td&gt;Product valid test initial load&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;P-VT02&lt;/td&gt;
&lt;td&gt;Product valid test incremental load 1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;P-VT03&lt;/td&gt;
&lt;td&gt;Product valid test incremental load 2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;S-VT01&lt;/td&gt;
&lt;td&gt;Sales valid test initial load&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;S-VT02&lt;/td&gt;
&lt;td&gt;Sales valid test incremental load 1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;S-VT03&lt;/td&gt;
&lt;td&gt;Sales valid test incremental load 2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;CLR&lt;/td&gt;
&lt;td&gt;Clear loaded data&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;A-VT01&lt;/td&gt;
&lt;td&gt;All data valid test initial load&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;A-VT02&lt;/td&gt;
&lt;td&gt;All data valid test incremental load 1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;A-VT03&lt;/td&gt;
&lt;td&gt;All data valid test incremental load 2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Identifier &lt;code&gt;10&lt;/code&gt; is reserved for clearing the loaded data between detailed single-table loads and simultaneous all-table loads, allowing the process to be restarted from the initial load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;We have explored the process of creating small test datasets and a data simulator. The simulator allows us to simulate both initial and incremental loads. Such simulation is possible if the data formats for the initial and incremental loads are identical. A small dataset allows for visual verification of data correctness during loading and is highly convenient for implementing and debugging data pipeline logic in real-world DWHs. For this type of debugging, SQLite is perfectly sufficient. The data cleaned in the previous article will be used later to test the fully built pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source code for this article: &lt;a href="https://github.com/dyacc11/DWH-StepByStep/tree/main/Part_02" rel="noopener noreferrer"&gt;DWH Step By Step Part 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/dyacc11/preparing-clean-data-for-a-dwh-lhc"&gt;Part 1: Preparing Clean Data for a DWH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dwh</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
    <item>
      <title>Preparing Clean Data for a DWH</title>
      <dc:creator>Dmitry</dc:creator>
      <pubDate>Sun, 25 Oct 2026 18:18:13 +0000</pubDate>
      <link>https://dev.to/dyacc11/preparing-clean-data-for-a-dwh-lhc</link>
      <guid>https://dev.to/dyacc11/preparing-clean-data-for-a-dwh-lhc</guid>
      <description>&lt;p&gt;&lt;em&gt;"On two occasions I have been asked, 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."&lt;br&gt;
Charles Babbage, 1864&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The "Product-Sales-Customer" model is a classic example of a star schema for a Data Warehouse (DWH) according to Ralph Kimball's methodology. The underlying business process is intuitively understandable to almost anyone.&lt;/p&gt;

&lt;p&gt;When developing an ETL pipeline (data transformation pipeline), a data engineer needs reliable source data on which to safely design and debug the logic.&lt;/p&gt;

&lt;p&gt;An excellent dataset for such tasks can be found in the GitHub repository prepared by Baraa Khatib Salkini: &lt;a href="https://github.com/DataWithBaraa/sql-data-warehouse-project" rel="noopener noreferrer"&gt;sql-data-warehouse-project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The source data is stored in the &lt;code&gt;01_datasets/source_crm&lt;/code&gt; directory, and its main structure can be described by the following diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85qfgeap7ru2suhpi7kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85qfgeap7ru2suhpi7kv.png" alt="Diagram1" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As can be seen from the diagram, the model consists of one fact table and two dimension tables. The "Product" dimension is implemented as a Slowly Changing Dimension Type 2 (SCD 2), meaning it stores the full history of changes. The "Customer" dimension, on the contrary, is implemented as a Slowly Changing Dimension Type 1 (SCD 1): it does not store history, but simply overwrites attributes, keeping only their current state.&lt;/p&gt;

&lt;p&gt;Overall, this is an excellent dataset containing 18,495 customers, 397 products, and 60,398 records in the sales fact table.&lt;/p&gt;

&lt;p&gt;However, in its raw form, this data has quality and integrity issues. The purpose of this article is to describe how to clean up this data so that it can be correctly used for DWH modeling and configuring the ETL pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Structure Description
&lt;/h2&gt;
&lt;h3&gt;
  
  
  "Product" Dimension (&lt;code&gt;prd_info&lt;/code&gt; table)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;prd_id&lt;/td&gt;
&lt;td&gt;surrogate product key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_key&lt;/td&gt;
&lt;td&gt;product business key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_nm&lt;/td&gt;
&lt;td&gt;product name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_cost&lt;/td&gt;
&lt;td&gt;product cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_line&lt;/td&gt;
&lt;td&gt;product line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_start_dt&lt;/td&gt;
&lt;td&gt;validity period start date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prd_end_dt&lt;/td&gt;
&lt;td&gt;validity period end date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For different validity periods of the same product, the business key remains unchanged, while the surrogate key changes. The currently active record has a &lt;code&gt;NULL&lt;/code&gt; value in the end date field.&lt;/p&gt;

&lt;p&gt;Example of the first few records from the table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prd_id&lt;/th&gt;
&lt;th&gt;prd_key&lt;/th&gt;
&lt;th&gt;prd_nm&lt;/th&gt;
&lt;th&gt;prd_cost&lt;/th&gt;
&lt;th&gt;prd_line&lt;/th&gt;
&lt;th&gt;prd_start_dt&lt;/th&gt;
&lt;th&gt;prd_end_dt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;210&lt;/td&gt;
&lt;td&gt;CO-RF-FR-R92B-58&lt;/td&gt;
&lt;td&gt;HL Road Frame - Black- 58&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;2003-07-01&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;211&lt;/td&gt;
&lt;td&gt;CO-RF-FR-R92R-58&lt;/td&gt;
&lt;td&gt;HL Road Frame - Red- 58&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;2003-07-01&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;212&lt;/td&gt;
&lt;td&gt;AC-HE-HL-U509-R&lt;/td&gt;
&lt;td&gt;Sport-100 Helmet- Red&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;2011-07-01&lt;/td&gt;
&lt;td&gt;2007-12-28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;213&lt;/td&gt;
&lt;td&gt;AC-HE-HL-U509-R&lt;/td&gt;
&lt;td&gt;Sport-100 Helmet- Red&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;2012-07-01&lt;/td&gt;
&lt;td&gt;2008-12-27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;td&gt;AC-HE-HL-U509-R&lt;/td&gt;
&lt;td&gt;Sport-100 Helmet- Red&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;2013-07-01&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Right here, we can already notice data quality issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product cost is missing in the first two rows.&lt;/li&gt;
&lt;li&gt;The validity period end date is earlier than its start date.&lt;/li&gt;
&lt;li&gt;Even if we swap these dates, the validity periods will still overlap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's note these errors as tasks that require fixing directly in the data.&lt;/p&gt;
&lt;h3&gt;
  
  
  "Customer" Dimension (&lt;code&gt;cust_info&lt;/code&gt; table)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cst_id&lt;/td&gt;
&lt;td&gt;surrogate customer key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_key&lt;/td&gt;
&lt;td&gt;customer business key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_firstname&lt;/td&gt;
&lt;td&gt;customer first name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_lastname&lt;/td&gt;
&lt;td&gt;customer last name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_marital_status&lt;/td&gt;
&lt;td&gt;customer marital status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_gndr&lt;/td&gt;
&lt;td&gt;customer gender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cst_create_date&lt;/td&gt;
&lt;td&gt;attribute creation or update date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example of a few records from the table (at the beginning and in the middle of the sample):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_marital_status&lt;/th&gt;
&lt;th&gt;cst_gndr&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;11000&lt;/td&gt;
&lt;td&gt;AW00011000&lt;/td&gt;
&lt;td&gt;Jon&lt;/td&gt;
&lt;td&gt;Yang&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;2025-10-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11001&lt;/td&gt;
&lt;td&gt;AW00011001&lt;/td&gt;
&lt;td&gt;Eugene&lt;/td&gt;
&lt;td&gt;Huang&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;2025-10-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29466&lt;/td&gt;
&lt;td&gt;AW00029466&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29466&lt;/td&gt;
&lt;td&gt;AW00029466&lt;/td&gt;
&lt;td&gt;Lance&lt;/td&gt;
&lt;td&gt;Jimenez&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;&lt;em&gt;NULL&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2026-01-26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29466&lt;/td&gt;
&lt;td&gt;AW00029466&lt;/td&gt;
&lt;td&gt;Lance&lt;/td&gt;
&lt;td&gt;Jimenez&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;2026-01-27&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is worth noting here that this is a Type 1 SCD dimension. For the same combination of surrogate and business keys, there are multiple records, but for analytics, only the latest one (with the maximum date) should be used.&lt;/p&gt;
&lt;h3&gt;
  
  
  "Sales" Fact Table (&lt;code&gt;sales_details&lt;/code&gt; table)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sls_ord_num&lt;/td&gt;
&lt;td&gt;order business key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_prd_key&lt;/td&gt;
&lt;td&gt;product business key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_cust_id&lt;/td&gt;
&lt;td&gt;surrogate customer key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_order_dt&lt;/td&gt;
&lt;td&gt;order date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_ship_dt&lt;/td&gt;
&lt;td&gt;shipping date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_due_dt&lt;/td&gt;
&lt;td&gt;planned delivery date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_quantity&lt;/td&gt;
&lt;td&gt;quantity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_price&lt;/td&gt;
&lt;td&gt;price per unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sls_sales&lt;/td&gt;
&lt;td&gt;sales amount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example of a few records from the table (at the beginning and in the middle of the sample):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SO43697&lt;/td&gt;
&lt;td&gt;BK-R93R-62&lt;/td&gt;
&lt;td&gt;21768&lt;/td&gt;
&lt;td&gt;20101229&lt;/td&gt;
&lt;td&gt;20110105&lt;/td&gt;
&lt;td&gt;20110110&lt;/td&gt;
&lt;td&gt;3578&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3578&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO43698&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;28389&lt;/td&gt;
&lt;td&gt;20101229&lt;/td&gt;
&lt;td&gt;20110105&lt;/td&gt;
&lt;td&gt;20110110&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO43699&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;25863&lt;/td&gt;
&lt;td&gt;20101229&lt;/td&gt;
&lt;td&gt;20110105&lt;/td&gt;
&lt;td&gt;20110110&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO51181&lt;/td&gt;
&lt;td&gt;HL-U509-R&lt;/td&gt;
&lt;td&gt;12132&lt;/td&gt;
&lt;td&gt;20121228&lt;/td&gt;
&lt;td&gt;20130104&lt;/td&gt;
&lt;td&gt;20130109&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO51182&lt;/td&gt;
&lt;td&gt;BK-R79Y-42&lt;/td&gt;
&lt;td&gt;22998&lt;/td&gt;
&lt;td&gt;20121228&lt;/td&gt;
&lt;td&gt;20130104&lt;/td&gt;
&lt;td&gt;20130109&lt;/td&gt;
&lt;td&gt;1701&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1701&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The following features are noticeable here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product key in the fact table is shortened: &lt;code&gt;HL-U509-R&lt;/code&gt; is used instead of the full key &lt;code&gt;AC-HE-HL-U509-R&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dates are presented in a different format: without using a hyphen as a separator.&lt;/li&gt;
&lt;li&gt;The product cost does not match: the fact table shows a price of 35, while the maximum price for this product in the product dimension is 13.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer to the first observation lies in the data structure: the product category is encoded at the beginning of the product dimension's business key. This can be seen in the category reference file &lt;code&gt;source_erp/PX_CAT_G1V2.csv&lt;/code&gt;, which contains the following information:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;CAT&lt;/th&gt;
&lt;th&gt;SUBCAT&lt;/th&gt;
&lt;th&gt;MAINTENANCE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AC_HE&lt;/td&gt;
&lt;td&gt;Accessories&lt;/td&gt;
&lt;td&gt;Helmets&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Data Correction
&lt;/h2&gt;

&lt;p&gt;So, we have reviewed the data structure and identified errors within it. Our task is to correct this data.&lt;/p&gt;

&lt;p&gt;But first, we will simplify the structure to leave only three entities, as shown in the diagram above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will remove categories and the product line. With these additional attributes, the data schema would turn into a "Snowflake" schema, so for simplicity, we will omit them. To do this:

&lt;ul&gt;
&lt;li&gt;Remove the &lt;code&gt;prd_line&lt;/code&gt; field from the &lt;code&gt;prd_info&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Cut the first 6 characters from the business key &lt;code&gt;prd_key&lt;/code&gt;, leaving only the product code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;cst_marital_status&lt;/code&gt; and &lt;code&gt;cst_gndr&lt;/code&gt; fields from the &lt;code&gt;cust_info&lt;/code&gt; table for the same reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will perform all operations in a SQLite database, as the data volume is relatively small and a single-user engine is more than enough for us. The correction process can be described by the following diagram using the &lt;code&gt;prd_info&lt;/code&gt; table as an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqi9g3z4dbf61znf7dcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqi9g3z4dbf61znf7dcn.png" alt="Diagram2" width="799" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence of actions is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import the &lt;code&gt;prd_info&lt;/code&gt; table from the &lt;code&gt;prd_info.csv&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Copy the required fields into the &lt;code&gt;Product&lt;/code&gt; table, where we also create several auxiliary fields. In doing so, some fields are populated immediately during the copy process.&lt;/li&gt;
&lt;li&gt;Using a sequence of queries, we populate the remaining auxiliary fields by generating the necessary data in them.&lt;/li&gt;
&lt;li&gt;Then, we update the main data, using the auxiliary fields as variables for the correction algorithms.&lt;/li&gt;
&lt;li&gt;Upon completion of the correction, before writing to files, we delete the auxiliary fields. Since they may also be needed for correcting other tables (e.g., the fact table), they are deleted only after all tables have been processed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It should be noted that the majority of the data that needs to be corrected consists of dates. We will regenerate them randomly, but in a way that keeps them consistent. The SQLite query language does not support procedural programming (variables, conditions, loops), so the initial constants for dates and their ranges can be set using a CTE (Common Table Expression):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; 
&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- Start date&lt;/span&gt;
        &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;            &lt;span class="c1"&gt;-- Generation interval in days&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we set the start date &lt;code&gt;bounds.A&lt;/code&gt; (&lt;code&gt;2025-01-01&lt;/code&gt;) and the generation range for the entire year of 2025 - 365 days from the start date (&lt;code&gt;bounds.B&lt;/code&gt;).&lt;br&gt;
Now, let's look at the data correction for each table separately.&lt;/p&gt;
&lt;h3&gt;
  
  
  Correction of &lt;code&gt;cust_info&lt;/code&gt; Table Data
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Analysis of &lt;code&gt;cust_info&lt;/code&gt; Table Data
&lt;/h4&gt;

&lt;p&gt;Let's load the data from the &lt;code&gt;cust_info.csv&lt;/code&gt; file into a database table, for example &lt;code&gt;data_fix.db&lt;/code&gt; (the name can be anything), specifying that missing values during import should be replaced with &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that, let's run a simple analytical query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueIdAndKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;IdNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;KeyNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CreatedDateNullCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cust_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalRows&lt;/th&gt;
&lt;th&gt;UniqueIds&lt;/th&gt;
&lt;th&gt;UniqueKeys&lt;/th&gt;
&lt;th&gt;UniqueIdAndKey&lt;/th&gt;
&lt;th&gt;IdNullCount&lt;/th&gt;
&lt;th&gt;KeyNullCount&lt;/th&gt;
&lt;th&gt;CreatedDateNullCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;18494&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;18488&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see from the results, we have four rows with an empty surrogate key, while there are no rows with an empty business key.&lt;/p&gt;

&lt;p&gt;These rows can be displayed separately by running the query &lt;code&gt;SELECT * FROM cust_info WHERE cst_id IS NULL&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;cst_key&lt;/th&gt;
&lt;th&gt;cst_firstname&lt;/th&gt;
&lt;th&gt;cst_lastname&lt;/th&gt;
&lt;th&gt;cst_marital_status&lt;/th&gt;
&lt;th&gt;cst_gndr&lt;/th&gt;
&lt;th&gt;cst_create_date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;SF566&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;PO25&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;13451235&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;A01Ass&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is evident that, apart from business keys, there are no other attributes for these records. All empty values are &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All these rows can be safely deleted from the table, as the fact table uses the surrogate key to link with customers, which is absent in these records. That is, these records will not affect the facts in any way.&lt;/p&gt;

&lt;p&gt;Let's modify the query to exclude such records and count duplicates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueIdAndKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;IdDuplicatesCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;KeyDuplicatesCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;IdAndKeyDuplicatesCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CreatedDateNullCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cust_info&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalRows&lt;/th&gt;
&lt;th&gt;UniqueIds&lt;/th&gt;
&lt;th&gt;UniqueKeys&lt;/th&gt;
&lt;th&gt;UniqueIdAndKey&lt;/th&gt;
&lt;th&gt;IdDuplicatesCount&lt;/th&gt;
&lt;th&gt;KeyDuplicatesCount&lt;/th&gt;
&lt;th&gt;IdAndKeyDuplicatesCount&lt;/th&gt;
&lt;th&gt;CreatedDateNullCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;18490&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, there are six duplicate records based on the combination of surrogate and business keys. At the same time, when counting unique combinations, the duplicate rows themselves are also taken into account. The distribution of duplicates by their count for each surrogate key can be calculated as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dupsCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;minCreationDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;maxCreationDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;julianday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;julianday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;maxDateDiff&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cust_info&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cst_id&lt;/th&gt;
&lt;th&gt;dupsCount&lt;/th&gt;
&lt;th&gt;minCreationDate&lt;/th&gt;
&lt;th&gt;maxCreationDate&lt;/th&gt;
&lt;th&gt;maxDateDiff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;29433&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;2026-01-27&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29449&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;2026-01-26&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29466&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;2026-01-27&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29473&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;2026-01-26&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29483&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;2026-01-27&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the maximum number of duplicates for a single surrogate key can be 3. There is only one such group, while the rest contain two records each. The interval between creation dates in these groups is a maximum of two days.&lt;/p&gt;

&lt;p&gt;Thus, if we generate records randomly within the year 2025, we can safely use a difference of one day for duplicates. Additionally, we have the ability to use sorting by creation date for ranking records.&lt;/p&gt;

&lt;p&gt;Now we have all the necessary information to generate new modification dates for customers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating the &lt;code&gt;Customer&lt;/code&gt; Table
&lt;/h4&gt;

&lt;p&gt;Earlier, we decided to remove the &lt;code&gt;cst_marital_status&lt;/code&gt; and &lt;code&gt;cst_gndr&lt;/code&gt; fields. Instead, we will add three auxiliary fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;dupsCnt&lt;/td&gt;
&lt;td&gt;Number of duplicates for the surrogate key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dupNo&lt;/td&gt;
&lt;td&gt;Duplicate number, sorted by creation date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newDate&lt;/td&gt;
&lt;td&gt;New random creation date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We save the new date in an additional column so we can verify the data without overwriting the original dates.&lt;/p&gt;

&lt;p&gt;The full query looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt;
&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cst_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cst_firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cst_lastname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cst_create_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dupCnt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cst_create_date&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dupNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RANDOM&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;newDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cust_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, all dates in the &lt;code&gt;newDate&lt;/code&gt; column fall within the year 2025. However, for duplicates, they might not be sequential (i.e., not exactly one day apart), which is easy to see by running the query &lt;code&gt;SELECT * FROM Customer WHERE dupCnt &amp;gt; 1;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since the number of duplicates does not exceed three, this situation can be fixed using two queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;
   &lt;span class="n"&gt;newDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+1 days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cst_id&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupNo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupNo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupCnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;
   &lt;span class="n"&gt;newDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+1 days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cst_id&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupNo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupNo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dupCnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing these queries, you can verify that the dates for duplicates of the same customer are spaced exactly one day apart.&lt;/p&gt;

&lt;p&gt;There are two steps left. The first step will transfer the new date from the &lt;code&gt;newDate&lt;/code&gt; field to the original &lt;code&gt;cst_create_date&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;cst_create_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following queries will remove the auxiliary fields that were used during the calculations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;dupCnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;dupNo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final verification query requires the &lt;code&gt;sales_details&lt;/code&gt; fact table to be loaded into the database as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sls_cust_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cst_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns no records. This means that all customers are used in the fact table, and nothing needs to be deleted from the "Customer" dimension.&lt;/p&gt;

&lt;p&gt;Now, we can export the table to a &lt;code&gt;Customer.csv&lt;/code&gt; file with column headers, keeping &lt;code&gt;NULL&lt;/code&gt; values as empty strings.&lt;/p&gt;

&lt;p&gt;This concludes the correction of the "Customer" dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Correction for the &lt;code&gt;prd_info&lt;/code&gt; Table
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Analysis of &lt;code&gt;prd_info&lt;/code&gt; Table Data
&lt;/h4&gt;

&lt;p&gt;First, let's check the table using a query similar to the initial statistics query for the &lt;code&gt;cust_info&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalRows&lt;/th&gt;
&lt;th&gt;UniqueIds&lt;/th&gt;
&lt;th&gt;UniqueKeys&lt;/th&gt;
&lt;th&gt;UniqueIdAndKey&lt;/th&gt;
&lt;th&gt;IdNullCount&lt;/th&gt;
&lt;th&gt;KeyNullCount&lt;/th&gt;
&lt;th&gt;StartDateNullCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;295&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see from the results, the number of unique surrogate keys equals the total number of rows in the table. Furthermore, the combination of the surrogate key and the business key is also unique across the entire table. There are no records with empty values for either the surrogate key or the business key. Moreover, there are no &lt;code&gt;NULL&lt;/code&gt; values in the interval start date.&lt;/p&gt;

&lt;p&gt;Note: all statistical queries are available in the repository within the &lt;code&gt;stat_queries.sql&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;As for the number of possible intervals and whether they are open or closed, this data can be obtained using another statistical query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;group_stats&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;-- calc each group&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalInGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prd_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;FinalPrdId&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;prd_info&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;final_null_stat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- add HasFinalNulls to check if last prd_end_dt is NULL&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
     &lt;span class="n"&gt;group_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;group_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalInGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;prd_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;HasFinalNulls&lt;/span&gt; 
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;group_stats&lt;/span&gt;
  &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;prd_info&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;prd_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FinalPrdId&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;TotalInGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;groupsCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HasFinalNulls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalNotNullEndDateCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HasFinalNulls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalNullEndDateCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;final_null_stat&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;TotalInGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query returns the following data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalInGroup&lt;/th&gt;
&lt;th&gt;groupsCount&lt;/th&gt;
&lt;th&gt;TotalNotNullEndDateCount&lt;/th&gt;
&lt;th&gt;TotalNullEndDateCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;218&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the maximum number of records in a group is three. At the same time, all groups with three records have an open interval: the last &lt;code&gt;prd_end_dt&lt;/code&gt; date is &lt;code&gt;NULL&lt;/code&gt;. Among the groups with two records, there are both open and closed intervals, meaning they have a specific finite end date for &lt;code&gt;prd_end_dt&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating the &lt;code&gt;Product&lt;/code&gt; Table
&lt;/h4&gt;

&lt;p&gt;To correctly generate intervals for products given these input conditions, we will need three random dates, and they must be sequential so that SCD 2 is filled correctly.&lt;/p&gt;

&lt;p&gt;For this, in the processing table, we will introduce not one, but three dates, and we will fill them in a more complex way.&lt;/p&gt;

&lt;p&gt;We will add seven auxiliary fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;recCnt&lt;/td&gt;
&lt;td&gt;number of records in the group for the business key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;sequential number of the record in the group, sorted by surrogate key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date1&lt;/td&gt;
&lt;td&gt;new random date 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date2&lt;/td&gt;
&lt;td&gt;new random date 2, such that date2 &amp;gt; date1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date3&lt;/td&gt;
&lt;td&gt;new random date 3, such that date3 &amp;gt; date2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;min_dt&lt;/td&gt;
&lt;td&gt;minimum new date among records in the group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max_dt&lt;/td&gt;
&lt;td&gt;maximum new date among records in the group&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Since we have no duplicates, slightly different names are used for the number of records in the group and the sequential number of the record.&lt;/p&gt;

&lt;p&gt;The full query looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;prd_id&lt;/span&gt; 
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prd_nm&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prd_cost&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prd_start_dt&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prd_end_dt&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;recCnt&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;min_dt&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_dt&lt;/span&gt;    
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;prd_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We leave all new dates empty for now, we trim the business key to remove categories, and we do not include the product line.&lt;/p&gt;

&lt;p&gt;We can immediately delete from the products those records that are not represented in the facts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will delete 220 rows, leaving 177 that are linked to facts. This will also delete records with empty prices.&lt;/p&gt;

&lt;p&gt;The next step is to generate three dates. Since procedural capabilities are not used, we can generate them for each business key using a recursive CTE with a limit of three rows. The result will be in the form of "sequential number-date", and this needs to be transposed into additional columns. SQLite does not support &lt;code&gt;PIVOT&lt;/code&gt;, so we transpose manually using &lt;code&gt;FILTER&lt;/code&gt; and aggregate functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt;
&lt;span class="c1"&gt;-- 1. get unique business keys&lt;/span&gt;
&lt;span class="n"&gt;target_list&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- 2. range for dates generation&lt;/span&gt;
&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- 3. recursive generator for each key&lt;/span&gt;
&lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;-- first date&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RANDOM&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;target_list&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="c1"&gt;-- next dates, up to 3 for each key&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RANDOM&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- 4. remove duplicates for each key&lt;/span&gt;
&lt;span class="n"&gt;unique_dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_result&lt;/span&gt; 
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt; 
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_result&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- 5. final output with numbering (1, 2, 3) for each key&lt;/span&gt;
&lt;span class="n"&gt;num_data&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
 &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;date_result&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;genId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;date_result&lt;/span&gt;
 &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;unique_dates&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;final_data&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;genId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;genId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;genId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;num_data&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; 
   &lt;span class="n"&gt;date1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;
   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;
   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date3&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;final_data&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: The random number generation algorithm used in the query does not guarantee the generation of all three sequential dates: there is a possibility that one or two dates may be &lt;code&gt;NULL&lt;/code&gt; due to the random selection of the first date within the specified range. To verify this, use the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it returns any rows, the date generation query should be executed again.&lt;/p&gt;

&lt;p&gt;Now we have the &lt;code&gt;date1&lt;/code&gt;, &lt;code&gt;date2&lt;/code&gt;, &lt;code&gt;date3&lt;/code&gt; fields filled, and we need to fill &lt;code&gt;prd_start_dt&lt;/code&gt; and &lt;code&gt;prd_end_dt&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;The following diagram shows how this is done for a group of three records with an open interval:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42m4a8a9igbyb36n1bh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42m4a8a9igbyb36n1bh2.png" alt="Diagram3" width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, we fill &lt;code&gt;prd_start_dt&lt;/code&gt; from &lt;code&gt;date1&lt;/code&gt; for the first record. The value from &lt;code&gt;date2&lt;/code&gt; is used both for &lt;code&gt;prd_start_dt&lt;/code&gt; of the second record and for &lt;code&gt;prd_end_dt&lt;/code&gt; of the first. The value from &lt;code&gt;date3&lt;/code&gt; is used both for &lt;code&gt;prd_start_dt&lt;/code&gt; of the third record and for &lt;code&gt;prd_end_dt&lt;/code&gt; of the second.&lt;/p&gt;

&lt;p&gt;If we have a closed interval for two records, the same algorithm applies.&lt;/p&gt;

&lt;p&gt;In the general case, here are three queries that implement this algorithm, taking into account records with &lt;code&gt;NULL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;prd_start_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;prd_start_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;prd_start_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The presence of the auxiliary field &lt;code&gt;recCnt&lt;/code&gt;, strictly speaking, is not required for the algorithm, but it allows you to quickly make slices by groups and verify its correctness.&lt;/p&gt;

&lt;p&gt;The last fields &lt;code&gt;min_dt&lt;/code&gt; and &lt;code&gt;max_dt&lt;/code&gt; are not needed for correcting the &lt;code&gt;Product&lt;/code&gt; table itself. They are used for correcting the sales table.&lt;/p&gt;

&lt;p&gt;They are filled with a separate query, assuming that the maximum date for the generation range, if the interval is open - the last day of 2025:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;_rngs&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="k"&gt;MIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;prd_start_dt&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="k"&gt;MAX&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;prd_end_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2025-12-31'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;mx&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; 
   &lt;span class="n"&gt;min_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_rngs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mn&lt;/span&gt;
   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_rngs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mx&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_rngs&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_rngs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Product&lt;/code&gt; table is ready for export. But we will not delete the auxiliary fields in it and export it until we correct the &lt;code&gt;Sales&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Correction for &lt;code&gt;sales_details&lt;/code&gt; Table
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Analysis of &lt;code&gt;sales_details&lt;/code&gt; Table Data
&lt;/h4&gt;

&lt;p&gt;First, let's check the table using a query similar to the initial statistics query for the &lt;code&gt;cust_info&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sls_prd_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueProductKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sls_cust_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueCustKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;sls_cust_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;UniqueProdAndCust&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;IdsNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ProductKeysNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sls_cust_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CustKeysNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sls_order_dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;OrderDateNullCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sls_quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;QuantityNullCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalRows&lt;/th&gt;
&lt;th&gt;UniqueIds&lt;/th&gt;
&lt;th&gt;UniqueProductKeys&lt;/th&gt;
&lt;th&gt;UniqueCustKeys&lt;/th&gt;
&lt;th&gt;UniqueProdAndCust&lt;/th&gt;
&lt;th&gt;IdsNullCount&lt;/th&gt;
&lt;th&gt;ProductKeysNullCount&lt;/th&gt;
&lt;th&gt;CustKeysNullCount&lt;/th&gt;
&lt;th&gt;OrderDateNullCount&lt;/th&gt;
&lt;th&gt;QuantityNullCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;60398&lt;/td&gt;
&lt;td&gt;27659&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;18484&lt;/td&gt;
&lt;td&gt;58922&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, with 60,398 rows in the table, there are only 27,659 unique business keys. There are no records with empty business keys or records without references to products or customers. There are also no NULL values in the quantity of purchased goods or the order date.&lt;/p&gt;

&lt;p&gt;The fact that there are fewer business keys deserves separate consideration. Let's generate statistics on the number of records per business key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;group_stats&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;-- calc each group&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;groupCount&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;groupCount&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TotalInGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;GroupsCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;group_stats&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;groupCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TotalInGroup&lt;/th&gt;
&lt;th&gt;GroupsCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9668&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8163&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5870&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;696&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, there can be up to 8 records in a group with a single business key. An example of such a group:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TI-T723&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TT-T092&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;HY-1023-70&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;CL-9009&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is obvious that one order represents a sale of several different products to one customer at once.&lt;/p&gt;

&lt;p&gt;And these data can create a problem for us: for all four of these products, the date interval in which they are valid must overlap. And it is precisely in the interval of intersection of these dates that we need to generate a new order date from 2025. The same should be true for all groups except the group of one product, since there we can generate the order date directly from the product's interval.&lt;/p&gt;

&lt;p&gt;To check this, we will create an additional table &lt;code&gt;sales_check&lt;/code&gt;, into which we will take part of the data from the sales table and part of the data from the products table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
 &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sls_cust_id&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_dt&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_dt&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recCnt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;recCntProduct&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;recCntSales&lt;/span&gt;
 &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;idSales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We take only one row from the products table by &lt;code&gt;Product.id = 1&lt;/code&gt;, since the maximum and minimum dates are duplicated for all rows in the group by product change. Now we are only interested in the upper date for a group of more than one product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;max_dt&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recCntSales&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query will return a single value &lt;code&gt;2025-12-31&lt;/code&gt;, which we set as the maximum for the current date of the open interval instead of &lt;code&gt;NULL&lt;/code&gt; in the product table. Therefore, in order for all products in the group to be current, we need to take the maximum date from &lt;code&gt;min_dt&lt;/code&gt; for generating a random purchase date and use the interval from it to &lt;code&gt;2025-12-31&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hence the recommendation: if you need to generate similar data after generating random product validity intervals according to SCD2, it makes sense for such orders with the purchase of several products at the same time to use an open interval for such products, so as not to have to regenerate dates for them.&lt;/p&gt;

&lt;p&gt;If you change the condition to &lt;code&gt;WHERE recCntSales = 1&lt;/code&gt;, you can see that there will be many different dates, which indicates a closed interval for many products. If you check the number of intervals for the product dimension &lt;code&gt;recCntProduct&lt;/code&gt; for groups of more than one product, you can see that it includes the entire possible set from one to three.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating the &lt;code&gt;Sales&lt;/code&gt; Table
&lt;/h4&gt;

&lt;p&gt;Since dates are identical within an order, it makes sense to create a separate table &lt;code&gt;sales_groups_dates&lt;/code&gt; for new dates specifically by order.&lt;/p&gt;

&lt;p&gt;We will also include the following columns in it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;recCntSales&lt;/td&gt;
&lt;td&gt;to control the group size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;startDate&lt;/td&gt;
&lt;td&gt;start date of the new date generation interval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;endDate&lt;/td&gt;
&lt;td&gt;end date of the new date generation interval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newOrderDate&lt;/td&gt;
&lt;td&gt;new random order creation date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newShipDate&lt;/td&gt;
&lt;td&gt;new random shipping date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newDueDate&lt;/td&gt;
&lt;td&gt;new random planned delivery date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's fill this table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;_maxD&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;startD&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;_maxD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recCntSales&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_maxD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startD&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;startDate&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_dt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;endDate&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;newOrderDate&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;newShipDate&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;newDueDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_maxD&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;
 &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_maxD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idSales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's verify that the maximum start date of the interval &lt;code&gt;startDate&lt;/code&gt; is calculated correctly for both the case of a group and the case of a single product with a closed date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_dt&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_dt&lt;/span&gt; 
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recCntSales&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startDate&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'SO54115'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SO43698'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;min_dt&lt;/th&gt;
&lt;th&gt;max_dt&lt;/th&gt;
&lt;th&gt;recCntSales&lt;/th&gt;
&lt;th&gt;startDate&lt;/th&gt;
&lt;th&gt;endDate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SO43698&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;2025-05-03&lt;/td&gt;
&lt;td&gt;2025-06-14&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2025-05-03&lt;/td&gt;
&lt;td&gt;2025-06-14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;CL-9009&lt;/td&gt;
&lt;td&gt;2025-03-14&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;HY-1023-70&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TI-T723&lt;/td&gt;
&lt;td&gt;2025-02-18&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TT-T092&lt;/td&gt;
&lt;td&gt;2025-03-15&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;2025-12-31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, both for a group of 4 products and for a single product with a limited date, the start and end dates of the generation interval are calculated correctly.&lt;/p&gt;

&lt;p&gt;Since we now have ranges for all groups, we can generate a random date from the range for the group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;newOrderDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;julianday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;julianday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;julianday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code works for a group of one product as well.&lt;/p&gt;

&lt;p&gt;Note: the code should not generate the end date of the interval, so we do not add one more day after the remainder of the division.&lt;/p&gt;

&lt;p&gt;After generating the order date, we can randomly generate the shipping date and delivery date sequentially within a 4-day interval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;newShipDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;newDueDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newShipDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' days'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following query will help verify the correctness of the newly generated dates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;
&lt;span class="k"&gt;EXCEPT&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_start_dt&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We subtract from the selection from the entire &lt;code&gt;sales_check&lt;/code&gt; table the results of the interval join query. Both subqueries between &lt;code&gt;EXCEPT&lt;/code&gt; should return the same number of rows - 60,398, and the full query should return no rows. This means that for all products, a match was found based on the validity ranges.&lt;/p&gt;

&lt;p&gt;Now we are ready to create the &lt;code&gt;Sales&lt;/code&gt; table with the same format and key fields, but with different values, except for the value of the &lt;code&gt;sls_quantity&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_cust_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sls_order_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newShipDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sls_ship_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newDueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sls_due_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_cost&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_quantity&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sls_sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_quantity&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sls_quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_cost&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sls_price&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt;  &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_ord_num&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_cust_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_cust_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sls_prd_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_key&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;  &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_start_dt&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;gd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newOrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prd_end_dt&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also recalculate both the price and the sales amount by quantity. We convert the dates to the same format as in the original table by removing the hyphen.&lt;/p&gt;

&lt;p&gt;Using three queries, we can compare the old data with the new data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'SO54115'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SO43698'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_details&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sls_ord_num&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'SO54115'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SO43698'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;prd_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prd_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prd_nm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prd_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prd_start_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prd_end_dt&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;prd_key&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'BK-M82S-44'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'TI-T723'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'TT-T092'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'HY-1023-70'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'CL-9009'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the result of the first query, that is, the new data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SO43698&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;28389&lt;/td&gt;
&lt;td&gt;20250520&lt;/td&gt;
&lt;td&gt;20250522&lt;/td&gt;
&lt;td&gt;20250525&lt;/td&gt;
&lt;td&gt;1912&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1912&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;CL-9009&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20250923&lt;/td&gt;
&lt;td&gt;20250924&lt;/td&gt;
&lt;td&gt;20250927&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;HY-1023-70&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20250923&lt;/td&gt;
&lt;td&gt;20250924&lt;/td&gt;
&lt;td&gt;20250927&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TI-T723&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20250923&lt;/td&gt;
&lt;td&gt;20250924&lt;/td&gt;
&lt;td&gt;20250927&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TT-T092&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20250923&lt;/td&gt;
&lt;td&gt;20250924&lt;/td&gt;
&lt;td&gt;20250927&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the result of the second query, that is, the old data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sls_ord_num&lt;/th&gt;
&lt;th&gt;sls_prd_key&lt;/th&gt;
&lt;th&gt;sls_cust_id&lt;/th&gt;
&lt;th&gt;sls_order_dt&lt;/th&gt;
&lt;th&gt;sls_ship_dt&lt;/th&gt;
&lt;th&gt;sls_due_dt&lt;/th&gt;
&lt;th&gt;sls_sales&lt;/th&gt;
&lt;th&gt;sls_quantity&lt;/th&gt;
&lt;th&gt;sls_price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SO43698&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;28389&lt;/td&gt;
&lt;td&gt;20101229&lt;/td&gt;
&lt;td&gt;20110105&lt;/td&gt;
&lt;td&gt;20110110&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;CL-9009&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;HY-1023-70&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TI-T723&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SO54115&lt;/td&gt;
&lt;td&gt;TT-T092&lt;/td&gt;
&lt;td&gt;27202&lt;/td&gt;
&lt;td&gt;20130309&lt;/td&gt;
&lt;td&gt;20130316&lt;/td&gt;
&lt;td&gt;20130321&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the third query gives the prices for the products from the list and their validity range:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prd_id&lt;/th&gt;
&lt;th&gt;prd_key&lt;/th&gt;
&lt;th&gt;prd_nm&lt;/th&gt;
&lt;th&gt;prd_cost&lt;/th&gt;
&lt;th&gt;prd_start_dt&lt;/th&gt;
&lt;th&gt;prd_end_dt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;346&lt;/td&gt;
&lt;td&gt;BK-M82S-44&lt;/td&gt;
&lt;td&gt;Mountain-100 Silver- 44&lt;/td&gt;
&lt;td&gt;1912&lt;/td&gt;
&lt;td&gt;2025-05-03&lt;/td&gt;
&lt;td&gt;2025-06-14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;484&lt;/td&gt;
&lt;td&gt;CL-9009&lt;/td&gt;
&lt;td&gt;Bike Wash - Dissolver&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2025-03-14&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;487&lt;/td&gt;
&lt;td&gt;HY-1023-70&lt;/td&gt;
&lt;td&gt;Hydration Pack - 70 oz.&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;2025-05-10&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;541&lt;/td&gt;
&lt;td&gt;TI-T723&lt;/td&gt;
&lt;td&gt;Touring Tire&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2025-02-18&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;530&lt;/td&gt;
&lt;td&gt;TT-T092&lt;/td&gt;
&lt;td&gt;Touring Tire Tube&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2025-03-15&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, in the new version of the &lt;code&gt;Product&lt;/code&gt; table, both dates and prices are consistent, unlike the old data.&lt;/p&gt;

&lt;p&gt;We only need to delete the auxiliary columns used for calculations and the two additional tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;recCnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;date3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;min_dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;max_dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sales_check&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sales_groups_dates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the &lt;code&gt;Product&lt;/code&gt; and &lt;code&gt;Sales&lt;/code&gt; tables can be exported. The data correction for the test dataset is complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have explored the data correction process using a simple schema as an example. This approach is also applicable to other data models, provided the data volume is not excessively large. When building a DWH, it is best to start with a small volume of records, which makes it easier to debug the transformation logic. In such cases, data can be generated using the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating random dates within a specified range for dimensions. The interval, initially limited to the year 2025, can be extended to multiple years if necessary, while the algorithms remain the same.&lt;/li&gt;
&lt;li&gt;Generating related data (e.g., sales records) in alignment with the generated dates in the dimensions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, the data could be generated from scratch: there are readily available scripts for randomly generating first and last names, product names, and business keys. Afterward, you can populate them with randomly generated yet mutually consistent dates, as described earlier in this article.&lt;/p&gt;

&lt;p&gt;It is crucial to start working with clean, correct data from the very beginning: this will allow you to quickly identify any flaws in the transformation logic, if they exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Baraa Khatib Salkini: &lt;a href="https://github.com/DataWithBaraa/sql-data-warehouse-project" rel="noopener noreferrer"&gt;sql-data-warehouse-project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code for this article: &lt;a href="https://github.com/dyacc11/DWH-StepByStep/tree/main/Part_01" rel="noopener noreferrer"&gt;DWH Step By Step Part 1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dwh</category>
      <category>sql</category>
      <category>dataengineering</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
