<?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 #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>
    <item>
      <title>SQL Pattern Series #6: The Routing Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 16 Jun 2026 14:37:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-6-the-routing-pattern-3nl9</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-6-the-routing-pattern-3nl9</guid>
      <description>&lt;p&gt;&lt;em&gt;Sometimes SQL decides what happens next&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #6 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 SQL can make decisions&lt;/li&gt;
&lt;li&gt;When IF and ELSE become useful&lt;/li&gt;
&lt;li&gt;Why stored procedures often contain routing logic&lt;/li&gt;
&lt;li&gt;How to think about SQL as a decision engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many developers first learn SQL as a query language.&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

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

&lt;p&gt;But eventually you discover something interesting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL can also decide what happens next.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Imagine an application sends a request to a stored procedure.&lt;/p&gt;

&lt;p&gt;The action could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retrieve users&lt;/li&gt;
&lt;li&gt;add a user&lt;/li&gt;
&lt;li&gt;delete a user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database must determine which action to perform.&lt;/p&gt;

&lt;p&gt;That means the query is no longer just retrieving data.&lt;/p&gt;

&lt;p&gt;It is making a decision.&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%2Fgkgoe7d8s5dimckvomys.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%2Fgkgoe7d8s5dimckvomys.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The Routing Pattern uses conditional logic to direct execution down different paths.&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;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'GET'&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;get_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ADD'&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;add_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ELSE&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;delete_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The example shown uses SQL Server (T-SQL) syntax. Conditional and procedural syntax varies between database systems, but the underlying pattern—routing execution based on a condition—exists in most platforms.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The database evaluates a condition.&lt;/p&gt;

&lt;p&gt;Based on the result, execution follows a specific route.&lt;/p&gt;

&lt;p&gt;Just like a road sign directs traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thinking About Routing
&lt;/h2&gt;

&lt;p&gt;Many SQL developers think primarily in terms of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tables&lt;/li&gt;
&lt;li&gt;rows&lt;/li&gt;
&lt;li&gt;queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Routing Pattern introduces another perspective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;decisions&lt;/li&gt;
&lt;li&gt;actions&lt;/li&gt;
&lt;li&gt;execution paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question changes from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What data should I retrieve?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen next?&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Imagine a user management system.&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;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'GET'&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;get_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieve existing users.&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;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ADD'&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;add_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new user.&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;ELSE&lt;/span&gt;
    &lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;delete_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove a user.&lt;/p&gt;

&lt;p&gt;A single procedure can direct execution to multiple outcomes.&lt;/p&gt;




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

&lt;p&gt;Routing logic appears frequently in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stored procedures&lt;/li&gt;
&lt;li&gt;ETL processes&lt;/li&gt;
&lt;li&gt;administrative scripts&lt;/li&gt;
&lt;li&gt;maintenance jobs&lt;/li&gt;
&lt;li&gt;workflow automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database often becomes responsible for coordinating actions rather than simply returning data.&lt;/p&gt;




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

&lt;p&gt;Routing logic can be extremely useful.&lt;/p&gt;

&lt;p&gt;But it can also become difficult to maintain if too many branches are added.&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;IF&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Large decision trees can become hard to follow.&lt;/p&gt;

&lt;p&gt;As routing logic grows, consider whether responsibilities should be split into separate procedures or services.&lt;/p&gt;




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

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

&lt;ul&gt;
&lt;li&gt;multiple actions share a common entry point&lt;/li&gt;
&lt;li&gt;a stored procedure must support different behaviors&lt;/li&gt;
&lt;li&gt;execution depends on user input&lt;/li&gt;
&lt;li&gt;workflow decisions belong close to the data&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;user management procedures&lt;/li&gt;
&lt;li&gt;import pipelines&lt;/li&gt;
&lt;li&gt;administrative tooling&lt;/li&gt;
&lt;li&gt;automation workflows&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;SQL is not limited to retrieving data.&lt;/p&gt;

