<?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 #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>
    <item>
      <title>SQL Pattern Series #9: The Period-over-Period Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 27 Jun 2026 14:31:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-9-the-period-over-period-pattern-5o4</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-9-the-period-over-period-pattern-5o4</guid>
      <description>&lt;p&gt;&lt;em&gt;Comparing each row to the one that came before it&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #9 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 compare a row to the previous row&lt;/li&gt;
&lt;li&gt;When to use &lt;code&gt;LAG()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How period-over-period analysis works&lt;/li&gt;
&lt;li&gt;Why this pattern is useful for trends and reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many reports answer a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened this month?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But decision-makers often want a different question answered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How did this month compare to last month?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where the Period-over-Period Pattern comes in.&lt;/p&gt;




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

&lt;p&gt;Suppose you have monthly revenue data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Month&lt;/span&gt;      &lt;span class="n"&gt;Revenue&lt;/span&gt;
&lt;span class="c1"&gt;---------- -------&lt;/span&gt;
&lt;span class="n"&gt;Jan&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;    &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;    &lt;span class="mi"&gt;12000&lt;/span&gt;
&lt;span class="n"&gt;Mar&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;    &lt;span class="mi"&gt;11000&lt;/span&gt;
&lt;span class="n"&gt;Apr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;    &lt;span class="mi"&gt;15000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the values individually is useful.&lt;/p&gt;

&lt;p&gt;But it doesn't immediately tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which months increased?&lt;/li&gt;
&lt;li&gt;Which months decreased?&lt;/li&gt;
&lt;li&gt;By how much?&lt;/li&gt;
&lt;li&gt;What trends are emerging?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To answer those questions, each row needs access to the previous row.&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%2Fxk3392k5taspif06ypyt.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%2Fxk3392k5taspif06ypyt.png" alt=" " width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Period-over-Period Pattern
&lt;/h2&gt;

&lt;p&gt;The Period-over-Period Pattern uses window functions to compare each row against a prior period.&lt;/p&gt;

&lt;p&gt;Most commonly:&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;LAG&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;LAG()&lt;/code&gt; retrieves a value from a previous row without requiring a self-join.&lt;/p&gt;

&lt;p&gt;Conceptually, SQL is asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What was the value in the previous period?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example Using LAG
&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;Month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Revenue&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;Month&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;PreviousRevenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;MonthlyRevenue&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;Month      Revenue   PreviousRevenue
---------- --------- ----------------
Jan-2025    10000    NULL
Feb-2025    12000    10000
Mar-2025    11000    12000
Apr-2025    15000    11000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row now has access to the previous period's value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Calculating the Difference
&lt;/h2&gt;

&lt;p&gt;Once the previous value is available, calculating the change becomes simple.&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;Month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Revenue&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;Month&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;PreviousRevenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Revenue&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Revenue&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;Month&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;RevenueChange&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;MonthlyRevenue&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;Month      Revenue   PreviousRevenue   RevenueChange
---------- --------- ---------------- -------------
Jan-2025    10000    NULL              NULL
Feb-2025    12000    10000             2000
Mar-2025    11000    12000            -1000
Apr-2025    15000    11000             4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now increases and decreases become obvious.&lt;/p&gt;




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

&lt;p&gt;Many business questions are really comparison questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue growth&lt;/li&gt;
&lt;li&gt;Subscriber growth&lt;/li&gt;
&lt;li&gt;Daily active users&lt;/li&gt;
&lt;li&gt;Download counts&lt;/li&gt;
&lt;li&gt;Inventory changes&lt;/li&gt;
&lt;li&gt;Website traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at raw numbers alone often hides the trend.&lt;/p&gt;

&lt;p&gt;Comparing each period to the previous one reveals movement.&lt;/p&gt;




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

&lt;p&gt;The ordering column matters.&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;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Revenue&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;Month&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous value depends entirely on the specified order.&lt;/p&gt;

&lt;p&gt;If the ordering is incorrect, the comparison will also be incorrect.&lt;/p&gt;

