<?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: Baldwin Apps</title>
    <description>The latest articles on DEV Community by Baldwin Apps (@baldwin_apps).</description>
    <link>https://dev.to/baldwin_apps</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%2F3751190%2F143d3d8f-9e37-4511-91e4-cc3be2ab4994.png</url>
      <title>DEV Community: Baldwin Apps</title>
      <link>https://dev.to/baldwin_apps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/baldwin_apps"/>
    <language>en</language>
    <item>
      <title>SQL Pattern Series #21: The Rolling Average Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 08 Aug 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-21-the-rolling-average-pattern-ecn</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-21-the-rolling-average-pattern-ecn</guid>
      <description>&lt;p&gt;Some datasets are noisy.&lt;/p&gt;

&lt;p&gt;Daily sales fluctuate.&lt;br&gt;
Website traffic spikes.&lt;br&gt;
Sensor readings jump around.&lt;/p&gt;

&lt;p&gt;When you look at the raw values, it can be difficult to see what is actually happening.&lt;/p&gt;

&lt;p&gt;The Rolling Average Pattern helps smooth out short-term fluctuations so the underlying trend becomes easier to see.&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.amazonaws.com%2Fuploads%2Farticles%2F99ctwq6peiuvcklfyvf7.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.amazonaws.com%2Fuploads%2Farticles%2F99ctwq6peiuvcklfyvf7.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;A rolling average uses a window function with a moving frame.&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;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
    &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frame determines how many previous rows are included in the calculation.&lt;/p&gt;

&lt;p&gt;For example:&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a 7-row rolling average.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Suppose we want a rolling 7-day average of sales.&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="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&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;RollingAvg&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SalesData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of showing only today's value, the query calculates the average across the current row and the previous six rows.&lt;/p&gt;

&lt;p&gt;This smooths out day-to-day fluctuations.&lt;/p&gt;




&lt;h3&gt;
  
  
  Intermediate Note
&lt;/h3&gt;

&lt;p&gt;The frame definition is important.&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current row&lt;/li&gt;
&lt;li&gt;Previous 6 rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for a total of 7 rows.&lt;/p&gt;

&lt;p&gt;Changing the frame changes the smoothing effect.&lt;/p&gt;

&lt;p&gt;Examples:&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3-row rolling average&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30-row rolling average&lt;/p&gt;

&lt;p&gt;Larger windows create smoother trends but react more slowly to changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Raw data often contains noise.&lt;/p&gt;

&lt;p&gt;Rolling averages help reveal the signal hidden underneath.&lt;/p&gt;

&lt;p&gt;Common uses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales trends&lt;/li&gt;
&lt;li&gt;Revenue analysis&lt;/li&gt;
&lt;li&gt;Website traffic monitoring&lt;/li&gt;
&lt;li&gt;Sensor measurements&lt;/li&gt;
&lt;li&gt;Financial data&lt;/li&gt;
&lt;li&gt;Operational metrics&lt;/li&gt;
&lt;li&gt;Capacity planning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to replace the raw data.&lt;/p&gt;

&lt;p&gt;The goal is to make trends easier to see.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thinking in Windows
&lt;/h2&gt;

&lt;p&gt;A useful mental model is to imagine a small window moving down the result set.&lt;/p&gt;

&lt;p&gt;At each row:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collect the rows inside the window.&lt;/li&gt;
&lt;li&gt;Calculate the average.&lt;/li&gt;
&lt;li&gt;Move the window forward one row.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The average changes gradually because most of the rows in the window remain the same from one calculation to the next.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Dialect Note
&lt;/h2&gt;

&lt;p&gt;Rolling averages are supported by modern versions of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL 8+&lt;/li&gt;
&lt;li&gt;MariaDB&lt;/li&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Window functions are one of the most powerful features in modern SQL and are widely used in reporting and analytics workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I reach for this pattern whenever someone says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The numbers are all over the place."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A rolling average often reveals the trend that the raw data is hiding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;The Rolling Average Pattern smooths short-term fluctuations so long-term trends become easier to understand.&lt;/p&gt;

&lt;p&gt;Sometimes the most important story in the data isn't today's value.&lt;/p&gt;

&lt;p&gt;It's the direction things have been moving over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #20: The Filtered Aggregate Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 04 Aug 2026 14:27:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-20-the-filtered-aggregate-pattern-23jg</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-20-the-filtered-aggregate-pattern-23jg</guid>
      <description>&lt;h1&gt;
  
  
  SQL Pattern Series #20: The Filtered Aggregate Pattern
&lt;/h1&gt;

&lt;p&gt;Sometimes you need multiple metrics from the same dataset.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total orders&lt;/li&gt;
&lt;li&gt;completed orders&lt;/li&gt;
&lt;li&gt;cancelled orders&lt;/li&gt;
&lt;li&gt;completed revenue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common beginner approach is writing a separate query for each metric.&lt;/p&gt;

&lt;p&gt;The Filtered Aggregate Pattern lets you calculate them all in a single query.&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.amazonaws.com%2Fuploads%2Farticles%2Fh92kphs4wp34p6otuk4z.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.amazonaws.com%2Fuploads%2Farticles%2Fh92kphs4wp34p6otuk4z.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Use an aggregate function together with a &lt;code&gt;CASE&lt;/code&gt; 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;SUM&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;condition&lt;/span&gt;
        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;value&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="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;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;condition&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;END&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common aggregate functions include:&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;SUM&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;AVG&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="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aggregate only the rows you care about.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Suppose we want several metrics for each customer.&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;customer_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;total_orders&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;CASE&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&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;END&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;completed_orders&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="k"&gt;CASE&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&lt;/span&gt;
            &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;amount&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;completed_revenue&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;CASE&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Cancelled'&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;END&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;cancelled_orders&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&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;customer_id&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 returns multiple business metrics from a single grouped result set.&lt;/p&gt;




&lt;h3&gt;
  
  
  Intermediate Note
&lt;/h3&gt;

&lt;p&gt;When using &lt;code&gt;COUNT(CASE...)&lt;/code&gt;, many SQL developers omit the &lt;code&gt;ELSE&lt;/code&gt; clause entirely.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;COUNT()&lt;/code&gt; ignores &lt;code&gt;NULL&lt;/code&gt;, this naturally counts only rows that satisfy the condition.&lt;/p&gt;

&lt;p&gt;For example:&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;COUNT&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&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;END&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is equivalent to:&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;COUNT&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&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="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;END&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 why you'll often see:&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;COUNT&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&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;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in production SQL code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;The Filtered Aggregate Pattern is one of the most practical reporting techniques in SQL.&lt;/p&gt;

&lt;p&gt;It allows you to calculate multiple KPIs without repeatedly scanning the same table.&lt;/p&gt;

&lt;p&gt;Common uses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Completed vs cancelled orders&lt;/li&gt;
&lt;li&gt;Paid vs unpaid invoices&lt;/li&gt;
&lt;li&gt;Active vs inactive users&lt;/li&gt;
&lt;li&gt;Revenue by status&lt;/li&gt;
&lt;li&gt;Success vs failure counts&lt;/li&gt;
&lt;li&gt;Dashboard metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing multiple queries, you can often calculate everything in a single grouped query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thinking in Buckets
&lt;/h2&gt;

&lt;p&gt;A useful mental model is to think of each &lt;code&gt;CASE&lt;/code&gt; expression as a bucket.&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;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;amount&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;places revenue into the "Completed" bucket.&lt;/p&gt;

&lt;p&gt;The aggregate function then totals everything in that bucket.&lt;/p&gt;

&lt;p&gt;This makes complex reports much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Dialect Note
&lt;/h2&gt;

&lt;p&gt;The Filtered Aggregate Pattern works across major SQL platforms including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;MariaDB&lt;/li&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some databases also support additional syntax such as &lt;code&gt;FILTER()&lt;/code&gt;, but the &lt;code&gt;CASE&lt;/code&gt; approach is widely portable.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I reach for this pattern whenever someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we see multiple metrics in the same report?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of running separate queries, a filtered aggregate often produces everything needed in a single result set.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;The Filtered Aggregate Pattern allows you to calculate multiple metrics from the same dataset by combining aggregate functions with conditional logic.&lt;/p&gt;