&lt;p&gt;Sometimes it acts as a decision engine.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Which action should happen next?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;rather than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which rows should be returned?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift opens up a different way of thinking about database programming.&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;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>beginners</category>
    </item>
    <item>
      <title>SQL Pattern Series #5: The Deduplication Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 13 Jun 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-5-the-deduplication-pattern-125g</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-5-the-deduplication-pattern-125g</guid>
      <description>&lt;p&gt;&lt;em&gt;Keeping the row you want and removing the rest&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #5 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 records&lt;/li&gt;
&lt;li&gt;Why duplicates are often unavoidable in real systems&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;ROW_NUMBER()&lt;/code&gt; helps isolate the record you want to keep&lt;/li&gt;
&lt;li&gt;A common pattern for deduplicating data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most developers eventually encounter a table that contains duplicate records.&lt;/p&gt;

&lt;p&gt;Sometimes the duplicates are accidental.&lt;/p&gt;

&lt;p&gt;Sometimes they are the result of imports, integrations, retries, or application bugs.&lt;/p&gt;

&lt;p&gt;The challenge is usually not finding the duplicates.&lt;/p&gt;

&lt;p&gt;The challenge is deciding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which row should survive?&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Imagine a Users table:&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;th&gt;CreatedDate&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;td&gt;2024-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:alice@example.com"&gt;alice@example.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-03-15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:bob@example.com"&gt;bob@example.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The email address appears more than once.&lt;/p&gt;

&lt;p&gt;If only one row should remain, which one do we keep?&lt;/p&gt;

&lt;p&gt;The oldest?&lt;/p&gt;

&lt;p&gt;The newest?&lt;/p&gt;

&lt;p&gt;The one with the highest ID?&lt;/p&gt;

&lt;p&gt;The answer depends on the business rule.&lt;/p&gt;

&lt;p&gt;Once that rule is defined, the Deduplication Pattern becomes useful.&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%2Fbvaqsmheu1ett65mr2kx.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%2Fbvaqsmheu1ett65mr2kx.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The most 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;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;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;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to assign a sequence number to each duplicate group.&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;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;Email&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;CreatedDate&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 creates groups based on Email.&lt;/p&gt;

&lt;p&gt;Within each group, rows are numbered.&lt;/p&gt;

&lt;p&gt;The newest record 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;The next 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;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and so on.&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="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;Email&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;CreatedDate&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;Users&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 returns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most recent record for each email address.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All other duplicates are excluded.&lt;/p&gt;




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

&lt;p&gt;Deduplication is one of the most common data-cleaning tasks.&lt;/p&gt;

&lt;p&gt;You'll encounter it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;importing data&lt;/li&gt;
&lt;li&gt;merging systems&lt;/li&gt;
&lt;li&gt;cleaning customer records&lt;/li&gt;
&lt;li&gt;preparing analytics datasets&lt;/li&gt;
&lt;li&gt;fixing application issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern allows you to express exactly which record should survive.&lt;/p&gt;

&lt;p&gt;That makes the logic clear and repeatable.&lt;/p&gt;




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

&lt;p&gt;Before window functions became widely available, deduplication often required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;self joins&lt;/li&gt;
&lt;li&gt;nested subqueries&lt;/li&gt;
&lt;li&gt;temporary tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Window functions simplified the process dramatically.&lt;/p&gt;

&lt;p&gt;By combining:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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="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;you can isolate duplicates in a single query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the Winning Row
&lt;/h2&gt;

&lt;p&gt;The most important part of the pattern is often:&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;/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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;CreatedDate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;keeps the newest row.&lt;/p&gt;

&lt;p&gt;But you could also choose:&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;CreatedDate&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to keep the oldest.&lt;/p&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;UserID&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to keep the highest ID.&lt;/p&gt;

&lt;p&gt;The ordering defines the business rule.&lt;/p&gt;




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

&lt;p&gt;One subtle detail:&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;CreatedDate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works well when every &lt;code&gt;CreatedDate&lt;/code&gt; value is unique.&lt;/p&gt;

&lt;p&gt;But what happens if two rows share the same timestamp?&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;Email&lt;/span&gt;                &lt;span class="n"&gt;CreatedDate&lt;/span&gt;
&lt;span class="c1"&gt;-------------------  -------------------&lt;/span&gt;
&lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;    &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&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;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;    &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&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;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case, &lt;code&gt;ROW_NUMBER()&lt;/code&gt; will still assign:&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;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the database may choose either row as the winner.&lt;/p&gt;

