<?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: Vijay Kumar</title>
    <description>The latest articles on DEV Community by Vijay Kumar (@vijaykumar13).</description>
    <link>https://dev.to/vijaykumar13</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%2F2501769%2F1b7efede-7f18-4b5f-b0ff-f2304075d32d.jpg</url>
      <title>DEV Community: Vijay Kumar</title>
      <link>https://dev.to/vijaykumar13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vijaykumar13"/>
    <language>en</language>
    <item>
      <title>Three months of sales vanished, and every dbt run was green</title>
      <dc:creator>Vijay Kumar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 22:09:21 +0000</pubDate>
      <link>https://dev.to/vijaykumar13/three-months-of-sales-vanished-and-every-dbt-run-was-green-c9c</link>
      <guid>https://dev.to/vijaykumar13/three-months-of-sales-vanished-and-every-dbt-run-was-green-c9c</guid>
      <description>&lt;p&gt;In this article I'll show you how a dbt incremental model that looked perfectly reasonable wiped out three months of finance data, why none of our checks caught it, and how we fixed it without a full refresh. If you use &lt;code&gt;delete+insert&lt;/code&gt; anywhere in your project, it's worth ten minutes to check you aren't doing the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some background
&lt;/h2&gt;

&lt;p&gt;I'm an analytics engineer at a leading North American housewares company. We are moving our SAP reporting off SAP BW and onto Snowflake and dbt, one report at a time. One of those reports is a gross-to-net finance report built from SAP's general-ledger line items. That's around 1.5 million rows per fiscal period and more than 150 million rows of history, so rebuilding it from scratch on every run was never an option.&lt;/p&gt;

