<?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.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 #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>
    <item>
      <title>One Practical SQL Trigger Example You Can Actually Use</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Fri, 29 May 2026 12:35:32 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/one-practical-sql-trigger-example-you-can-actually-use-1adl</link>
      <guid>https://dev.to/baldwin_apps/one-practical-sql-trigger-example-you-can-actually-use-1adl</guid>
      <description>&lt;p&gt;&lt;em&gt;One UPDATE statement. One trigger. One automatic audit record — no extra code required.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Triggers are one of those SQL features that can seem a little mysterious at first, but the basic idea is simple: a trigger lets the database automatically do something when data changes.&lt;/p&gt;

&lt;p&gt;In this example, we'll use a trigger to create a basic audit trail.&lt;/p&gt;

&lt;p&gt;Whenever a row in an &lt;code&gt;Employees&lt;/code&gt; table is updated, the trigger will record the old and new salary values in a separate audit table.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The primary examples use SQL Server / T-SQL syntax. Where behavior or syntax differs meaningfully across SQL Server, MySQL, PostgreSQL, and Oracle, that's called out directly.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Trigger?
&lt;/h2&gt;

&lt;p&gt;A trigger is a block of SQL that runs automatically in response to an event such as an &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means the database can react to changes without relying on someone to remember to run extra code manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Main Table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server / MySQL / PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This syntax works across SQL Server, MySQL, and PostgreSQL. In Oracle, &lt;code&gt;VARCHAR&lt;/code&gt; is supported but &lt;code&gt;VARCHAR2&lt;/code&gt; is the preferred type:&lt;/em&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="c1"&gt;-- Oracle&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="n"&gt;VARCHAR2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Insert Some Sample Data
&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;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Employees&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;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Alice Johnson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60000&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Brian Smith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72000&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Multi-row INSERT with VALUES works across all four databases.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create the Audit Table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;AuditID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OldSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NewSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ChangedAt&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This table will store each salary change, including the old value, the new value, and when the change happened.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Auto-incrementing primary keys and current timestamp functions vary across databases:&lt;/em&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="c1"&gt;-- MySQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;AuditID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OldSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NewSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ChangedAt&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;AuditID&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OldSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NewSalary&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ChangedAt&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Oracle&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;AuditID&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OldSalary&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NewSalary&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ChangedAt&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;SYSDATE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Key differences:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server uses &lt;code&gt;IDENTITY(1,1)&lt;/code&gt; and &lt;code&gt;GETDATE()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MySQL uses &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; and &lt;code&gt;NOW()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL uses &lt;code&gt;SERIAL&lt;/code&gt; (or &lt;code&gt;GENERATED ALWAYS AS IDENTITY&lt;/code&gt; in newer versions) and &lt;code&gt;NOW()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Oracle uses &lt;code&gt;GENERATED ALWAYS AS IDENTITY&lt;/code&gt; and &lt;code&gt;SYSDATE&lt;/code&gt;
## Step 4: Create the Trigger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the databases diverge most significantly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL Server&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_LogSalaryChange&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&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;OldSalary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewSalary&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;d&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;OldSalary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;NewSalary&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt; &lt;span class="n"&gt;d&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;inserted&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmployeeID&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SQL Server, triggers can access two special logical tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;deleted&lt;/code&gt; contains the old version of updated rows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inserted&lt;/code&gt; contains the new version of updated rows
The trigger joins those tables on &lt;code&gt;EmployeeID&lt;/code&gt; and logs only rows where the salary actually changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;A note on the WHERE clause: a simple &lt;code&gt;d.Salary &amp;lt;&amp;gt; i.Salary&lt;/code&gt; won't catch changes involving NULL values — in SQL, &lt;code&gt;NULL &amp;lt;&amp;gt; anything&lt;/code&gt; evaluates to unknown, not true. The additional &lt;code&gt;IS NULL&lt;/code&gt; conditions ensure those changes are captured correctly. This applies to all four database versions below.&lt;/em&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="c1"&gt;-- MySQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_LogSalaryChange&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&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;OldSalary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewSalary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&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="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&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;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL (requires a trigger function)&lt;/span&gt;
&lt;span class="c1"&gt;-- Note: EXECUTE FUNCTION requires PostgreSQL 11+&lt;/span&gt;
&lt;span class="c1"&gt;-- Use EXECUTE PROCEDURE for older versions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;log_salary_change&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&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;OldSalary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewSalary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&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="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&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;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_LogSalaryChange&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;log_salary_change&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Oracle&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_LogSalaryChange&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&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;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;EmployeeSalaryAudit&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;OldSalary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewSalary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="k"&gt;OLD&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="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&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;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Key differences across databases:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server uses &lt;code&gt;deleted&lt;/code&gt; and &lt;code&gt;inserted&lt;/code&gt; virtual tables and handles multiple rows as sets in a single statement-level trigger&lt;/li&gt;
&lt;li&gt;MySQL uses &lt;code&gt;OLD&lt;/code&gt; and &lt;code&gt;NEW&lt;/code&gt; and requires &lt;code&gt;FOR EACH ROW&lt;/code&gt; — MySQL only supports row-level triggers&lt;/li&gt;
&lt;li&gt;PostgreSQL separates the trigger logic into a reusable trigger function, then attaches it to the table — &lt;code&gt;OLD&lt;/code&gt; and &lt;code&gt;NEW&lt;/code&gt; are used inside the function. &lt;code&gt;EXECUTE FUNCTION&lt;/code&gt; requires PostgreSQL 11 or later; use &lt;code&gt;EXECUTE PROCEDURE&lt;/code&gt; for older versions&lt;/li&gt;
&lt;li&gt;Oracle uses &lt;code&gt;:OLD&lt;/code&gt; and &lt;code&gt;:NEW&lt;/code&gt; (note the colon prefix) and requires &lt;code&gt;FOR EACH ROW&lt;/code&gt; for row-level triggers
## A Note on Statement-Level vs Row-Level Triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the most important distinctions to understand — and one that can produce unexpected results if you're not aware of it.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;row-level trigger&lt;/strong&gt; fires once for every row affected by the operation. Update 50 rows and it fires 50 times.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;statement-level trigger&lt;/strong&gt; fires once per SQL statement, regardless of how many rows were affected.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server triggers are statement-level by default, which is why the SQL Server example joins &lt;code&gt;deleted&lt;/code&gt; and &lt;code&gt;inserted&lt;/code&gt; as sets rather than referencing single OLD/NEW values&lt;/li&gt;
&lt;li&gt;MySQL only supports row-level triggers&lt;/li&gt;
&lt;li&gt;PostgreSQL supports both — &lt;code&gt;FOR EACH ROW&lt;/code&gt; for row-level, &lt;code&gt;FOR EACH STATEMENT&lt;/code&gt; for statement-level&lt;/li&gt;
&lt;li&gt;Oracle supports both — &lt;code&gt;FOR EACH ROW&lt;/code&gt; for row-level, statement-level is the default without it
This matters in practice: if you run a bulk update affecting 1,000 rows, a row-level trigger fires 1,000 times. For audit logging on large tables, that's worth thinking about before you deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Test It
&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;65000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;EmployeeID&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;h2&gt;
  
  
  Step 6: Check the Audit Table