&lt;p&gt;If the result must be predictable, 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="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;Email&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;CreatedDate&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;UserID&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;Now the ordering is deterministic.&lt;/p&gt;

&lt;p&gt;When multiple rows share the same timestamp, the row with the highest &lt;code&gt;UserID&lt;/code&gt; wins consistently.&lt;/p&gt;




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

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

&lt;ul&gt;
&lt;li&gt;duplicate records exist&lt;/li&gt;
&lt;li&gt;I need one row per business entity&lt;/li&gt;
&lt;li&gt;data quality issues must be cleaned&lt;/li&gt;
&lt;li&gt;multiple systems have been merged&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;duplicate customer records&lt;/li&gt;
&lt;li&gt;duplicate email addresses&lt;/li&gt;
&lt;li&gt;repeated imports&lt;/li&gt;
&lt;li&gt;overlapping transaction data&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Finding duplicates is only half the problem.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Which row should survive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Deduplication Pattern provides a repeatable way to answer that question.&lt;/p&gt;

&lt;p&gt;By combining:&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;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;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can identify the winning row and remove the rest.&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;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 #4: The Moving Sum Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-4-the-moving-sum-pattern-3ca2</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-4-the-moving-sum-pattern-3ca2</guid>
      <description>&lt;p&gt;&lt;em&gt;Seeing what is happening over the last N rows&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #4 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 moving sum is&lt;/li&gt;
&lt;li&gt;How window functions make moving calculations possible&lt;/li&gt;
&lt;li&gt;Why moving sums are useful for trend analysis&lt;/li&gt;
&lt;li&gt;When to use ROWS BETWEEN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most reports start with simple totals.&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SalesAmount&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;But eventually a different question appears:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What has happened over the last 30 days?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;What is the rolling total over the last 30 transactions?&lt;/p&gt;
&lt;/blockquote&gt;

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




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

&lt;p&gt;Looking at individual rows often hides the bigger picture.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&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;Jan 1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 2&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 3&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 4&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Individual values move up and down.&lt;/p&gt;

&lt;p&gt;Sometimes what matters is the cumulative behavior over a recent window.&lt;/p&gt;

&lt;p&gt;Questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What were sales during the last 30 days?&lt;/li&gt;
&lt;li&gt;How many errors occurred during the last 100 events?&lt;/li&gt;
&lt;li&gt;What is the rolling total of customer purchases?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;require more context than a single row provides.&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%2Fa5dmcld2zlz1dbchpbl3.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%2Fa5dmcld2zlz1dbchpbl3.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;A moving sum calculates the total of the current row plus a specified number of previous rows.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
    &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually this creates a window.&lt;/p&gt;

&lt;p&gt;As each row is processed, the window moves forward.&lt;/p&gt;

&lt;p&gt;Rows enter the window.&lt;/p&gt;

&lt;p&gt;Older rows leave the window.&lt;/p&gt;

&lt;p&gt;The calculation updates automatically.&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="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
             &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;moving_sum&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 produces a rolling 30-row total.&lt;/p&gt;

&lt;p&gt;Each result includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current row&lt;/li&gt;
&lt;li&gt;the previous 29 rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The window moves forward one row at a time.&lt;/p&gt;




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

&lt;p&gt;Moving sums help reveal trends that individual rows often hide.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

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

&lt;p&gt;you begin asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What has happened recently?&lt;/p&gt;
&lt;/blockquote&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;financial analysis&lt;/li&gt;
&lt;li&gt;monitoring systems&lt;/li&gt;
&lt;li&gt;operational dashboards&lt;/li&gt;
&lt;li&gt;customer activity tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is often smoother and easier to interpret than raw values alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Window Functions Change Everything
&lt;/h2&gt;

&lt;p&gt;Many SQL developers first encounter moving sums when learning window functions.&lt;/p&gt;