&lt;p&gt;So the model was incremental, and the config looked 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="p"&gt;{{&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;materialized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'incremental'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;incremental_strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'delete+insert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;unique_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fiscal_year'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fiscal_period'&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;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stg_gl_line_items'&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;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_incremental&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;where&lt;/span&gt; &lt;span class="n"&gt;entered_on&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;dateadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endif&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reprocess anything entered in the last month, keyed on the fiscal period. It ran three times a day for weeks without a single failure, and honestly, when I first read it, it looked fine to me too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we saw
&lt;/h2&gt;

&lt;p&gt;Then the report showed zero sales for April, May and June.&lt;/p&gt;

&lt;p&gt;We only found it by accident. I was building a new commentary report on top of this table as part of the migration, and when I checked the new report's numbers, April to June were almost empty. The new report was fine. The table underneath it wasn't.&lt;/p&gt;

&lt;p&gt;When I counted rows per period for the current fiscal year, this is what came back:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fiscal period&lt;/th&gt;
&lt;th&gt;Rows in table&lt;/th&gt;
&lt;th&gt;Same period, prior year&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P004 (April)&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;~1.6M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P005 (May)&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;~1.6M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P006 (June)&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;~1.4M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fxsto4q7mt7ahcst9rznl.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%2Fxsto4q7mt7ahcst9rznl.png" alt="Rows per fiscal period: prior year vs before the fix vs after the reload" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The periods weren't empty. They had been cut down to a few dozen rows each, and that's part of the reason nothing caught it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What delete+insert actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;delete+insert&lt;/code&gt; is the strategy you pick when you want to replace a slice of a table cleanly. On every incremental run dbt does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Builds the batch, which is your model's SQL with the &lt;code&gt;is_incremental()&lt;/code&gt; filter applied.&lt;/li&gt;
&lt;li&gt;Deletes every row in the target table whose &lt;code&gt;unique_key&lt;/code&gt; matches any key in the batch.&lt;/li&gt;
&lt;li&gt;Inserts the batch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is the one to pay attention to. With &lt;code&gt;unique_key = (fiscal_year, fiscal_period)&lt;/code&gt; the key doesn't identify a row, it identifies a whole fiscal period. If April shows up anywhere in the batch, all of April gets deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it went wrong
&lt;/h2&gt;

&lt;p&gt;Accounting doesn't stop at month end. Adjustments, reclasses and corrections get posted to earlier periods all the time.&lt;/p&gt;

&lt;p&gt;So say in August someone posts a correction to April. The row was entered in August, so it passes the &lt;code&gt;entered_on &amp;gt;= current_date - 30&lt;/code&gt; filter and comes into the batch. The batch now contains the key &lt;code&gt;(2026, 004)&lt;/code&gt;. dbt deletes every April row in the table, all ~1.5 million of them, and then inserts the batch. For April, the batch has one row.&lt;/p&gt;

&lt;p&gt;Every late posting to an old period did the same thing. After a few weeks, the periods that got the most corrections had nothing left in them except those corrections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nothing caught it
&lt;/h2&gt;

&lt;p&gt;The run succeeded because dbt did exactly what the config told it to. The generic tests passed because &lt;code&gt;not_null&lt;/code&gt; and &lt;code&gt;unique&lt;/code&gt; are just as happy with 88 rows as with 1.5 million. And the table was never empty, since new data kept arriving every day, so the freshness and "zero rows" checks had nothing to complain about.&lt;/p&gt;

&lt;p&gt;In the end we only found it because someone happened to be building something else on top of the table and looked closely at the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;delete+insert&lt;/code&gt;, whatever your incremental filter brings in has to contain the entire partition for every key it touches. So instead of picking up rows that were entered recently, we pick up the periods that had recent activity, and then take every row in those periods:&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="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_incremental&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;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiscal_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fiscal_period&lt;/span&gt;&lt;span class="p"&gt;)&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;fiscal_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fiscal_period&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stg_gl_line_items'&lt;/span&gt;&lt;span class="p"&gt;)&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;entered_on&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;dateadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;current_date&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;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endif&lt;/span&gt; &lt;span class="o"&gt;%&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 late April correction brings in all of April. dbt deletes April and puts the complete April back.&lt;/p&gt;

&lt;p&gt;Yes, this costs more per run than the old filter, because it reprocesses whole periods. But that is the real cost of using this strategy correctly. The old filter only looked cheaper because it was throwing data away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repairing the data without a full refresh
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--full-refresh&lt;/code&gt; would have fixed it, but that meant rebuilding 150M+ rows and every year of history. Instead we added a variable that widens the window to every period from a given date onward:&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="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reload_from'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;none&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;where&lt;/span&gt; &lt;span class="n"&gt;fiscal_year&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;fiscal_period&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;
      &lt;span class="n"&gt;to_varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{ var("reload_from") }}'&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="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;lpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{{ var("reload_from") }}'&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&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;else&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;-- the normal whole-period filter from above&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endif&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbt build &lt;span class="nt"&gt;-s&lt;/span&gt; my_gl_model+ &lt;span class="nt"&gt;--vars&lt;/span&gt; &lt;span class="s1"&gt;'{reload_from: "2026-04-01"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail I'd point out: the date gets snapped to the fiscal period, it is never used as a plain date filter. A mid-period date would hand &lt;code&gt;delete+insert&lt;/code&gt; half a period, which is the same bug all over again.&lt;/p&gt;

&lt;p&gt;Reloading five periods was about 7.6M source rows instead of 154M. After it ran, April to June were back to between 1.37M and 1.60M rows each.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test I would add on day one
&lt;/h2&gt;

&lt;p&gt;A simple singular test that compares each period with the same period last year would have caught this the first night:&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="c1"&gt;-- tests/assert_no_period_collapse.sql&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;counts&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;fiscal_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fiscal_period&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="k"&gt;row_count&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_gl_model'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&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;2&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;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fiscal_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fiscal_period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;row_count&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="k"&gt;row_count&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;prior_year_count&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
  &lt;span class="k"&gt;on&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;fiscal_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fiscal_year&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;fiscal_period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fiscal_period&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;row_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;row_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="c1"&gt;-- tune this for your seasonality&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it returns any rows, a period is suspiciously smaller than last year's and the test fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  To sum up
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;delete+insert&lt;/code&gt;, think of the unique_key as "what am I about to delete", not as a row ID. Make your incremental filter bring in whole partitions, and design for late corrections from day one, because in finance data they're normal. A green run only tells you the pipeline executed, so add at least one volume test that compares against history. And when you need to repair data, reload a bounded window lined up with your partitions instead of reaching for &lt;code&gt;--full-refresh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to check your own project, this lists every model using the strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rl&lt;/span&gt; &lt;span class="s2"&gt;"delete+insert"&lt;/span&gt; models/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each one, look at the &lt;code&gt;is_incremental()&lt;/code&gt; filter and ask whether it can ever bring in only part of a partition. If it filters on a row-level timestamp, it probably can.&lt;/p&gt;

&lt;p&gt;This is the first in a series I'm writing about failures in the data stack where everything is green and the numbers are still wrong. Next I'll write about a dbt default that dropped 18 columns from one of our tables for six weeks without a single warning, so follow along if that sounds useful. And if your team is in the middle of moving off SAP BW, I'd be happy to compare notes.&lt;/p&gt;

</description>
      <category>dbt</category>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