&lt;p&gt;The fastest query is often the one that answers multiple questions in a single pass through the data&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>SQL Pattern Series #19: The Reset Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 01 Aug 2026 14:37:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-19-the-reset-pattern-358a</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-19-the-reset-pattern-358a</guid>
      <description>&lt;p&gt;&lt;em&gt;Choosing between surgical removal and a full reset&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #19 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The difference between &lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;TRUNCATE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When each approach is appropriate&lt;/li&gt;
&lt;li&gt;Why intent matters when removing data&lt;/li&gt;
&lt;li&gt;Common scenarios for each operation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most developers eventually need to remove data.&lt;/p&gt;

&lt;p&gt;The question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you want to remove some rows?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you want to remove everything?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer determines which tool you reach for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine an Orders table.&lt;/p&gt;

&lt;p&gt;Sometimes you need to remove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old records&lt;/li&gt;
&lt;li&gt;test data&lt;/li&gt;
&lt;li&gt;invalid transactions&lt;/li&gt;
&lt;li&gt;duplicate imports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other times you simply want to start over.&lt;/p&gt;

&lt;p&gt;Those are two very different goals.&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.amazonaws.com%2Fuploads%2Farticles%2Fuvw8jhpap69gcm14sbpn.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.amazonaws.com%2Fuploads%2Farticles%2Fuvw8jhpap69gcm14sbpn.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reset Pattern
&lt;/h2&gt;

&lt;p&gt;The Reset Pattern is about choosing the appropriate level of removal.&lt;/p&gt;

&lt;p&gt;There are two common approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Surgical Reset
&lt;/h3&gt;

&lt;p&gt;Remove only the rows you specify.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nuclear Reset
&lt;/h3&gt;

&lt;p&gt;Remove every row in the 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;TRUNCATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both remove data.&lt;/p&gt;

&lt;p&gt;But they communicate very different intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using DELETE
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;DELETE&lt;/code&gt; statement removes rows selectively.&lt;/p&gt;

&lt;p&gt;For example:&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;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-01'&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 removes only older orders.&lt;/p&gt;

&lt;p&gt;Everything else remains.&lt;/p&gt;

&lt;p&gt;Conceptually, SQL asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which rows match the condition?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Only those rows are removed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using TRUNCATE
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;TRUNCATE TABLE&lt;/code&gt; statement removes all rows.&lt;/p&gt;

&lt;p&gt;For example:&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;TRUNCATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This operation clears the table entirely.&lt;/p&gt;

&lt;p&gt;Conceptually, SQL asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you want to keep any rows?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, the table is emptied.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Many SQL mistakes occur when developers choose the wrong level of removal.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deleting too much data&lt;/li&gt;
&lt;li&gt;deleting too little data&lt;/li&gt;
&lt;li&gt;writing unnecessary filters&lt;/li&gt;
&lt;li&gt;performing large row-by-row deletions when a full reset is intended&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Reset Pattern encourages you to think about intent first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Surgical vs Nuclear
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DELETE
&lt;/h3&gt;

&lt;p&gt;Best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specific rows must be removed&lt;/li&gt;
&lt;li&gt;conditions are important&lt;/li&gt;
&lt;li&gt;only part of the data should disappear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old audit records&lt;/li&gt;
&lt;li&gt;completed jobs&lt;/li&gt;
&lt;li&gt;test users&lt;/li&gt;
&lt;li&gt;invalid transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TRUNCATE
&lt;/h3&gt;

&lt;p&gt;Best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all rows should be removed&lt;/li&gt;
&lt;li&gt;a staging table needs to be cleared&lt;/li&gt;
&lt;li&gt;test environments need a clean slate&lt;/li&gt;
&lt;li&gt;temporary data should be reset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETL staging tables&lt;/li&gt;
&lt;li&gt;development environments&lt;/li&gt;
&lt;li&gt;test datasets&lt;/li&gt;
&lt;li&gt;scratch tables&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Note on Performance
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;TRUNCATE&lt;/code&gt; often behave differently internally.&lt;/p&gt;

&lt;p&gt;In many database systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; removes rows individually&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TRUNCATE&lt;/code&gt; removes all rows using a more efficient mechanism&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, &lt;code&gt;TRUNCATE&lt;/code&gt; is often significantly faster when the goal is to empty an entire table.&lt;/p&gt;

&lt;p&gt;In some databases, TRUNCATE cannot be rolled back after execution. Always verify transactional behavior before using it in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Database Differences
&lt;/h2&gt;

&lt;p&gt;Support for &lt;code&gt;TRUNCATE&lt;/code&gt;, permissions, transaction behavior, logging, and identity reset behavior varies between database systems.&lt;/p&gt;

&lt;p&gt;Always consult your database documentation before using it in production.&lt;/p&gt;

&lt;p&gt;The pattern remains the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DELETE removes selected rows.&lt;/p&gt;

&lt;p&gt;TRUNCATE removes all rows.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use &lt;code&gt;DELETE&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cleaning specific records&lt;/li&gt;
&lt;li&gt;removing historical data&lt;/li&gt;
&lt;li&gt;fixing data quality issues&lt;/li&gt;
&lt;li&gt;applying business rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I typically use &lt;code&gt;TRUNCATE&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resetting staging tables&lt;/li&gt;
&lt;li&gt;rebuilding test environments&lt;/li&gt;
&lt;li&gt;clearing temporary datasets&lt;/li&gt;
&lt;li&gt;starting fresh&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Before removing data, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Am I removing some rows?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Am I removing all rows?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single question usually points to the correct solution.&lt;/p&gt;

&lt;p&gt;Choose the surgical tool when precision matters.&lt;/p&gt;

&lt;p&gt;Choose the nuclear option when a full reset is the goal.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #14: The Top-Per-Group Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #15: The Percent-of-Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #16: The Missing Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #17: The Reusable Logic Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #18: The Duplicate Detection Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #18: The Duplicate Detection Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-18-the-duplicate-detection-pattern-4c56</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-18-the-duplicate-detection-pattern-4c56</guid>
      <description>&lt;p&gt;&lt;em&gt;Finding values that appear more than once&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #18 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to identify duplicate values&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt; work together&lt;/li&gt;
&lt;li&gt;When duplicate detection is useful&lt;/li&gt;
&lt;li&gt;Why duplicate data can create reporting problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many data problems are obvious.&lt;/p&gt;

&lt;p&gt;Missing values.&lt;/p&gt;

&lt;p&gt;Incorrect values.&lt;/p&gt;

&lt;p&gt;Broken relationships.&lt;/p&gt;

&lt;p&gt;Duplicate data is different.&lt;/p&gt;

&lt;p&gt;Sometimes everything looks normal until the numbers stop making sense.&lt;/p&gt;

&lt;p&gt;That is where the Duplicate Detection Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a table of users:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;UserID&lt;/th&gt;
&lt;th&gt;Email&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;&lt;a href="mailto:alice@example.com"&gt;alice@example.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:bob@example.com"&gt;bob@example.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:alice@example.com"&gt;alice@example.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At first glance, the table appears fine.&lt;/p&gt;

&lt;p&gt;But one email address appears twice.&lt;/p&gt;

&lt;p&gt;If email addresses are supposed to be unique, that duplicate may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an import problem&lt;/li&gt;
&lt;li&gt;an application bug&lt;/li&gt;
&lt;li&gt;bad validation&lt;/li&gt;
&lt;li&gt;data quality issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge is finding those repeated values.&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.amazonaws.com%2Fuploads%2Farticles%2Fo1vgx6z595bcu5nz6pkr.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.amazonaws.com%2Fuploads%2Farticles%2Fo1vgx6z595bcu5nz6pkr.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Duplicate Detection Pattern
&lt;/h2&gt;