&lt;p&gt;Always verify that the ordering column reflects the actual sequence you intend to analyze.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond One Period
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LAG()&lt;/code&gt; can also look farther back.&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;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;Month&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves the value from three rows earlier.&lt;/p&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quarter-over-quarter comparisons&lt;/li&gt;
&lt;li&gt;Seasonal analysis&lt;/li&gt;
&lt;li&gt;Historical benchmarking&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 Period-over-Period Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comparing revenue trends&lt;/li&gt;
&lt;li&gt;analyzing subscriber growth&lt;/li&gt;
&lt;li&gt;tracking downloads&lt;/li&gt;
&lt;li&gt;measuring engagement changes&lt;/li&gt;
&lt;li&gt;identifying sudden increases or decreases&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;month-over-month revenue&lt;/li&gt;
&lt;li&gt;week-over-week traffic&lt;/li&gt;
&lt;li&gt;day-over-day activity&lt;/li&gt;
&lt;li&gt;year-over-year comparisons&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Many reports become more useful when you stop asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and start asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What changed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Period-over-Period Pattern helps answer that question by giving each row access to the one that came before it.&lt;/p&gt;

&lt;p&gt;Sometimes the trend is more important than the value 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;/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 #8: The Query Order Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 23 Jun 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-8-the-query-order-pattern-2mem</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-8-the-query-order-pattern-2mem</guid>
      <description>&lt;p&gt;&lt;em&gt;Understanding the order SQL actually processes a query&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #8 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;Why SQL does not run strictly from top to bottom&lt;/li&gt;
&lt;li&gt;The difference between written order and logical processing order&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;WHERE&lt;/code&gt; filters rows before grouping&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;HAVING&lt;/code&gt; filters groups after aggregation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SQL queries are usually written 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="n"&gt;CustomerID&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;OrderCount&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;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-01'&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="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;5&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;OrderCount&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;That looks like the order SQL runs in.&lt;/p&gt;

&lt;p&gt;But logically, SQL processes the query in a different sequence.&lt;/p&gt;

&lt;p&gt;That difference explains a lot of beginner confusion.&lt;/p&gt;




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

&lt;p&gt;Many SQL developers first read queries from top to bottom.&lt;/p&gt;

&lt;p&gt;That makes sense visually.&lt;/p&gt;

&lt;p&gt;The query starts 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;SELECT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So it feels like &lt;code&gt;SELECT&lt;/code&gt; must happen first.&lt;/p&gt;

&lt;p&gt;But SQL is declarative.&lt;/p&gt;

&lt;p&gt;You describe the result you want.&lt;/p&gt;

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

&lt;p&gt;Logical processing order helps you understand why certain clauses behave the way they do.&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%2Fv2rxhh48ri2m8emcr5l8.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%2Fv2rxhh48ri2m8emcr5l8.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The Query Order Pattern is the habit of thinking about SQL in logical stages.&lt;/p&gt;

&lt;p&gt;A simplified logical order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WHERE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not necessarily the physical execution plan.&lt;/p&gt;

&lt;p&gt;The optimizer may rearrange operations internally.&lt;/p&gt;

&lt;p&gt;But this logical order explains how the query is interpreted.&lt;/p&gt;




&lt;h2&gt;
  
  
  FROM: Choose the Data Source
&lt;/h2&gt;

&lt;p&gt;SQL first determines where the data comes from.&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;Orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This establishes the table or joined result set that the rest of the query will operate on.&lt;/p&gt;

&lt;p&gt;Before rows can be filtered, grouped, or selected, SQL needs a source.&lt;/p&gt;




&lt;h2&gt;
  
  
  WHERE: Filter Rows
&lt;/h2&gt;

&lt;p&gt;Next, &lt;code&gt;WHERE&lt;/code&gt; filters individual 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;WHERE&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens before grouping.&lt;/p&gt;

&lt;p&gt;That means &lt;code&gt;WHERE&lt;/code&gt; cannot filter aggregate values like:&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because those counts do not exist yet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WHERE&lt;/code&gt; works row by row.&lt;/p&gt;




&lt;h2&gt;
  
  
  GROUP BY: Create Groups
&lt;/h2&gt;

&lt;p&gt;After row filtering, SQL groups the remaining 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;GROUP&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;Rows with the same &lt;code&gt;CustomerID&lt;/code&gt; are collected into groups.&lt;/p&gt;

&lt;p&gt;Once groups exist, aggregate 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="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;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;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be calculated.&lt;/p&gt;




&lt;h2&gt;
  
  
  HAVING: Filter Groups
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HAVING&lt;/code&gt; filters groups after aggregation.&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;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;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why &lt;code&gt;HAVING&lt;/code&gt; can use aggregate expressions.&lt;/p&gt;

&lt;p&gt;At this point, SQL has already grouped the rows and calculated the count for each group.&lt;/p&gt;

&lt;p&gt;A useful shortcut:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;WHERE&lt;/code&gt; filters rows.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HAVING&lt;/code&gt; filters groups.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  SELECT: Choose the Output
&lt;/h2&gt;