&lt;p&gt;Without window functions, moving calculations often require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;self joins&lt;/li&gt;
&lt;li&gt;correlated subqueries&lt;/li&gt;
&lt;li&gt;temporary tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Window functions provide a cleaner solution.&lt;/p&gt;

&lt;p&gt;The Moving Sum Pattern is one of the most common examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Rows vs Dates
&lt;/h2&gt;

&lt;p&gt;One important detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;blockquote&gt;
&lt;p&gt;Previous 29 rows&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Previous 29 days&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different concepts.&lt;/p&gt;

&lt;p&gt;If dates contain gaps, weekends, or missing records, row counts and date ranges may not align.&lt;/p&gt;

&lt;p&gt;Always verify that the window matches the business requirement.&lt;/p&gt;




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

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

&lt;ul&gt;
&lt;li&gt;I need rolling totals&lt;/li&gt;
&lt;li&gt;I want to smooth noisy data&lt;/li&gt;
&lt;li&gt;I need trend visibility&lt;/li&gt;
&lt;li&gt;The analysis depends on recent activity&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;rolling sales totals&lt;/li&gt;
&lt;li&gt;rolling transaction counts&lt;/li&gt;
&lt;li&gt;rolling inventory movement&lt;/li&gt;
&lt;li&gt;rolling customer activity&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Individual rows show events.&lt;/p&gt;

&lt;p&gt;Moving sums show context.&lt;/p&gt;

&lt;p&gt;The Moving Sum Pattern helps answer:&lt;/p&gt;

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

&lt;p&gt;instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened on this row?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift often makes trends easier to see and decisions easier to make.&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;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 #3: The Missing Data Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 06 Jun 2026 14:39:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-3-the-missing-data-pattern-411e</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-3-the-missing-data-pattern-411e</guid>
      <description>&lt;p&gt;&lt;em&gt;Finding rows that disappear because of the wrong JOIN&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #3 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 rows sometimes disappear unexpectedly&lt;/li&gt;
&lt;li&gt;The difference between &lt;code&gt;INNER JOIN&lt;/code&gt; and &lt;code&gt;LEFT JOIN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to identify missing related data&lt;/li&gt;
&lt;li&gt;When to use the Missing Data Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most SQL developers eventually encounter a report that looks wrong.&lt;/p&gt;

&lt;p&gt;The numbers seem too low.&lt;/p&gt;

&lt;p&gt;Rows appear to be missing.&lt;/p&gt;

&lt;p&gt;And yet the query runs successfully.&lt;/p&gt;

&lt;p&gt;Often the issue is not the data.&lt;/p&gt;

&lt;p&gt;It's the JOIN.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question Behind the Query
&lt;/h2&gt;

&lt;p&gt;Many SQL queries involve combining information from multiple tables.&lt;/p&gt;

&lt;p&gt;The key question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I only want matching rows?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Do I want all rows from one table, even when no match exists?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those questions lead to very different results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Matching Rows Only
&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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns only rows that exist in both tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep All Rows
&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns all rows from the left table, even when no matching row exists in the right table.&lt;/p&gt;

&lt;p&gt;Rows without a match return &lt;code&gt;NULL&lt;/code&gt; values for the right-side columns.&lt;/p&gt;

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

&lt;p&gt;The Missing Data Pattern appears when data seems to have vanished from a result set.&lt;/p&gt;

&lt;p&gt;In many cases, nothing is actually missing.&lt;/p&gt;

&lt;p&gt;The query is simply filtering rows through an &lt;code&gt;INNER JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine a list of orders.&lt;/p&gt;

&lt;p&gt;Some orders have matching customers.&lt;/p&gt;

&lt;p&gt;Some do not.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;INNER JOIN&lt;/code&gt; only returns the orders with matching customer records.&lt;/p&gt;

&lt;p&gt;The remaining rows disappear from the result.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;LEFT JOIN&lt;/code&gt; reveals those unmatched rows.&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%2F8b6dfswldsm6g4f1lnof.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%2F8b6dfswldsm6g4f1lnof.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The image above summarizes the core idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;INNER JOIN&lt;/code&gt; returns only matching rows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LEFT JOIN&lt;/code&gt; keeps all rows from the left table&lt;/li&gt;
&lt;li&gt;Missing matches appear as &lt;code&gt;NULL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sometimes the missing rows are the most important rows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Using INNER JOIN
&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;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderID&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;Orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;ON&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;ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query returns only orders that have a matching customer record.&lt;/p&gt;