&lt;p&gt;A common solution uses:&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;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;combined with:&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;HAVING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern 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;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;column_name&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;DuplicateCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column_name&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;The query groups identical values together and returns only groups that appear more than once.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;Email&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;DuplicateCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Users&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;Email&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;This query asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which email addresses occur more than once?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;DuplicateCount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:alice@example.com"&gt;alice@example.com&lt;/a&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;The result identifies the duplicated value and shows how many times it appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Duplicate data can quietly distort reports and analytics.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer counts become inflated&lt;/li&gt;
&lt;li&gt;revenue calculations become inaccurate&lt;/li&gt;
&lt;li&gt;dashboards show incorrect totals&lt;/li&gt;
&lt;li&gt;automated processes execute multiple times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The earlier duplicates are detected, the easier they are to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  GROUP BY vs HAVING
&lt;/h2&gt;

&lt;p&gt;This pattern is a useful reminder of the difference between:&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;WHERE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&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;HAVING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  WHERE
&lt;/h3&gt;

&lt;p&gt;Filters rows before grouping.&lt;/p&gt;

&lt;h3&gt;
  
  
  HAVING
&lt;/h3&gt;

&lt;p&gt;Filters groups after grouping.&lt;/p&gt;

&lt;p&gt;For example:&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;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;Email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database must first build the groups before it can determine which groups contain duplicates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Uses
&lt;/h2&gt;

&lt;p&gt;The Duplicate Detection Pattern is frequently used to identify:&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate email addresses
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;Email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Duplicate usernames
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;UserName&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Duplicate account numbers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;AccountNumber&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Duplicate product identifiers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ProductCode&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Duplicate business keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;BusinessKey&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In each case, the goal is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find values that appear more than once.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Finding the Actual Rows
&lt;/h2&gt;

&lt;p&gt;Sometimes you need more than the duplicated value.&lt;/p&gt;

&lt;p&gt;You need the rows themselves.&lt;/p&gt;

&lt;p&gt;One common approach is:&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;Users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Email&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;Email&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Users&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;Email&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;This returns all rows that participate in a duplicate group.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Uniqueness Constraints
&lt;/h2&gt;

&lt;p&gt;Ideally, many duplicates should never occur.&lt;/p&gt;

&lt;p&gt;Database constraints such as:&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;UNIQUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can help prevent duplicate values from being inserted.&lt;/p&gt;

&lt;p&gt;However, duplicate detection remains useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auditing existing data&lt;/li&gt;
&lt;li&gt;validating imports&lt;/li&gt;
&lt;li&gt;cleaning legacy databases&lt;/li&gt;
&lt;li&gt;investigating reporting issues&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Duplicate Detection Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validating imported data&lt;/li&gt;
&lt;li&gt;troubleshooting reports&lt;/li&gt;
&lt;li&gt;checking data quality&lt;/li&gt;
&lt;li&gt;preparing data migrations&lt;/li&gt;
&lt;li&gt;auditing business keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate emails&lt;/li&gt;
&lt;li&gt;duplicate usernames&lt;/li&gt;
&lt;li&gt;duplicate customer records&lt;/li&gt;
&lt;li&gt;duplicate account numbers&lt;/li&gt;
&lt;li&gt;repeated transaction identifiers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Many SQL problems begin with a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this value appear more than once?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Duplicate Detection Pattern helps answer that question quickly.&lt;/p&gt;

&lt;p&gt;Group the values.&lt;/p&gt;

&lt;p&gt;Count the occurrences.&lt;/p&gt;

&lt;p&gt;Keep the groups greater than one.&lt;/p&gt;

&lt;p&gt;Sometimes the problem is not missing data.&lt;/p&gt;

&lt;p&gt;Sometimes it is the same data appearing twice.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #14: The Top-Per-Group Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #15: The Percent-of-Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #16: The Missing Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #17: The Reusable Logic Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #17: The Reusable Logic Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 25 Jul 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-17-the-reusable-logic-pattern-33dg</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-17-the-reusable-logic-pattern-33dg</guid>
      <description>&lt;p&gt;&lt;em&gt;Building the logic once so the final query is easier to read&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #17 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What a CTE is&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;WITH&lt;/code&gt; can make query logic easier to read&lt;/li&gt;
&lt;li&gt;Why reusable logic helps reduce complexity&lt;/li&gt;
&lt;li&gt;When to use the Reusable Logic Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some SQL queries become difficult to read because they try to do everything at once.&lt;/p&gt;

&lt;p&gt;They calculate values.&lt;/p&gt;

&lt;p&gt;Filter rows.&lt;/p&gt;

&lt;p&gt;Group data.&lt;/p&gt;

&lt;p&gt;Join tables.&lt;/p&gt;

&lt;p&gt;Apply business rules.&lt;/p&gt;

&lt;p&gt;Return results.&lt;/p&gt;

&lt;p&gt;All in one long statement.&lt;/p&gt;

&lt;p&gt;That can work.&lt;/p&gt;

&lt;p&gt;But it can also become difficult to understand.&lt;/p&gt;

&lt;p&gt;That is where the Reusable Logic Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine you need to calculate total revenue by customer, then return only customers whose revenue exceeds a threshold.&lt;/p&gt;

&lt;p&gt;You could write the logic as a nested subquery.&lt;/p&gt;

&lt;p&gt;But as the query grows, the structure becomes harder to follow.&lt;/p&gt;

&lt;p&gt;The issue is not always the SQL itself.&lt;/p&gt;

&lt;p&gt;Sometimes the issue is that the logic needs a name.&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.amazonaws.com%2Fuploads%2Farticles%2Flengd5b4r7rdneyo5h54.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.amazonaws.com%2Fuploads%2Farticles%2Flengd5b4r7rdneyo5h54.png" alt=" " width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reusable Logic Pattern
&lt;/h2&gt;

&lt;p&gt;The Reusable Logic Pattern uses a common table expression, or CTE, to name an intermediate result.&lt;/p&gt;

&lt;p&gt;The general form is:&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;cte_name&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="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="n"&gt;cte_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CTE lets you build a piece of logic once, give it a meaningful name, and query it in the final statement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;RevenueByCustomer&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;CustomerID&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;Amount&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;TotalRevenue&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&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;CustomerID&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;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TotalRevenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;RevenueByCustomer&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;TotalRevenue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part calculates revenue by customer.&lt;/p&gt;

&lt;p&gt;The final query filters the result.&lt;/p&gt;

&lt;p&gt;The logic now has a name:&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="n"&gt;RevenueByCustomer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That name makes the query easier to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;CTEs can make complex queries easier to read by separating steps.&lt;/p&gt;

&lt;p&gt;Instead of forcing the reader to understand everything at once, the query can be read in stages.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build revenue by customer.&lt;/li&gt;
&lt;li&gt;Query the result.&lt;/li&gt;
&lt;li&gt;Apply a final filter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That structure reduces cognitive load.&lt;/p&gt;

&lt;p&gt;It also makes the query easier to debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  CTEs Are Not Tables
&lt;/h2&gt;

&lt;p&gt;A CTE may look like a temporary table, but it is not necessarily stored as one.&lt;/p&gt;

&lt;p&gt;It is a named query expression available to the statement that follows it.&lt;/p&gt;

&lt;p&gt;The database optimizer decides how to process it.&lt;/p&gt;

&lt;p&gt;That means a CTE is usually best understood as a readability and structure tool, not automatically as a performance tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Naming the Logic
&lt;/h2&gt;

&lt;p&gt;The most valuable part of a CTE is often the name.&lt;/p&gt;

&lt;p&gt;Compare 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;WITH&lt;/span&gt; &lt;span class="n"&gt;x&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="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;to 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;WITH&lt;/span&gt; &lt;span class="n"&gt;RevenueByCustomer&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="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;The second version tells the reader what the intermediate result represents.&lt;/p&gt;

&lt;p&gt;Good names help turn query structure into documentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiple CTEs
&lt;/h2&gt;

&lt;p&gt;You can often use more than one CTE to build a query in steps.&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;RevenueByCustomer&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;CustomerID&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;Amount&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;TotalRevenue&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&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;CustomerID&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;HighValueCustomers&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;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TotalRevenue&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;RevenueByCustomer&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;TotalRevenue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&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;HighValueCustomers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can make multi-step logic easier to follow.&lt;/p&gt;