&lt;p&gt;After filtering and grouping, SQL determines which expressions appear in the result.&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="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;OrderCount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where output columns and aliases are produced.&lt;/p&gt;

&lt;p&gt;That is why aliases created in &lt;code&gt;SELECT&lt;/code&gt; are not always available in earlier clauses such as &lt;code&gt;WHERE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The alias may not exist yet in the logical processing order.&lt;/p&gt;




&lt;h2&gt;
  
  
  ORDER BY: Sort the Result
&lt;/h2&gt;

&lt;p&gt;Finally, &lt;code&gt;ORDER BY&lt;/code&gt; sorts the result set.&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;OrderCount&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;Unlike &lt;code&gt;WHERE&lt;/code&gt;, many database systems allow &lt;code&gt;ORDER BY&lt;/code&gt; to reference aliases created in &lt;code&gt;SELECT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By this point, the output result has been formed, so the alias is available for sorting.&lt;/p&gt;




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

&lt;p&gt;Understanding query order helps explain many common SQL surprises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;code&gt;WHERE COUNT(*) &amp;gt; 5&lt;/code&gt; does not work&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;HAVING COUNT(*) &amp;gt; 5&lt;/code&gt; does work&lt;/li&gt;
&lt;li&gt;Why some aliases are not available in &lt;code&gt;WHERE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Why filters before grouping affect the final aggregates&lt;/li&gt;
&lt;li&gt;Why SQL feels different from procedural programming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this pattern clicks, many SQL errors become easier to diagnose.&lt;/p&gt;




&lt;h2&gt;
  
  
  Written Order vs Logical Order
&lt;/h2&gt;

&lt;p&gt;SQL is written in one order:&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;FROM&lt;/span&gt;
&lt;span class="k"&gt;WHERE&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;HAVING&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it is logically interpreted in another:&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="k"&gt;WHERE&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;HAVING&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not mean the database literally executes every query in that exact sequence.&lt;/p&gt;

&lt;p&gt;Modern optimizers are far more sophisticated than that.&lt;/p&gt;

&lt;p&gt;But the logical order is still useful because it explains the meaning of the query.&lt;/p&gt;




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

&lt;p&gt;I think about the Query Order Pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a query fails because an alias is not recognized&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt; become confusing&lt;/li&gt;
&lt;li&gt;aggregate filters are involved&lt;/li&gt;
&lt;li&gt;reports return unexpected totals&lt;/li&gt;
&lt;li&gt;learners try to read SQL from top to bottom&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;filtering grouped reports&lt;/li&gt;
&lt;li&gt;calculating counts by customer&lt;/li&gt;
&lt;li&gt;filtering sales totals&lt;/li&gt;
&lt;li&gt;debugging aggregate queries&lt;/li&gt;
&lt;li&gt;explaining why SQL clauses behave differently&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;SQL is written in one order, but understood in stages.&lt;/p&gt;

&lt;p&gt;The most useful shortcut is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;WHERE&lt;/code&gt; filters rows.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HAVING&lt;/code&gt; filters groups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you understand that sequence, many SQL queries become easier to read, debug, and explain.&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;/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 #7: The Running Total Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 20 Jun 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-7-the-running-total-pattern-5af9</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-7-the-running-total-pattern-5af9</guid>
      <description>&lt;p&gt;&lt;em&gt;Seeing how values build over time&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #7 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 running total is&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;SUM()&lt;/code&gt; works as a window function&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;ORDER BY&lt;/code&gt; matters inside &lt;code&gt;OVER()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When to use running totals in reports and dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most reports show totals.&lt;/p&gt;

&lt;p&gt;But sometimes a single total is not enough.&lt;/p&gt;

&lt;p&gt;You may also need to see how that total builds over time.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;What were cumulative sales by day?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;How did the balance grow after each transaction?&lt;/p&gt;
&lt;/blockquote&gt;

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




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

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;OrderDate&lt;/th&gt;
&lt;th&gt;DailyTotal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A regular total can tell you the final amount.&lt;/p&gt;

&lt;p&gt;But it does not show the path.&lt;/p&gt;

&lt;p&gt;A running total shows how the value accumulates row by row.&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%2Fh0syd3fcuxqvx1rxougs.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%2Fh0syd3fcuxqvx1rxougs.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The Running Total Pattern uses a window function to calculate a cumulative total.&lt;/p&gt;