&lt;p&gt;Any order without a match is excluded.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Show me only the rows that exist in both tables.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Example Using LEFT JOIN
&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;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderID&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;Orders&lt;/span&gt; &lt;span class="n"&gt;o&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;Customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;ON&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;ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query keeps every order.&lt;/p&gt;

&lt;p&gt;If a matching customer does not exist, the customer columns return &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Show me every order, whether a matching customer exists or not.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Many reporting problems are not caused by bad data.&lt;/p&gt;

&lt;p&gt;They are caused by accidental filtering.&lt;/p&gt;

&lt;p&gt;Developers often write an &lt;code&gt;INNER JOIN&lt;/code&gt; without realizing that unmatched rows are being removed.&lt;/p&gt;

&lt;p&gt;The Missing Data Pattern encourages you to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What rows am I losing?&lt;/li&gt;
&lt;li&gt;Should unmatched rows be visible?&lt;/li&gt;
&lt;li&gt;Is the absence of a match important information?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the missing rows are exactly what you're trying to find.&lt;/p&gt;




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

&lt;p&gt;When a report seems incomplete, one of the first things I check is the JOIN type.&lt;/p&gt;

&lt;p&gt;A quick comparison 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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&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;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;can often reveal whether rows are being filtered unintentionally.&lt;/p&gt;

&lt;p&gt;This is especially useful when troubleshooting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;incomplete reports&lt;/li&gt;
&lt;li&gt;missing customers&lt;/li&gt;
&lt;li&gt;orphaned records&lt;/li&gt;
&lt;li&gt;failed imports&lt;/li&gt;
&lt;li&gt;data quality issues&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;I often think about the Missing Data Pattern when I need to find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customers without orders&lt;/li&gt;
&lt;li&gt;orders without customers&lt;/li&gt;
&lt;li&gt;employees without managers&lt;/li&gt;
&lt;li&gt;products without sales&lt;/li&gt;
&lt;li&gt;records missing related data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these situations, the absence of a relationship is often the most valuable information.&lt;/p&gt;




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

&lt;p&gt;When data seems to disappear, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the data actually missing?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Is my JOIN hiding it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes changing:&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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&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="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;completely changes what you discover.&lt;/p&gt;

&lt;p&gt;And sometimes the missing rows are the 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;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 #2: The Match Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Tue, 02 Jun 2026 16:31:00 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-2-the-match-pattern-4716</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-2-the-match-pattern-4716</guid>
      <description>&lt;p&gt;&lt;em&gt;Choosing between exact matches and pattern matches&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #2 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 exact matching and pattern matching&lt;/li&gt;
&lt;li&gt;When to use &lt;code&gt;=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When to use &lt;code&gt;LIKE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Why a small operator change can change the meaning of a query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most SQL developers run into this distinction eventually.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=&lt;/code&gt; and &lt;code&gt;LIKE&lt;/code&gt; solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=&lt;/code&gt; checks for an exact value.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LIKE&lt;/code&gt; checks whether a value fits a pattern.&lt;/p&gt;

&lt;p&gt;That difference matters when the question changes from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this exactly Admin?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this start with Admin?&lt;/p&gt;
&lt;/blockquote&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%2Fpk3ojvk28o4t2qov6u3d.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%2Fpk3ojvk28o4t2qov6u3d.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above summarizes the core idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;=&lt;/code&gt; asks whether a value is exactly equal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LIKE&lt;/code&gt; asks whether a value matches a pattern.&lt;/li&gt;
&lt;li&gt;Both are useful, but they answer different questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exact Match Example
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;=&lt;/code&gt; when the value must match exactly.&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="k"&gt;Role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Admin'&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;Is the role exactly &lt;code&gt;Admin&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It will match:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;But it will not match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AdminAssistant
Administrator
SuperAdmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;=&lt;/code&gt; operator is precise.&lt;/p&gt;




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