&lt;p&gt;Each CTE represents one stage of the process.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Database Differences
&lt;/h2&gt;

&lt;p&gt;CTEs are widely supported, but behavior can vary between database systems.&lt;/p&gt;

&lt;p&gt;Some databases may inline CTEs.&lt;/p&gt;

&lt;p&gt;Some may materialize them in certain situations.&lt;/p&gt;

&lt;p&gt;Some support recursive CTEs.&lt;/p&gt;

&lt;p&gt;Some have optimizer rules that affect performance.&lt;/p&gt;

&lt;p&gt;The syntax is broadly familiar, but always validate performance and behavior in your specific database system.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Reusable Logic Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a query has multiple logical steps&lt;/li&gt;
&lt;li&gt;nested subqueries are becoming hard to read&lt;/li&gt;
&lt;li&gt;intermediate results need meaningful names&lt;/li&gt;
&lt;li&gt;the same derived logic is referenced later&lt;/li&gt;
&lt;li&gt;I want the query to be easier to debug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;revenue by customer&lt;/li&gt;
&lt;li&gt;filtered account groups&lt;/li&gt;
&lt;li&gt;staged reporting logic&lt;/li&gt;
&lt;li&gt;pre-aggregated data&lt;/li&gt;
&lt;li&gt;multi-step analytics queries&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Complex SQL becomes easier when each part has a clear job.&lt;/p&gt;

&lt;p&gt;The Reusable Logic Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I name this piece of logic?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A CTE lets you build the logic once and query it clearly.&lt;/p&gt;

&lt;p&gt;Sometimes the best improvement is not a shorter query.&lt;/p&gt;

&lt;p&gt;It is a query that explains itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #14: The Top-Per-Group Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #15: The Percent-of-Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #16: The Missing Match Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #16: The Missing Match Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-16-the-missing-match-pattern-229l</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-16-the-missing-match-pattern-229l</guid>
      <description>&lt;p&gt;&lt;em&gt;Finding rows that have no related match&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #16 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to find rows that do not have related records&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;LEFT JOIN&lt;/code&gt; and &lt;code&gt;NULL&lt;/code&gt; often appear together&lt;/li&gt;
&lt;li&gt;When missing relationships reveal important information&lt;/li&gt;
&lt;li&gt;How the Missing Match Pattern differs from the Presence Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many SQL queries focus on what exists.&lt;/p&gt;

&lt;p&gt;Sometimes the more interesting question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is missing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers who never placed an order&lt;/li&gt;
&lt;li&gt;Users who never logged in&lt;/li&gt;
&lt;li&gt;Products that have never been sold&lt;/li&gt;
&lt;li&gt;Employees without managers&lt;/li&gt;
&lt;li&gt;Accounts with no activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where the Missing Match Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine two tables:&lt;/p&gt;

&lt;h3&gt;
  
  
  Customers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&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;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Orders
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;CustomerID&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;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&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;Alice and Bob have orders.&lt;/p&gt;

&lt;p&gt;Charlie does not.&lt;/p&gt;

&lt;p&gt;Suppose you want to find:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which customers have never placed an order?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is not stored directly.&lt;/p&gt;

&lt;p&gt;You must identify the missing relationship.&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.amazonaws.com%2Fuploads%2Farticles%2Fyf1jwc08s9l7hvn821j5.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.amazonaws.com%2Fuploads%2Farticles%2Fyf1jwc08s9l7hvn821j5.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Missing Match Pattern
&lt;/h2&gt;

&lt;p&gt;A common solution uses:&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;combined with a check for:&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;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is:&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;related_table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;related_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns rows that failed to find a match.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&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;The query keeps all customers.&lt;/p&gt;

&lt;p&gt;If a matching order exists, the order columns are populated.&lt;/p&gt;

&lt;p&gt;If no match exists, the order columns become:&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;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final filter keeps only those unmatched rows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Charlie appears because no matching order was found.&lt;/p&gt;

&lt;p&gt;That missing relationship is exactly what we were looking for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Many important business questions involve absence rather than presence.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inactive customers&lt;/li&gt;
&lt;li&gt;unsold products&lt;/li&gt;
&lt;li&gt;users who never completed onboarding&lt;/li&gt;
&lt;li&gt;employees without assignments&lt;/li&gt;
&lt;li&gt;projects without owners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Missing Match Pattern helps surface these gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Presence vs Missing Match
&lt;/h2&gt;

&lt;p&gt;The Presence Pattern asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does a matching row exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Missing Match Pattern asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does a matching row NOT exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  Presence Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns rows that have matches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing Match Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&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;related_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns rows that do not have matches.&lt;/p&gt;

&lt;p&gt;They are closely related patterns that answer opposite questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Uses
&lt;/h2&gt;

&lt;p&gt;The Missing Match Pattern appears frequently in auditing and reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customers with no orders
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Products with no sales
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&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;Sales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProductID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Employees without managers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Users who never logged in
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;LoginHistory&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;LoginHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserID&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In each case, the missing match is the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on NOT EXISTS
&lt;/h2&gt;

&lt;p&gt;Many developers also solve this problem using:&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;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many experienced SQL developers prefer &lt;code&gt;NOT EXISTS&lt;/code&gt; because it directly communicates the intent of finding rows with no matching related records.&lt;/p&gt;

&lt;p&gt;This often expresses the intent very clearly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return customers for whom no matching order exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both approaches are common.&lt;/p&gt;

&lt;p&gt;The best choice often depends on readability, team conventions, and the specific database system.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Missing Match Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;looking for inactive records&lt;/li&gt;
&lt;li&gt;validating data completeness&lt;/li&gt;
&lt;li&gt;auditing relationships&lt;/li&gt;
&lt;li&gt;identifying orphaned records&lt;/li&gt;
&lt;li&gt;finding things that should exist but do not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customers with no orders&lt;/li&gt;
&lt;li&gt;products with no sales&lt;/li&gt;
&lt;li&gt;users with no activity&lt;/li&gt;
&lt;li&gt;projects without owners&lt;/li&gt;
&lt;li&gt;records missing related data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Many SQL questions are really asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should have a match, but doesn't?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Missing Match Pattern helps answer that question.&lt;/p&gt;

&lt;p&gt;Find the relationship.&lt;/p&gt;

&lt;p&gt;Keep the failures.&lt;/p&gt;

&lt;p&gt;Sometimes the missing rows are the report.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #14: The Top-Per-Group Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #15: The Percent-of-Total Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SQL Bubble Pop&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download on iOS:&lt;br&gt;
&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn more:&lt;br&gt;
&lt;a href="https://sqlbubblepop.com" rel="noopener noreferrer"&gt;https://sqlbubblepop.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #15: The Percent-of-Total Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 18 Jul 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-15-the-percent-of-total-pattern-4bj0</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-15-the-percent-of-total-pattern-4bj0</guid>
      <description>&lt;p&gt;&lt;em&gt;Seeing each value's contribution to the whole&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #15 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to calculate percentages without a self-join&lt;/li&gt;
&lt;li&gt;How window functions simplify percent-of-total calculations&lt;/li&gt;
&lt;li&gt;Why percentages often communicate better than raw numbers&lt;/li&gt;
&lt;li&gt;When to use the Percent-of-Total Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raw numbers are useful.&lt;/p&gt;

&lt;p&gt;But sometimes the more important question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much of the total does this represent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What percentage of sales comes from each product category?&lt;/li&gt;
&lt;li&gt;What percentage of revenue comes from each region?&lt;/li&gt;
&lt;li&gt;What percentage of support tickets belong to each team?&lt;/li&gt;
&lt;li&gt;What percentage of traffic comes from each source?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where the Percent-of-Total Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a sales table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Services&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The total sales are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the raw numbers do not immediately show each category's contribution.&lt;/p&gt;

