<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Case Studies on ForgeSchema.com</title>
    <link>https://forgeschema.com/tags/case-studies/</link>
    <description>Recent content in Case Studies on ForgeSchema.com</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 18 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://forgeschema.com/tags/case-studies/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Adding a Foreign Key to a Legacy Orders Table Full of Orphan Rows</title>
      <link>https://forgeschema.com/adding-a-foreign-key-to-a-legacy-orders-table-full-of-orphan-rows/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/adding-a-foreign-key-to-a-legacy-orders-table-full-of-orphan-rows/</guid>
      <description>&lt;p&gt;This one comes up whenever an old application gets a second look. The &lt;code&gt;orders&lt;/code&gt; table has a &lt;code&gt;customer_id&lt;/code&gt; column. It has always pointed at &lt;code&gt;customers.id&lt;/code&gt;. Nobody ever declared that as a foreign key, because the original developer said the app handles it.&lt;/p&gt;&#xA;&lt;p&gt;The app mostly did. Over the years a few customers were deleted by hand, a data import skipped some rows, and now a handful of orders point at customers that don&amp;rsquo;t exist. Nobody knows how many.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding a Required Column to a Busy PostgreSQL Table and What Happens to Old Rows</title>
      <link>https://forgeschema.com/adding-a-required-column-to-a-busy-postgresql-table-and-what-happens-to-old-rows/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/adding-a-required-column-to-a-busy-postgresql-table-and-what-happens-to-old-rows/</guid>
      <description>&lt;p&gt;An online shop wants three new columns on &lt;code&gt;orders&lt;/code&gt;. A &lt;code&gt;status&lt;/code&gt; that every order must have. A &lt;code&gt;fulfilled_at&lt;/code&gt; timestamp. And a &lt;code&gt;public_id&lt;/code&gt; uuid to show customers instead of the internal key. All three are NOT NULL. The table has years of orders in it.&lt;/p&gt;&#xA;&lt;p&gt;On paper that&amp;rsquo;s three &lt;code&gt;ADD COLUMN&lt;/code&gt; lines. In practice each one does something different to the rows already there, and the difference between them is a lock that lasts a millisecond or one that lasts as long as it takes to rewrite the whole table.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding a Unique Email Constraint to a Customers Table That Already Has Duplicates</title>
      <link>https://forgeschema.com/adding-a-unique-email-constraint-to-a-customers-table-that-already-has-duplicates/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/adding-a-unique-email-constraint-to-a-customers-table-that-already-has-duplicates/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s a scenario almost every product team hits sooner or later. Sign-up has been running for two years. Somebody finally notices that &lt;code&gt;customers.email&lt;/code&gt; was never unique, and the login code quietly picks whichever row comes back first. The fix looks like one line of YAML.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;unique&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;customers_email_key&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;columns&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;email]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The line is easy. The data is the problem. Two years of sign-ups almost certainly left a few duplicates behind, maybe a user who registered twice, maybe a test account copied from staging. You won&amp;rsquo;t know until PostgreSQL tries to build the constraint.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building an Index on a Large PostgreSQL Table Without Freezing the Rest of the App</title>
      <link>https://forgeschema.com/building-an-index-on-a-large-postgresql-table-without-freezing-the-rest-of-the-app/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/building-an-index-on-a-large-postgresql-table-without-freezing-the-rest-of-the-app/</guid>
      <description>&lt;p&gt;Reporting is slow. The query behind the monthly dashboard filters &lt;code&gt;events&lt;/code&gt; by account and date, and &lt;code&gt;events&lt;/code&gt; is by far the biggest table in the database. Everyone agrees it needs an index. Nobody wants to be the one who runs it.&lt;/p&gt;&#xA;&lt;p&gt;The fear is reasonable. An ordinary &lt;code&gt;CREATE INDEX&lt;/code&gt; blocks writes to the table while it builds, and a migration that holds an exclusive lock blocks reads too. On a big table that can take a while. Lock every table in the schema for the length of the migration, as ForgeSchema&amp;rsquo;s own first alpha did, and the whole app stalls for a reporting index.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Catching PostgreSQL Schema Drift in CI With ForgeSchema Verify</title>
      <link>https://forgeschema.com/catching-postgresql-schema-drift-in-ci-with-forgeschema-verify/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/catching-postgresql-schema-drift-in-ci-with-forgeschema-verify/</guid>
      <description>&lt;p&gt;Picture a team with three environments: development, staging and production. The schema lives in a YAML file in the repository, and everyone agrees that file is the truth. Six months in, nobody is quite sure it still is.&lt;/p&gt;&#xA;&lt;p&gt;Staging got an extra index during a load test. Production has a column someone added from a SQL console during an incident. Development was rebuilt from scratch last week and is probably fine. Nobody wrote any of this down.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How ForgeSchema Blocks an Accidental Column Drop From a Bad Merge</title>
      <link>https://forgeschema.com/how-forgeschema-blocks-an-accidental-column-drop-from-a-bad-merge/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/how-forgeschema-blocks-an-accidental-column-drop-from-a-bad-merge/</guid>
      <description>&lt;p&gt;Two branches both touched &lt;code&gt;schema/app.yaml&lt;/code&gt;. One added a &lt;code&gt;phone&lt;/code&gt; column to &lt;code&gt;customers&lt;/code&gt;. The other moved a few columns around for readability. Git resolved the conflict, the build was green, and the merged file was missing &lt;code&gt;billing_address&lt;/code&gt;. Nobody noticed. YAML diffs of moved blocks are hard to read.&lt;/p&gt;&#xA;&lt;p&gt;With a tool that turns a diff straight into SQL, the next deploy runs &lt;code&gt;DROP COLUMN billing_address&lt;/code&gt;. The column and every value in it are gone, and the first sign is a support ticket about invoices.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Renaming a PostgreSQL Column Without Losing Data in ForgeSchema</title>
      <link>https://forgeschema.com/renaming-a-postgresql-column-without-losing-data-in-forgeschema/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/renaming-a-postgresql-column-without-losing-data-in-forgeschema/</guid>
      <description>&lt;p&gt;A small team is tidying up an old schema. The &lt;code&gt;customers&lt;/code&gt; table has a column called &lt;code&gt;customer_name&lt;/code&gt;, which is redundant inside a table called customers. They want &lt;code&gt;full_name&lt;/code&gt;. They edit the YAML, swap the name, and run a diff.&lt;/p&gt;&#xA;&lt;p&gt;Most diff-based migration tools see exactly what you&amp;rsquo;d expect from text. One column disappeared, another appeared. The cheap way to get from one to the other is drop and add, and the cheap way deletes every name in the table.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Why ForgeSchema Refuses a Migration Plan Made Stale by an Overnight Hotfix</title>
      <link>https://forgeschema.com/why-forgeschema-refuses-a-migration-plan-made-stale-by-an-overnight-hotfix/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://forgeschema.com/why-forgeschema-refuses-a-migration-plan-made-stale-by-an-overnight-hotfix/</guid>
      <description>&lt;p&gt;Monday afternoon, a developer builds a migration plan. It adds a &lt;code&gt;notes&lt;/code&gt; column to &lt;code&gt;customers&lt;/code&gt; and an index on it. Two colleagues review it, approve it, and it&amp;rsquo;s scheduled for Wednesday&amp;rsquo;s maintenance window.&lt;/p&gt;&#xA;&lt;p&gt;Tuesday night there&amp;rsquo;s an incident. Someone on call adds a column to &lt;code&gt;customers&lt;/code&gt; by hand to unblock a hotfix. It works, everyone goes to bed, and the plan sitting in the deploy queue still describes Monday&amp;rsquo;s database.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