&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="n"&gt;EmployeeSalaryAudit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a row showing that Employee 1's salary changed from &lt;code&gt;60000.00&lt;/code&gt; to &lt;code&gt;65000.00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That happened automatically because of the trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Useful
&lt;/h2&gt;

&lt;p&gt;This kind of trigger can be useful when you want the database to help maintain a history of important changes.&lt;/p&gt;

&lt;p&gt;Instead of depending on application code to remember to log the update, the database handles it consistently — regardless of which application, tool, or user made the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Caution
&lt;/h2&gt;

&lt;p&gt;Triggers are powerful, but they should be used carefully.&lt;/p&gt;

&lt;p&gt;Because they run automatically behind the scenes, they can make database behavior harder to understand if too much hidden logic accumulates. A developer looking at the &lt;code&gt;UPDATE&lt;/code&gt; statement alone has no visible indication that something else is happening as a result.&lt;/p&gt;

&lt;p&gt;For a simple audit trail like this, though, a trigger can be a practical and readable solution — and audit logging is one of the most widely accepted use cases for triggers across all four major databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;This is a small example, but it demonstrates the central idea: when data changes, the database can respond automatically.&lt;/p&gt;

&lt;p&gt;The syntax looks different depending on which database you're working with — but the concept is the same everywhere. A trigger watches for an event, and when that event happens, it acts.&lt;/p&gt;