&lt;p&gt;We often want to see:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Percent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Services&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That requires comparing each value to the overall total.&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.amazonaws.com%2Fuploads%2Farticles%2F2th7cwqprph7dbdmrtzu.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.amazonaws.com%2Fuploads%2Farticles%2F2th7cwqprph7dbdmrtzu.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Percent-of-Total Pattern
&lt;/h2&gt;

&lt;p&gt;A common solution uses a window aggregate:&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calculates the grand total while still preserving individual rows.&lt;/p&gt;

&lt;p&gt;The pattern is:&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;column&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="k"&gt;column&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row divides itself by the overall total.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sales&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="mi"&gt;0&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;Sales&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;AS&lt;/span&gt; &lt;span class="n"&gt;PercentOfTotal&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SalesData&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 calculates the total sales once and makes that value available to every row.&lt;/p&gt;

&lt;p&gt;Each row then computes its own percentage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;th&gt;PercentOfTotal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Services&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To display percentages:&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="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Sales&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&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;Sales&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="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="n"&gt;PercentOfTotal&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;Category&lt;/th&gt;
&lt;th&gt;PercentOfTotal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;30.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Services&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Humans are often better at understanding proportions than raw counts.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Category A: 5,000
Category B: 3,000
Category C: 2,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Category A: 50%
Category B: 30%
Category C: 20%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The percentages immediately communicate relative importance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Window Functions Make This Easy
&lt;/h2&gt;

&lt;p&gt;Before window functions became widely available, developers often solved this problem using:&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;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sales&lt;/span&gt; &lt;span class="o"&gt;/&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sales&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;SalesData&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;SalesData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That approach works.&lt;/p&gt;

&lt;p&gt;But window functions often make the intent clearer because both the row value and the total remain visible in the same query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Percent of Total Within Groups
&lt;/h2&gt;

&lt;p&gt;Sometimes you want percentages inside groups rather than across the entire table.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What percentage of regional sales comes from each product?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can partition the total:&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sales&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;Region&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each region receives its own denominator.&lt;/p&gt;

&lt;p&gt;The same pattern applies:&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="n"&gt;Sales&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;Sales&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;Region&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Note on Integer Division
&lt;/h2&gt;

&lt;p&gt;One common mistake is:&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="n"&gt;Sales&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;Sales&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both values are integers, some databases may perform integer division.&lt;/p&gt;

&lt;p&gt;To avoid this, force decimal math:&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="n"&gt;Sales&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="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&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;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sales&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact syntax varies by database system.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Percent-of-Total Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comparing contributions&lt;/li&gt;
&lt;li&gt;building dashboards&lt;/li&gt;
&lt;li&gt;analyzing revenue distribution&lt;/li&gt;
&lt;li&gt;measuring category importance&lt;/li&gt;
&lt;li&gt;presenting executive summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sales by category&lt;/li&gt;
&lt;li&gt;revenue by region&lt;/li&gt;
&lt;li&gt;tickets by department&lt;/li&gt;
&lt;li&gt;users by platform&lt;/li&gt;
&lt;li&gt;expenses by cost center&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Raw numbers tell you how much.&lt;/p&gt;

&lt;p&gt;Percentages tell you how important.&lt;/p&gt;

&lt;p&gt;The Percent-of-Total Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What share of the whole does this represent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes contribution matters more than magnitude.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #14: The Top-Per-Group Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #14: The Top-Per-Group Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-14-the-top-per-group-pattern-3jmo</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-14-the-top-per-group-pattern-3jmo</guid>
      <description>&lt;p&gt;&lt;em&gt;Finding the most important row within each group&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #14 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to find the top row within each group&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;ROW_NUMBER()&lt;/code&gt; is commonly used for this problem&lt;/li&gt;
&lt;li&gt;How partitioning creates independent rankings&lt;/li&gt;
&lt;li&gt;When to use the Top-Per-Group Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many SQL problems involve finding a single "best" row.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The latest order for each customer&lt;/li&gt;
&lt;li&gt;The highest sale in each region&lt;/li&gt;
&lt;li&gt;The newest status update for each ticket&lt;/li&gt;
&lt;li&gt;The most recent login for each user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge is that you don't want the top row overall.&lt;/p&gt;

&lt;p&gt;You want the top row within each group.&lt;/p&gt;

&lt;p&gt;That is where the Top-Per-Group Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine an Orders table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;OrderDate&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;2024-01-15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2024-02-10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2024-01-20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2024-03-01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Suppose you need:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most recent order for each customer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simple &lt;code&gt;ORDER BY&lt;/code&gt; won't solve the problem.&lt;/p&gt;

&lt;p&gt;You need to rank rows separately within each customer group.&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.amazonaws.com%2Fuploads%2Farticles%2F3f4qd76mgtoymksnohrx.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.amazonaws.com%2Fuploads%2Farticles%2F3f4qd76mgtoymksnohrx.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Top-Per-Group Pattern
&lt;/h2&gt;

&lt;p&gt;The most common solution uses:&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="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="k"&gt;column&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window function creates a ranking inside each group.&lt;/p&gt;

&lt;p&gt;The highest-ranked row receives:&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="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are the rows we keep.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;o&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;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;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerID&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;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderDate&lt;/span&gt; &lt;span class="k"&gt;DESC&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;rn&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rn&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;This query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Groups rows by customer.&lt;/li&gt;
&lt;li&gt;Orders each customer's rows from newest to oldest.&lt;/li&gt;
&lt;li&gt;Assigns row numbers.&lt;/li&gt;
&lt;li&gt;Returns only the first row in each group.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;OrderDate&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;2024-02-10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2024-03-01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each customer now has exactly one row.&lt;/p&gt;

&lt;p&gt;The most recent one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Many reporting and analytics tasks require a single representative row.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latest customer activity&lt;/li&gt;
&lt;li&gt;newest support ticket update&lt;/li&gt;
&lt;li&gt;highest scoring transaction&lt;/li&gt;
&lt;li&gt;most recent account balance&lt;/li&gt;
&lt;li&gt;top-performing product per category&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Top-Per-Group Pattern solves these problems consistently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Partitioning Creates Independent Rankings
&lt;/h2&gt;

&lt;p&gt;The key idea is:&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;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without partitioning:&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="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would rank all rows together.&lt;/p&gt;

&lt;p&gt;With partitioning:&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="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;CustomerID&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each customer receives their own ranking sequence.&lt;/p&gt;

&lt;p&gt;Think of it as creating a separate leaderboard for each group.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Ties
&lt;/h2&gt;

&lt;p&gt;Suppose two orders have the exact same timestamp.&lt;/p&gt;

&lt;p&gt;Which row should become:&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="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a tie-breaker, the database may choose one arbitrarily.&lt;/p&gt;

&lt;p&gt;For deterministic results, consider adding another column:&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="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;CustomerID&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;OrderDate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;OrderID&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees a predictable winner.&lt;/p&gt;




&lt;h2&gt;
  
  
  ROW_NUMBER vs RANK
&lt;/h2&gt;

&lt;p&gt;The Top-Per-Group Pattern typically uses:&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="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because it guarantees exactly one row receives:&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="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functions such as:&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="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&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="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may return multiple rows when ties occur.&lt;/p&gt;

&lt;p&gt;That behavior can be useful, but it solves a slightly different problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Top-Per-Group Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I need the latest row per entity&lt;/li&gt;
&lt;li&gt;I need the highest value per category&lt;/li&gt;
&lt;li&gt;I need one representative row from each group&lt;/li&gt;
&lt;li&gt;ranking matters within groups&lt;/li&gt;
&lt;li&gt;duplicate historical records exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latest order per customer&lt;/li&gt;
&lt;li&gt;newest employee record&lt;/li&gt;
&lt;li&gt;highest sale per region&lt;/li&gt;
&lt;li&gt;most recent account activity&lt;/li&gt;
&lt;li&gt;latest status change per ticket&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Many SQL problems are really asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which row wins within each group?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Top-Per-Group Pattern answers that question.&lt;/p&gt;

&lt;p&gt;Create a ranking.&lt;/p&gt;

&lt;p&gt;Partition by the group.&lt;/p&gt;