&lt;p&gt;Use &lt;code&gt;LIKE&lt;/code&gt; when the value may vary but still follows a recognizable pattern.&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="k"&gt;Role&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Admin%'&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;Does the role start with &lt;code&gt;Admin&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may match values like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin
AdminAssistant
Administrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;%&lt;/code&gt; wildcard means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any number of characters may appear here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So &lt;code&gt;Admin%&lt;/code&gt; means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Starts with &lt;code&gt;Admin&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;The &lt;strong&gt;Match Pattern&lt;/strong&gt; is about choosing the right kind of comparison for the question being asked.&lt;/p&gt;

&lt;p&gt;Sometimes you need precision.&lt;/p&gt;

&lt;p&gt;Sometimes you need flexibility.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;=&lt;/code&gt; for exact matches&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;LIKE&lt;/code&gt; for pattern-based matches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the effect can be significant.&lt;/p&gt;

&lt;p&gt;A query that is too strict may miss valid rows.&lt;/p&gt;

&lt;p&gt;A query that is too broad may return rows you did not intend to include.&lt;/p&gt;




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

&lt;p&gt;This distinction shows up in many real-world queries.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Find one exact status&lt;/li&gt;
&lt;li&gt;Find names that start with a prefix&lt;/li&gt;
&lt;li&gt;Find emails from a specific domain&lt;/li&gt;
&lt;li&gt;Find product codes with a shared pattern&lt;/li&gt;
&lt;li&gt;Find log messages containing a phrase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these questions requires a different kind of match.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Exact status match
&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;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;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks for orders where the status is exactly &lt;code&gt;Pending&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Prefix match
&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;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;Username&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'test%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks for usernames that start with &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Contains match
&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;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;Products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ProductName&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%keyboard%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks for product names that contain &lt;code&gt;keyboard&lt;/code&gt; anywhere in the value.&lt;/p&gt;




&lt;h3&gt;
  
  
  Suffix match
&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;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;Customers&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;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks for email addresses that end with &lt;code&gt;@example.com&lt;/code&gt;.&lt;/p&gt;




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

&lt;p&gt;Pattern matching can be powerful, but it can also affect performance.&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Admin%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may be easier for a database to optimize because the pattern has a fixed beginning.&lt;/p&gt;

&lt;p&gt;But 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;WHERE&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%Admin%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be more expensive because the database may need to search inside the value rather than starting from the beginning.&lt;/p&gt;

&lt;p&gt;The exact behavior depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the database system&lt;/li&gt;
&lt;li&gt;indexes&lt;/li&gt;
&lt;li&gt;collation settings&lt;/li&gt;
&lt;li&gt;data size&lt;/li&gt;
&lt;li&gt;query plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As always, validate assumptions 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 &lt;code&gt;=&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I need one exact value&lt;/li&gt;
&lt;li&gt;The column contains controlled values&lt;/li&gt;
&lt;li&gt;The comparison should be strict&lt;/li&gt;
&lt;li&gt;The query should not include partial matches&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;I need prefix matching&lt;/li&gt;
&lt;li&gt;I need suffix matching&lt;/li&gt;
&lt;li&gt;I need contains matching&lt;/li&gt;
&lt;li&gt;The data is text-based and variable&lt;/li&gt;
&lt;li&gt;I am intentionally searching for a pattern&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Small operator difference.&lt;/p&gt;

&lt;p&gt;Very different behavior.&lt;/p&gt;

&lt;p&gt;When writing SQL, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Am I looking for an exact value?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Am I looking for a pattern?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question determines whether the query should use &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;LIKE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The better you understand the match you need, the easier it becomes to write queries that return the right rows.&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 Patterns&lt;/strong&gt; series, 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 article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Pattern Series #1: The Presence 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>datascience</category>
      <category>programming</category>
    </item>
    <item>
      <title>SQL Pattern Series #1: The Presence Pattern</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Sat, 30 May 2026 03:26:55 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/sql-pattern-series-1-the-presence-pattern-a5f</link>
      <guid>https://dev.to/baldwin_apps/sql-pattern-series-1-the-presence-pattern-a5f</guid>
      <description>&lt;p&gt;&lt;em&gt;Thinking in terms of existence instead of lists&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Pattern Series #1 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;When EXISTS and IN solve the same problem&lt;/li&gt;