&lt;p&gt;The common structure 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;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;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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SUM()&lt;/code&gt; calculates the total.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OVER()&lt;/code&gt; clause defines the window.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; restarts the calculation for each group.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ORDER BY&lt;/code&gt; controls the sequence in which values accumulate.&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;Region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OrderDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DailyTotal&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;DailyTotal&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="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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;RunningTotal&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DailySales&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 a running total for each region.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;West&lt;/code&gt; region, the running total builds across West rows.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;East&lt;/code&gt; region, the running total starts over and builds across East 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;Region&lt;/th&gt;
&lt;th&gt;OrderDate&lt;/th&gt;
&lt;th&gt;DailyTotal&lt;/th&gt;
&lt;th&gt;RunningTotal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;325&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The running total shows the cumulative value after each row.&lt;/p&gt;

&lt;p&gt;Instead of only seeing the final total, you can see how the total grows.&lt;/p&gt;




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

&lt;p&gt;Running totals are useful because they show progression.&lt;/p&gt;

&lt;p&gt;They help answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much has accumulated so far?&lt;/li&gt;
&lt;li&gt;When did the total pass a threshold?&lt;/li&gt;
&lt;li&gt;How quickly is value building?&lt;/li&gt;
&lt;li&gt;How does one group compare to another over time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the pattern useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sales reporting&lt;/li&gt;
&lt;li&gt;finance reports&lt;/li&gt;
&lt;li&gt;inventory tracking&lt;/li&gt;
&lt;li&gt;account balances&lt;/li&gt;
&lt;li&gt;progress dashboards&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  PARTITION BY Controls the Reset
&lt;/h2&gt;

&lt;p&gt;One important part of 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;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;Region&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells SQL:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Calculate a separate running total for each region.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without &lt;code&gt;PARTITION BY&lt;/code&gt;, SQL calculates one running total across the entire result set.&lt;/p&gt;

&lt;p&gt;That may be correct in some cases.&lt;/p&gt;

&lt;p&gt;But if you need one running total per customer, region, account, product, or category, &lt;code&gt;PARTITION BY&lt;/code&gt; defines where the total resets.&lt;/p&gt;




&lt;h2&gt;
  
  
  ORDER BY Controls the Story
&lt;/h2&gt;

&lt;p&gt;The other important part 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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A running total depends on sequence.&lt;/p&gt;

&lt;p&gt;If the rows are ordered differently, the running total changes.&lt;/p&gt;

&lt;p&gt;That means the &lt;code&gt;ORDER BY&lt;/code&gt; inside the window function is not just formatting.&lt;/p&gt;

&lt;p&gt;It defines the logic of the calculation.&lt;/p&gt;




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

&lt;p&gt;If two rows have the same &lt;code&gt;OrderDate&lt;/code&gt;, the database may not have a predictable order between them.&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may not be enough if multiple rows share the same date.&lt;/p&gt;

&lt;p&gt;If the result must be deterministic, add a tiebreaker:&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;DailyTotal&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="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="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;SaleID&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;RunningTotal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now SQL has a stable order when two rows have the same date.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running Total vs Moving Sum
&lt;/h2&gt;

&lt;p&gt;The Running Total Pattern is closely related to the Moving Sum Pattern.&lt;/p&gt;

&lt;p&gt;But they answer different questions.&lt;/p&gt;

&lt;p&gt;A running total asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much has accumulated so far?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A moving sum asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the total over the most recent window?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Running totals keep building.&lt;/p&gt;

&lt;p&gt;Moving sums slide forward.&lt;/p&gt;

&lt;p&gt;Both are useful, but they tell different stories.&lt;/p&gt;




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

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

&lt;ul&gt;
&lt;li&gt;values accumulate over time&lt;/li&gt;
&lt;li&gt;I need cumulative totals&lt;/li&gt;
&lt;li&gt;each row should show progress so far&lt;/li&gt;
&lt;li&gt;totals need to restart by group&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;cumulative sales by region&lt;/li&gt;
&lt;li&gt;running account balances&lt;/li&gt;
&lt;li&gt;inventory movement over time&lt;/li&gt;
&lt;li&gt;cumulative signups&lt;/li&gt;
&lt;li&gt;progress toward a goal&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Regular totals show the final number.&lt;/p&gt;

&lt;p&gt;Running totals show how the number builds.&lt;/p&gt;

&lt;p&gt;The Running Total Pattern helps answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much has accumulated so far?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes it one of the most useful patterns for reports, dashboards, and trend analysis.&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;/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>tutorial</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