&lt;p&gt;Keep:&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="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes finding the best row is easier than aggregating them all.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #13: The Gap Detection Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #13: The Gap Detection Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 11 Jul 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-13-the-gap-detection-pattern-2hgp</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-13-the-gap-detection-pattern-2hgp</guid>
      <description>&lt;p&gt;&lt;em&gt;Finding what is missing in a sequence&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #13 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to detect missing values in a sequence&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;LEAD()&lt;/code&gt; compares the current row to the next row&lt;/li&gt;
&lt;li&gt;Why gaps often reveal important data issues&lt;/li&gt;
&lt;li&gt;When to use the Gap Detection Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the most important rows are the ones that are not there.&lt;/p&gt;

&lt;p&gt;A missing ID.&lt;/p&gt;

&lt;p&gt;A missing date.&lt;/p&gt;

&lt;p&gt;A missing event.&lt;/p&gt;

&lt;p&gt;A skipped invoice number.&lt;/p&gt;

&lt;p&gt;A gap in a sequence can reveal that something happened.&lt;/p&gt;

&lt;p&gt;Or that something failed to happen.&lt;/p&gt;

&lt;p&gt;That is where the Gap Detection Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a table of numbers:&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At first glance, the table looks simple.&lt;/p&gt;

&lt;p&gt;But something is missing.&lt;/p&gt;

&lt;p&gt;The sequence jumps from &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;7&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means &lt;code&gt;4&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;, and &lt;code&gt;6&lt;/code&gt; are absent.&lt;/p&gt;

&lt;p&gt;A basic query can show the rows that exist.&lt;/p&gt;

&lt;p&gt;The Gap Detection Pattern helps you find the spaces between them.&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.amazonaws.com%2Fuploads%2Farticles%2F6jhik1bnqeydnj68uysh.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.amazonaws.com%2Fuploads%2Farticles%2F6jhik1bnqeydnj68uysh.png" alt=" " width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gap Detection Pattern
&lt;/h2&gt;

&lt;p&gt;The Gap Detection Pattern compares each row to the next row in sequence.&lt;/p&gt;

&lt;p&gt;A common approach uses:&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="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LEAD()&lt;/code&gt; lets the current row see a value from a following row.&lt;/p&gt;

&lt;p&gt;That makes it useful for detecting jumps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next_id&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;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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;AS&lt;/span&gt; &lt;span class="n"&gt;next_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;next_id&lt;/span&gt; &lt;span class="o"&gt;&amp;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;This query identifies rows where the next value is not the expected next value.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;id = 3&lt;/code&gt; and &lt;code&gt;next_id = 7&lt;/code&gt;, then a gap exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&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;next_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This result does not list every missing value.&lt;/p&gt;

&lt;p&gt;It shows where the gap starts and where the next available value appears.&lt;/p&gt;

&lt;p&gt;From this result, you know the missing values are between &lt;code&gt;3&lt;/code&gt; and &lt;code&gt;7&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Gaps often tell the real story.&lt;/p&gt;

&lt;p&gt;They can reveal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing records&lt;/li&gt;
&lt;li&gt;failed imports&lt;/li&gt;
&lt;li&gt;skipped IDs&lt;/li&gt;
&lt;li&gt;missing dates&lt;/li&gt;
&lt;li&gt;broken event sequences&lt;/li&gt;
&lt;li&gt;incomplete logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many systems, missing values are not obvious until you compare neighboring rows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detecting Date Gaps
&lt;/h2&gt;

&lt;p&gt;The same idea can apply to dates.&lt;/p&gt;

&lt;p&gt;For example:&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;activity_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next_activity_date&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;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;activity_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity_date&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;activity_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;next_activity_date&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;activity_log&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;next_activity_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;activity_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks whether the next date is more than one day after the current date.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Date arithmetic syntax varies by database system. The example above uses PostgreSQL-style interval syntax. SQL Server, MySQL, Oracle, and SQLite use different approaches.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Gap Detection vs Missing Match
&lt;/h2&gt;

&lt;p&gt;Gap detection and missing match are related, but they are not the same pattern.&lt;/p&gt;

&lt;p&gt;The Missing Match Pattern asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which rows do not have a related row somewhere else?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Gap Detection Pattern asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which expected values are missing from a sequence?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both patterns help reveal absence.&lt;/p&gt;

&lt;p&gt;But they look for different kinds of absence.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Sequences
&lt;/h2&gt;

&lt;p&gt;This pattern works best when there is a meaningful expected order.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invoice numbers&lt;/li&gt;
&lt;li&gt;event numbers&lt;/li&gt;
&lt;li&gt;dates&lt;/li&gt;
&lt;li&gt;monthly periods&lt;/li&gt;
&lt;li&gt;ordered checkpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is less useful when gaps are normal or meaningless.&lt;/p&gt;

&lt;p&gt;For example, many database identity columns can have gaps because of rollbacks, deletes, caching, or failed inserts.&lt;/p&gt;

&lt;p&gt;A missing ID does not always mean something is wrong.&lt;/p&gt;

&lt;p&gt;The question is whether the sequence is meaningful for the business problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Gap Detection Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sequence continuity matters&lt;/li&gt;
&lt;li&gt;records should appear in order&lt;/li&gt;
&lt;li&gt;missing dates need investigation&lt;/li&gt;
&lt;li&gt;logs appear incomplete&lt;/li&gt;
&lt;li&gt;expected events did not happen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skipped invoice numbers&lt;/li&gt;
&lt;li&gt;missing daily snapshots&lt;/li&gt;
&lt;li&gt;missing monthly reports&lt;/li&gt;
&lt;li&gt;broken event sequences&lt;/li&gt;
&lt;li&gt;incomplete audit logs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Existing rows tell you what happened.&lt;/p&gt;

&lt;p&gt;Gaps tell you what may be missing.&lt;/p&gt;

&lt;p&gt;The Gap Detection Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should be here, but is not?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the missing values are the real story.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #12: The Fallback Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>tutorial</category>
      <category>beginners</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>SQL Pattern Series #12: The Fallback Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:27:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-12-the-fallback-pattern-1c5j</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-12-the-fallback-pattern-1c5j</guid>
      <description>&lt;p&gt;&lt;em&gt;Using the first available value when data is incomplete&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #12 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What &lt;code&gt;COALESCE()&lt;/code&gt; does&lt;/li&gt;
&lt;li&gt;How fallback values help with missing data&lt;/li&gt;
&lt;li&gt;Why fallback logic makes query results easier to use&lt;/li&gt;
&lt;li&gt;When to use the Fallback Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real data is often incomplete.&lt;/p&gt;

&lt;p&gt;A customer may not have a phone number.&lt;/p&gt;

&lt;p&gt;An employee may not have a work email.&lt;/p&gt;

&lt;p&gt;A product may not have a display name.&lt;/p&gt;

&lt;p&gt;If the query returns &lt;code&gt;NULL&lt;/code&gt;, the result may be technically correct.&lt;/p&gt;

&lt;p&gt;But it may not be very useful.&lt;/p&gt;

&lt;p&gt;That is where the Fallback Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine an &lt;code&gt;Employees&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;EmployeeID&lt;/th&gt;
&lt;th&gt;WorkEmail&lt;/th&gt;
&lt;th&gt;PersonalEmail&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;&lt;a href="mailto:alice@company.com"&gt;alice@company.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:alice@gmail.com"&gt;alice@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:bob@gmail.com"&gt;bob@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You want to show one preferred email address for each employee.&lt;/p&gt;

&lt;p&gt;The rule is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the work email if it exists.&lt;/li&gt;
&lt;li&gt;Otherwise use the personal email.&lt;/li&gt;
&lt;li&gt;Otherwise show a default message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is fallback logic.&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.amazonaws.com%2Fuploads%2Farticles%2Fh9w9bulu49oyz9wiokjh.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.amazonaws.com%2Fuploads%2Farticles%2Fh9w9bulu49oyz9wiokjh.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fallback Pattern
&lt;/h2&gt;