&lt;li&gt;The difference between set membership and existence&lt;/li&gt;
&lt;li&gt;Why the underlying mental model matters&lt;/li&gt;
&lt;li&gt;When I typically reach for EXISTS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most SQL developers write a query like this at some point:&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;c&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;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;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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it works.&lt;/p&gt;

&lt;p&gt;But sometimes it isn't the best way to think about the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question Behind the Query
&lt;/h2&gt;

&lt;p&gt;Many SQL problems can be framed in two different ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Membership
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this value in a set?&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;CustomerID&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Existence
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Does at least one matching row exist?&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;Both approaches often return the same result.&lt;/p&gt;

&lt;p&gt;But they represent different mental models.&lt;/p&gt;




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

&lt;p&gt;The &lt;strong&gt;Presence Pattern&lt;/strong&gt; is useful when you do not actually care about the values being returned from a related table.&lt;/p&gt;

&lt;p&gt;You only care whether a matching row exists.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Customers who have placed an order&lt;/li&gt;
&lt;li&gt;Users who have logged in&lt;/li&gt;
&lt;li&gt;Employees assigned to a project&lt;/li&gt;
&lt;li&gt;Products that have sales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, the question is often:&lt;/p&gt;

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

&lt;p&gt;rather than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What values are contained in this list?&lt;/p&gt;
&lt;/blockquote&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%2F4uappd1wt68l2uoku6q3.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%2F4uappd1wt68l2uoku6q3.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above summarizes the core idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EXISTS asks whether at least one matching row exists&lt;/li&gt;
&lt;li&gt;IN checks whether a value belongs to a set&lt;/li&gt;
&lt;li&gt;Both often return the same result&lt;/li&gt;
&lt;li&gt;The difference is usually the mental model&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Using EXISTS
&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;WHERE&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;The subquery is correlated to the outer query.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;For this customer, does at least one matching order exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As soon as the answer becomes true, the condition is satisfied.&lt;/p&gt;




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

&lt;p&gt;Many SQL developers initially learn syntax.&lt;/p&gt;

&lt;p&gt;Over time, they discover that query writing is really about choosing the right mental model.&lt;/p&gt;

&lt;p&gt;The Presence Pattern encourages you to think in terms of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existence&lt;/li&gt;
&lt;li&gt;relationships&lt;/li&gt;
&lt;li&gt;matching rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;instead of building lists unnecessarily.&lt;/p&gt;

&lt;p&gt;That shift often makes queries easier to reason about.&lt;/p&gt;




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

&lt;p&gt;Modern database optimizers are extremely sophisticated.&lt;/p&gt;

&lt;p&gt;In many systems, &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;EXISTS&lt;/code&gt; may be rewritten into similar execution plans.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same result does not necessarily mean the same execution strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the same syntax does not necessarily mean different performance.&lt;/p&gt;

&lt;p&gt;Always validate assumptions with execution plans and real-world testing.&lt;/p&gt;

&lt;p&gt;The value of this pattern is primarily conceptual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existence vs. membership&lt;/li&gt;
&lt;li&gt;relationship vs. list&lt;/li&gt;
&lt;li&gt;presence vs. values&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;I typically consider &lt;code&gt;EXISTS&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I only need to know whether related data exists&lt;/li&gt;
&lt;li&gt;The subquery may return many rows&lt;/li&gt;
&lt;li&gt;The relationship itself is the focus of the query&lt;/li&gt;
&lt;li&gt;I want the query to communicate intent clearly&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;customers with orders&lt;/li&gt;
&lt;li&gt;users with activity&lt;/li&gt;
&lt;li&gt;products with transactions&lt;/li&gt;
&lt;li&gt;accounts with associated records&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Many SQL problems become easier when you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I need the values?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Do I simply need to know whether they exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That small distinction changes how you think about the query.&lt;/p&gt;

&lt;p&gt;And sometimes, changing how you think about the problem is more important than changing the syntax.&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;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>beginners</category>
      <category>database</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