&lt;p&gt;That's what makes triggers useful — and why they're worth understanding.&lt;/p&gt;

&lt;p&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; covers the query logic and filtering patterns that form the building blocks of trigger design — through daily challenges and active recall that make these concepts click faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download &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; free on the App Store.&lt;/strong&gt; Search "SQL Bubble Pop."&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A trigger runs automatically when data changes — no manual intervention needed&lt;/li&gt;
&lt;li&gt;SQL Server uses &lt;code&gt;deleted&lt;/code&gt; and &lt;code&gt;inserted&lt;/code&gt; virtual tables; other databases use &lt;code&gt;OLD&lt;/code&gt; and &lt;code&gt;NEW&lt;/code&gt;; Oracle adds a colon prefix (&lt;code&gt;:OLD&lt;/code&gt; and &lt;code&gt;:NEW&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;PostgreSQL requires a separate trigger function before attaching the trigger to a table (&lt;code&gt;EXECUTE FUNCTION&lt;/code&gt; requires PostgreSQL 11+)&lt;/li&gt;
&lt;li&gt;Row-level triggers fire once per affected row; statement-level triggers fire once per SQL statement — know which you're using&lt;/li&gt;
&lt;li&gt;Audit logging is one of the most widely accepted and practical use cases for triggers&lt;/li&gt;
&lt;li&gt;Use triggers carefully — hidden logic can make database behavior harder to reason about at scale&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>Before SQL, We Had to Tell Computers Everything. Then One Idea Changed That Forever.</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Thu, 28 May 2026 19:37:32 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/before-sql-we-had-to-tell-computers-everything-then-one-idea-changed-that-forever-3ddl</link>
      <guid>https://dev.to/baldwin_apps/before-sql-we-had-to-tell-computers-everything-then-one-idea-changed-that-forever-3ddl</guid>
      <description>&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%2Fm1pcm35lde2qzgaajy51.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%2Fm1pcm35lde2qzgaajy51.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
Here's something most SQL tutorials skip entirely:&lt;/p&gt;

&lt;p&gt;SQL wasn't just a new syntax. It was a new &lt;em&gt;way of thinking&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Before SQL existed, most systems were built around procedural programming. You didn't describe a result — you described every step to get there. Fetch the data. Check a condition. Process row by row. Loop again. Handle the edge cases. Store the result. Repeat.&lt;/p&gt;

&lt;p&gt;It worked. But it was exhausting. And fragile. And hard to scale.&lt;/p&gt;

&lt;p&gt;Then SQL introduced an idea that sounds almost too simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell the system what you want — not how to do it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Way Was Expensive
&lt;/h2&gt;

&lt;p&gt;Procedural data access put all the cognitive weight on the developer. You were responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deciding how to traverse the data&lt;/li&gt;
&lt;li&gt;Managing every loop and condition manually&lt;/li&gt;
&lt;li&gt;Anticipating performance edge cases&lt;/li&gt;
&lt;li&gt;Writing code that was tightly coupled to &lt;em&gt;how&lt;/em&gt; the data was stored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Change the storage structure, and your code broke. Add more records, and your performance tanked. It scaled poorly in both directions — complexity and volume.&lt;/p&gt;

&lt;p&gt;The database engine knew things about the data that you didn't. But you weren't allowed to use that knowledge. You had to do it yourself, row by row, the hard way.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Query. A Radically Different Contract.
&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="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No loops. No manual traversal. No step-by-step instruction manual.&lt;/p&gt;

&lt;p&gt;You described the &lt;em&gt;outcome&lt;/em&gt;. The database figured out the &lt;em&gt;path&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's not a small thing. That's a complete inversion of the relationship between the developer and the system.&lt;/p&gt;

&lt;p&gt;With SQL, you stopped being the engine and started being the director. You tell the system what matters. The query optimizer decides how to get there — what index to use, what join order makes sense, what execution plan fits the data.&lt;/p&gt;

&lt;p&gt;More power. Less code. More room to focus on &lt;em&gt;what the problem actually is&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Still Matters in 2025
&lt;/h2&gt;

&lt;p&gt;You might think this is ancient history — SQL is nearly 50 years old at this point. But the shift it introduced isn't historical trivia.&lt;/p&gt;

&lt;p&gt;Every modern analytics dashboard you've ever seen is built on it. Every reporting pipeline. Every business intelligence tool. Every time a developer writes a query to answer a business question, they're standing on that original insight: &lt;em&gt;describe the result, let the system do the work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The declarative mindset also spread far beyond SQL. It shaped how we think about CSS (describe the layout, not the pixel math), how React works (describe the UI state, not the DOM manipulation), how infrastructure-as-code tools like Terraform operate.&lt;/p&gt;

&lt;p&gt;SQL wasn't just influential for databases. It was a template for how humans could collaborate with computers more effectively.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mindset Shift Is the Point
&lt;/h2&gt;

&lt;p&gt;This is why I always tell new learners: SQL isn't really about memorizing syntax. The syntax is a few keywords. You can look those up.&lt;/p&gt;

&lt;p&gt;The deeper skill is learning to think in outcomes.&lt;/p&gt;

&lt;p&gt;What result do you need? What does the data look like? What shape do you want the answer in? Let the engine worry about the how.&lt;/p&gt;

&lt;p&gt;That's a trainable skill. And once it clicks, it changes the way you approach problems — not just in databases, but everywhere you interact with systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  You Can Practice This Right Now
&lt;/h2&gt;

&lt;p&gt;If you've been avoiding SQL because it feels technical or gatekept, I want to offer a different frame:&lt;/p&gt;

&lt;p&gt;SQL is one of the most &lt;em&gt;human-readable&lt;/em&gt; languages we have. You're not writing machine code. You're describing what you want in something close to plain English. "Select all customers where their balance is greater than 1000" — that &lt;em&gt;is&lt;/em&gt; the query, almost word for word.&lt;/p&gt;

&lt;p&gt;The barrier is lower than it looks. The mental model is the thing worth building.&lt;/p&gt;

&lt;p&gt;Start there. The syntax will follow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tiye Baldwin-Anderson is the founder of &lt;a href="https://baldwinapps.com" rel="noopener noreferrer"&gt;Baldwin Apps LLC&lt;/a&gt; &lt;br&gt;
and the creator of &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;, a gamified iOS app for learning SQL through play. She writes about SQL, tech literacy, and building things that make learning feel like a game.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>COALESCE in SQL: the “Don’t Let NULL Win” Function You’ll Use Everywhere</title>
      <dc:creator>Baldwin Apps</dc:creator>
      <pubDate>Wed, 18 Feb 2026 11:07:46 +0000</pubDate>
      <link>https://dev.to/baldwin_apps/coalesce-in-sql-the-dont-let-null-win-function-youll-use-everywhere-n7o</link>
      <guid>https://dev.to/baldwin_apps/coalesce-in-sql-the-dont-let-null-win-function-youll-use-everywhere-n7o</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from Medium. If NULL has ever “blanked out” a result you know should exist, COALESCE is the fix you’ll use forever.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever looked at a query result and thought:&lt;/p&gt;

&lt;p&gt;“Why is this blank? I know there’s data here…”&lt;/p&gt;

&lt;p&gt;…it was probably NULL doing what NULL does.&lt;/p&gt;

&lt;p&gt;Some databases will happily let a missing value sneak through your calculations, concatenations, and reports — then you’re left debugging a spreadsheet like it’s a crime scene.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;COALESCE&lt;/strong&gt;: one of the simplest functions in SQL, and also one of the most useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What COALESCE does (in plain English)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;COALESCE&lt;/strong&gt; returns the first non-NULL value from a list of expressions.&lt;/p&gt;

&lt;p&gt;Think of it like a fallback chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If this value is NULL, try the next one&lt;/li&gt;
&lt;li&gt;If that’s NULL too, try the next one&lt;/li&gt;
&lt;li&gt;Keep going…&lt;/li&gt;
&lt;li&gt;If they’re all NULL, you get NULL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The basic pattern
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COALESCE(value1, value2, value3, ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Returns the first non-NULL value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick example: “Give me a name… any name”
&lt;/h2&gt;

&lt;p&gt;Let’s say you have a users table and people can have a display name, a full name, or (worst case) nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  COALESCE(display_name, full_name, 'Anonymous') AS shown_name
FROM users;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use display_name if it exists&lt;/li&gt;
&lt;li&gt;Otherwise use full_name&lt;/li&gt;
&lt;li&gt;Otherwise label them 'Anonymous'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building apps, dashboards, or any user-facing output, this is basically required.&lt;/p&gt;

&lt;h2&gt;
  
  
  COALESCE vs NULL: why it matters
&lt;/h2&gt;

&lt;p&gt;NULL isn’t “empty” or “zero” or “blank”.&lt;/p&gt;

&lt;p&gt;NULL means: unknown / missing / not provided.&lt;/p&gt;

&lt;p&gt;And SQL treats NULL like a wildcard ghost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NULL breaks math&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 10 + NULL;   -- result: NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if you’re doing totals, averages, costs, scoring, etc., one NULL can nuke your result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix it with COALESCE&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 10 + COALESCE(NULL, 0);  -- result: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-world use cases you’ll hit constantly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1) Safe totals (avoid NULL turning everything into NULL)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  order_id,
  COALESCE(subtotal, 0)
  + COALESCE(tax, 0)
  + COALESCE(shipping, 0) AS total
FROM orders;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is reporting-grade SQL: stable, predictable, and less likely to make you question reality at 1 a.m.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Text concatenation without “NULL poisoning”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many SQL dialects will produce NULL if you concatenate text with NULL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  COALESCE(first_name, '')
  || ' '
  || COALESCE(last_name, '') AS full_name
FROM customers;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Depending on your database you may use CONCAT() instead of ||, but the COALESCE idea stays the same.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) “Prefer this column, fallback to that one”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Classic: billing address might be missing so you fall back to shipping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  customer_id,
  COALESCE(billing_state, shipping_state) AS state
FROM customer_addresses;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common gotcha: type mismatches
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;COALESCE&lt;/strong&gt; returns a single value, and SQL tries to make all the arguments compatible.&lt;/p&gt;

&lt;p&gt;So avoid mixing types like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COALESCE(age, 'unknown')   -- might error or auto-cast in weird ways
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep it numeric:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COALESCE(age, 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;or keep it text:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COALESCE(CAST(age AS VARCHAR), 'unknown')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Casting syntax varies by database.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;Where are NULLs messing with your life right now?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;totals?&lt;/li&gt;
&lt;li&gt;string output?&lt;/li&gt;
&lt;li&gt;missing user fields?&lt;/li&gt;
&lt;li&gt;inconsistent imports?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your scenario in the comments — if it’s a common pain point, I’ll write the next article as a fix.&lt;/p&gt;

&lt;p&gt;🫧 SQL Bubble Pop — a quick, game-style SQL trainer designed to help you build instinct for joins, views, CTEs, and query logic in short daily sessions.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>programming</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