&lt;p&gt;The Fallback Pattern uses &lt;code&gt;COALESCE()&lt;/code&gt; to return the first non-&lt;code&gt;NULL&lt;/code&gt; value from a list.&lt;/p&gt;

&lt;p&gt;The general form is:&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="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expression2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;expressionN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQL evaluates the expressions from left to right.&lt;/p&gt;

&lt;p&gt;It returns the first value that is not &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If all values are &lt;code&gt;NULL&lt;/code&gt;, the result is &lt;code&gt;NULL&lt;/code&gt; unless you provide a final fallback value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;EmployeeID&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;WorkEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;PersonalEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'No Email Provided'&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;PreferredEmail&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Employees&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 says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;WorkEmail&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;WorkEmail&lt;/code&gt; is missing, use &lt;code&gt;PersonalEmail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If both are missing, use &lt;code&gt;'No Email Provided'&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;EmployeeID&lt;/th&gt;
&lt;th&gt;PreferredEmail&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;&lt;a href="mailto:alice@company.com"&gt;alice@company.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:bob@gmail.com"&gt;bob@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;No Email Provided&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result is easier to read because missing values no longer break the output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NULL&lt;/code&gt; is meaningful.&lt;/p&gt;

&lt;p&gt;It often means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This value is unknown, unavailable, or not applicable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But reports and applications often need something usable to display.&lt;/p&gt;

&lt;p&gt;The Fallback Pattern helps you create cleaner output without changing the underlying data.&lt;/p&gt;

&lt;p&gt;It is especially useful when data quality varies across sources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Uses
&lt;/h2&gt;

&lt;p&gt;The Fallback Pattern appears in many everyday queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preferred contact value
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WorkEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PersonalEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'No Email Provided'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Display name fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Address fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ShippingAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BillingAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Numeric fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DiscountAmount&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;Each example follows the same idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the best available value.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Note on Data Types
&lt;/h2&gt;

&lt;p&gt;Most databases require the expressions inside &lt;code&gt;COALESCE()&lt;/code&gt; to be compatible.&lt;/p&gt;

&lt;p&gt;For example, this is usually fine:&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="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WorkEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PersonalEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'No Email Provided'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because all values are text-like.&lt;/p&gt;

&lt;p&gt;But mixing unrelated data types may cause errors or implicit conversions.&lt;/p&gt;

&lt;p&gt;As always, check the behavior in your database system.&lt;/p&gt;




&lt;h2&gt;
  
  
  COALESCE vs ISNULL / IFNULL / NVL
&lt;/h2&gt;

&lt;p&gt;Different database systems may offer vendor-specific functions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISNULL()&lt;/code&gt; in SQL Server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IFNULL()&lt;/code&gt; in MySQL and SQLite&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NVL()&lt;/code&gt; in Oracle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;COALESCE()&lt;/code&gt; is widely supported and works across many SQL systems.&lt;/p&gt;

&lt;p&gt;The exact behavior can vary in small ways, especially around data types, but the pattern is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return the first available value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unlike COALESCE(), these vendor-specific alternatives typically accept only two arguments, so they cannot express a multi-step fallback chain in a single call.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Fallback Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a display value should not be blank&lt;/li&gt;
&lt;li&gt;multiple columns can provide the same kind of value&lt;/li&gt;
&lt;li&gt;reports need readable defaults&lt;/li&gt;
&lt;li&gt;missing data should not break the result&lt;/li&gt;
&lt;li&gt;a query needs a safe substitute for &lt;code&gt;NULL&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preferred email addresses&lt;/li&gt;
&lt;li&gt;customer display names&lt;/li&gt;
&lt;li&gt;default labels&lt;/li&gt;
&lt;li&gt;optional discounts&lt;/li&gt;
&lt;li&gt;missing category names&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Missing data does not always have to break the result.&lt;/p&gt;

&lt;p&gt;The Fallback Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should I use if this value is missing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;COALESCE()&lt;/code&gt; gives SQL a clear fallback chain.&lt;/p&gt;

&lt;p&gt;Use the first available value.&lt;/p&gt;

&lt;p&gt;Keep the result usable.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #11: The Merge Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #11: The Merge Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 04 Jul 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-11-the-merge-pattern-46aj</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-11-the-merge-pattern-46aj</guid>
      <description>&lt;p&gt;&lt;em&gt;Combining result sets without accidentally paying the deduplication tax&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #11 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The difference between &lt;code&gt;UNION&lt;/code&gt; and &lt;code&gt;UNION ALL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;UNION&lt;/code&gt; removes duplicates&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;UNION ALL&lt;/code&gt; is often faster&lt;/li&gt;
&lt;li&gt;When to keep duplicates and when to remove them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes you need to combine rows from multiple queries.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Show active customers and archived customers together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Combine current orders with historical orders.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where the Merge Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine two result sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set A
-----
1
2
3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set B
-----
3
4
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want one combined result.&lt;/p&gt;

&lt;p&gt;But there is an important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should duplicate rows be removed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question determines whether you should use &lt;code&gt;UNION&lt;/code&gt; or &lt;code&gt;UNION ALL&lt;/code&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2F6x0nlq86bogx7nasorhk.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.amazonaws.com%2Fuploads%2Farticles%2F6x0nlq86bogx7nasorhk.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Merge Pattern
&lt;/h2&gt;

&lt;p&gt;The Merge Pattern combines multiple result sets into one result.&lt;/p&gt;

&lt;p&gt;The two most common tools are:&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;UNION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&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;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They look similar.&lt;/p&gt;

&lt;p&gt;But they do different work.&lt;/p&gt;




&lt;h2&gt;
  
  
  UNION Removes Duplicates
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UNION&lt;/code&gt; combines result sets and removes duplicate rows.&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;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SetA&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SetB&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value
-----
1
2
3
4
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The duplicate &lt;code&gt;3&lt;/code&gt; appears only once.&lt;/p&gt;

&lt;p&gt;That can be exactly what you want.&lt;/p&gt;

&lt;p&gt;But removing duplicates requires extra work.&lt;/p&gt;




&lt;h2&gt;
  
  
  UNION ALL Keeps Everything
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UNION ALL&lt;/code&gt; combines result sets and keeps all rows.&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;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SetA&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SetB&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value
-----
1
2
3
3
4
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The duplicate &lt;code&gt;3&lt;/code&gt; remains.&lt;/p&gt;

&lt;p&gt;This is often faster because the database does not need to deduplicate the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Many developers use &lt;code&gt;UNION&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;But that default can hide two problems.&lt;/p&gt;

&lt;p&gt;First, it may remove rows you actually needed.&lt;/p&gt;

&lt;p&gt;Second, it may require unnecessary sorting or deduplication work.&lt;/p&gt;

&lt;p&gt;If duplicates are valid, &lt;code&gt;UNION ALL&lt;/code&gt; is often the better choice.&lt;/p&gt;

&lt;p&gt;The key question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I need a distinct combined result?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I need to preserve every row?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Suppose you have current and archived orders:&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;OrderID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CurrentOrders&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;OrderID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ArchivedOrders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the tables represent separate time ranges, duplicates may not be a concern.&lt;/p&gt;

&lt;p&gt;In that case, &lt;code&gt;UNION ALL&lt;/code&gt; keeps all rows and avoids unnecessary deduplication.&lt;/p&gt;

&lt;p&gt;But if the two sources may overlap, and duplicate rows should be removed, then &lt;code&gt;UNION&lt;/code&gt; may be appropriate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Column Rules
&lt;/h2&gt;

&lt;p&gt;Both sides of a &lt;code&gt;UNION&lt;/code&gt; or &lt;code&gt;UNION ALL&lt;/code&gt; must return compatible columns.&lt;/p&gt;

&lt;p&gt;For example:&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;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ActiveCustomers&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ArchivedCustomers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number of columns should match.&lt;/p&gt;

&lt;p&gt;The column positions should represent the same meaning.&lt;/p&gt;

&lt;p&gt;The data types should be compatible.&lt;/p&gt;

&lt;p&gt;SQL combines by column position, not by column name.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Performance
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UNION&lt;/code&gt; often requires extra work because the database must identify duplicate rows.&lt;/p&gt;

&lt;p&gt;That may involve sorting, hashing, or other internal operations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UNION ALL&lt;/code&gt; avoids that deduplication step.&lt;/p&gt;

&lt;p&gt;So when duplicates are acceptable or impossible, &lt;code&gt;UNION ALL&lt;/code&gt; is usually the better default.&lt;/p&gt;

&lt;p&gt;As always, validate performance with real execution plans and real workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Merge Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;combining current and historical records&lt;/li&gt;
&lt;li&gt;merging active and archived data&lt;/li&gt;
&lt;li&gt;stacking results from similar queries&lt;/li&gt;
&lt;li&gt;building reporting datasets&lt;/li&gt;
&lt;li&gt;combining data from multiple sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I reach for &lt;code&gt;UNION&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicates must be removed&lt;/li&gt;
&lt;li&gt;the final result should be distinct&lt;/li&gt;
&lt;li&gt;overlapping sources are possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I reach for &lt;code&gt;UNION ALL&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicates are valid&lt;/li&gt;
&lt;li&gt;all rows should be preserved&lt;/li&gt;
&lt;li&gt;the sources do not overlap&lt;/li&gt;
&lt;li&gt;performance matters and deduplication is unnecessary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UNION&lt;/code&gt; and &lt;code&gt;UNION ALL&lt;/code&gt; both merge result sets.&lt;/p&gt;

&lt;p&gt;But they do not mean the same thing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UNION&lt;/code&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Combine the rows and remove duplicates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;UNION ALL&lt;/code&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Combine the rows and keep everything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do not pay the deduplication tax unless you need to.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #10: The Hierarchy Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL Pattern Series #10: The Hierarchy Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 30 Jun 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-10-the-hierarchy-pattern-ofm</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-10-the-hierarchy-pattern-ofm</guid>
      <description>&lt;p&gt;&lt;em&gt;Relating rows in a table to other rows in the same table&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #10 of 21&lt;/p&gt;

&lt;p&gt;A collection of practical SQL patterns that help developers recognize common solutions to recurring database problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;In this article you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What a self-join is&lt;/li&gt;
&lt;li&gt;How one table can represent hierarchical relationships&lt;/li&gt;
&lt;li&gt;How to connect employees to managers&lt;/li&gt;
&lt;li&gt;When to use the Hierarchy Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most joins connect one table to another table.&lt;/p&gt;

&lt;p&gt;For example:&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="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But sometimes the relationship exists inside the same table.&lt;/p&gt;

&lt;p&gt;That is where the Hierarchy Pattern becomes useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine an &lt;code&gt;Employees&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;EmployeeID&lt;/th&gt;
&lt;th&gt;EmployeeName&lt;/th&gt;
&lt;th&gt;ManagerID&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;Alice&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&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;Carla&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Diego&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;The employee and the manager are both stored in the same table.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bob&lt;/code&gt; is an employee.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Alice&lt;/code&gt; is also an employee.&lt;/p&gt;

&lt;p&gt;But Alice is Bob's manager.&lt;/p&gt;

&lt;p&gt;So the table needs to relate one row to another row within itself.&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.amazonaws.com%2Fuploads%2Farticles%2Fogvx22s4v4gn3ox5nchh.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.amazonaws.com%2Fuploads%2Farticles%2Fogvx22s4v4gn3ox5nchh.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hierarchy Pattern
&lt;/h2&gt;

&lt;p&gt;The Hierarchy Pattern uses a table more than once in the same query.&lt;/p&gt;

&lt;p&gt;This is usually called a self-join.&lt;/p&gt;

&lt;p&gt;The key idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat the same table as if it were two different roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&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;FROM&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;e&lt;/code&gt; represents the employee&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m&lt;/code&gt; represents the manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both aliases point to the same table.&lt;/p&gt;

&lt;p&gt;But each alias plays a different role in the query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeName&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ManagerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&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 returns each employee with their manager's name.&lt;/p&gt;

&lt;p&gt;The join connects:&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="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagerID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually, SQL asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which employee row points to another employee row as its manager?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;EmployeeID&lt;/th&gt;
&lt;th&gt;EmployeeName&lt;/th&gt;
&lt;th&gt;ManagerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Carla&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Diego&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that Alice does not appear in this result.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because Alice has no manager.&lt;/p&gt;

&lt;p&gt;Her &lt;code&gt;ManagerID&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt;, so the inner join does not find a matching manager row.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keeping Top-Level Rows
&lt;/h2&gt;

&lt;p&gt;If you want to include employees who do not have managers, use a &lt;code&gt;LEFT JOIN&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;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeName&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ManagerName&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagerID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps every employee.&lt;/p&gt;

&lt;p&gt;Employees without managers return &lt;code&gt;NULL&lt;/code&gt; for &lt;code&gt;ManagerName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is useful for top-level roles such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CEO&lt;/li&gt;
&lt;li&gt;founder&lt;/li&gt;
&lt;li&gt;department head&lt;/li&gt;
&lt;li&gt;root category&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;Hierarchical relationships appear all over real systems.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;employees and managers&lt;/li&gt;
&lt;li&gt;categories and parent categories&lt;/li&gt;
&lt;li&gt;folders and parent folders&lt;/li&gt;
&lt;li&gt;comments and replies&lt;/li&gt;
&lt;li&gt;accounts and parent accounts&lt;/li&gt;
&lt;li&gt;organization charts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever one row points to another row in the same table, the Hierarchy Pattern may apply.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Recursion
&lt;/h2&gt;

&lt;p&gt;A self-join is useful for one level of hierarchy.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;employee → manager&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But if you need multiple levels:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;employee → manager → director → vice president&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then a single self-join may not be enough.&lt;/p&gt;

&lt;p&gt;For deeper hierarchies, many databases support recursive queries, often using recursive CTEs.&lt;/p&gt;

&lt;p&gt;The exact syntax varies by database system.&lt;/p&gt;

&lt;p&gt;But the underlying idea is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A row can relate to another row in the same structure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When I Reach for This Pattern
&lt;/h2&gt;

&lt;p&gt;I typically use the Hierarchy Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a table references itself&lt;/li&gt;
&lt;li&gt;one row has a parent row&lt;/li&gt;
&lt;li&gt;I need to display a parent name&lt;/li&gt;
&lt;li&gt;I need to build a basic hierarchy&lt;/li&gt;
&lt;li&gt;I need to connect a child row to its immediate parent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;employee-manager reports&lt;/li&gt;
&lt;li&gt;category trees&lt;/li&gt;
&lt;li&gt;folder structures&lt;/li&gt;
&lt;li&gt;comment threads&lt;/li&gt;
&lt;li&gt;account relationships&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;Not every relationship crosses tables.&lt;/p&gt;

&lt;p&gt;Sometimes the relationship lives inside one table.&lt;/p&gt;

&lt;p&gt;The Hierarchy Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does this row relate to another row in the same table?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A self-join lets one table play multiple roles in the same query.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Pattern Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;SQL Pattern Series&lt;/strong&gt;, a collection of practical SQL patterns that help developers recognize common problem-solving approaches found in reporting, analytics, and application development.&lt;/p&gt;

&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #2: The Match Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #3: The Missing Data Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #4: The Moving Sum Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #5: The Deduplication Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #6: The Routing Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #7: The Running Total Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #8: The Query Order Pattern&lt;/li&gt;
&lt;li&gt;SQL Pattern Series #9: The Period-over-Period Pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQL Bubble Pop
&lt;/h2&gt;

&lt;p&gt;If you are learning SQL or helping others learn SQL, I created &lt;strong&gt;&lt;a href="https://apps.apple.com/us/app/sql-bubble-pop-sql-coding-game/id6744767120" rel="noopener noreferrer"&gt;SQL Bubble Pop&lt;/a&gt;&lt;/strong&gt;, a mobile game that teaches SQL concepts through quick, interactive challenges and pattern recognition exercises.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn SQL by recognizing patterns instead of memorizing syntax.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
