<?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>Forem: Jessica Aki</title>
    <description>The latest articles on Forem by Jessica Aki (@mazinocodes).</description>
    <link>https://forem.com/mazinocodes</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%2F3695234%2F440a5f78-d681-4396-a787-5824d0cad6f7.png</url>
      <title>Forem: Jessica Aki</title>
      <link>https://forem.com/mazinocodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mazinocodes"/>
    <language>en</language>
    <item>
      <title>SQL Stored Procedures: From Hard‑Coded Logic to Reusable SQL</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Sun, 01 Feb 2026 23:04:56 +0000</pubDate>
      <link>https://forem.com/mazinocodes/sql-stored-procedures-from-hard-coded-logic-to-reusable-sql-mn5</link>
      <guid>https://forem.com/mazinocodes/sql-stored-procedures-from-hard-coded-logic-to-reusable-sql-mn5</guid>
      <description>&lt;p&gt;When I first learned SQL, everything was very… static.&lt;/p&gt;

&lt;p&gt;I wrote queries. They ran. They returned results. End of story.&lt;/p&gt;

&lt;p&gt;Then I hit stored procedures.&lt;/p&gt;

&lt;p&gt;Suddenly SQL wasn’t just about asking questions anymore, it was about telling the database to do work. And honestly, that shift confused me more than I expected.&lt;/p&gt;

&lt;p&gt;This post is not a perfect or advanced guide. It’s the explanation I wish I had when stored procedures first stopped making sense, especially when CASE and parameters entered the picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Finally Clicked: Stored Procedures Are Just Reusable Logic
&lt;/h2&gt;

&lt;p&gt;The mistake I made early on was treating stored procedures as something exotic.&lt;/p&gt;

&lt;p&gt;They’re not.&lt;/p&gt;

&lt;p&gt;The simplest way this finally made sense to me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A stored procedure is just a named SQL logic that lives in the database and can accept inputs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;If you already understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;functions&lt;/li&gt;
&lt;li&gt;parameters&lt;/li&gt;
&lt;li&gt;if / else logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then stored procedures are not a new concept, just a new name in a new environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Plain SQL Started Feeling Limited
&lt;/h2&gt;

&lt;p&gt;Imagine this very normal requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Give me all orders above a certain amount.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In plain SQL, I might write:&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;orders&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;total_amount&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 works… but it’s &lt;strong&gt;hard-coded&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If tomorrow the rule changes to 1500, I’m editing the query.&lt;br&gt;
If another report needs 500, I’m copying the query.&lt;/p&gt;

&lt;p&gt;That repetition is where stored procedures start to make sense.&lt;/p&gt;


&lt;h2&gt;
  
  
  My First Useful Stored Procedure
&lt;/h2&gt;

&lt;p&gt;This was my first step:&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;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;procedure&lt;/span&gt; &lt;span class="n"&gt;high_value_orders&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;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;total_amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I could just run:&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;exec&lt;/span&gt; &lt;span class="n"&gt;high_value_orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This felt nice… but still flawed. The rule is still hard-coded.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Upgrade: Parameters
&lt;/h2&gt;

&lt;p&gt;This is where things finally started feeling powerful. Instead of fixing the value inside the procedure, I pass it in.&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;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;procedure&lt;/span&gt; &lt;span class="n"&gt;high_value_orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;p_min_amount&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;)&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;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;total_amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p_min_amount&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;Now I can reuse the same logic:&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;exec&lt;/span&gt; &lt;span class="n"&gt;high_value_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;high_value_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a big moment for me. I wasn’t just writing SQL anymore rather I was writing configurable behaviour.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where CASE Entered the Picture
&lt;/h2&gt;

&lt;p&gt;CASE confused me at first because I kept seeing it explained abstractly.&lt;/p&gt;

&lt;p&gt;What helped was using it to encode business rules.&lt;/p&gt;

&lt;p&gt;Example requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Classify customers based on how much they’ve spent.”&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;select&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total_spent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;total_spent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'VIP'&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;total_spent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'Regular'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;'Occasional'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;customer_type&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finally clicked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CASE is SQL’s version of if / else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just conditions evaluated top to bottom.&lt;/p&gt;




&lt;h2&gt;
  
  
  Combining CASE With Stored Procedures
&lt;/h2&gt;

&lt;p&gt;This is where everything came together.&lt;/p&gt;

&lt;p&gt;Let’s say the business wants:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Show me customers, but label them differently depending on the threshold I care about.”&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;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;procedure&lt;/span&gt; &lt;span class="n"&gt;customer_segments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;p_vip_threshold&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;)&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;select&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_spent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;total_spent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;p_vip_threshold&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'VIP'&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;'Non-VIP'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&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;Now the logic adapts:&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;exec&lt;/span&gt; &lt;span class="n"&gt;customer_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;customer_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same procedure. Different behavior.&lt;/p&gt;

&lt;p&gt;That’s when stored procedures stopped feeling academic and started feeling useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Was Doing Wrong Before
&lt;/h2&gt;

&lt;p&gt;Looking back, my mistakes were simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I treated stored procedures like advanced SQL syntax&lt;/li&gt;
&lt;li&gt;I avoided parameters and hard-coded everything&lt;/li&gt;
&lt;li&gt;I saw CASE as a trick, not logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I reframed them as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable logic&lt;/li&gt;
&lt;li&gt;configurable inputs&lt;/li&gt;
&lt;li&gt;conditional rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything settled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters Beyond Syntax
&lt;/h2&gt;

&lt;p&gt;SQL is not just querying data, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enforcing rules&lt;/li&gt;
&lt;li&gt;centralizing logic&lt;/li&gt;
&lt;li&gt;reducing duplication&lt;/li&gt;
&lt;li&gt;making behaviour explicit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stored procedures aren’t always the answer but understanding them changes how you think about databases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If stored procedures or CASE ever felt heavy or unnecessary, I get it.&lt;/p&gt;

&lt;p&gt;They only make sense once you stop thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I write this query?”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;“How do I make this logic reusable?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift took me a while but once it happened, SQL stopped feeling shallow.&lt;/p&gt;




&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Data &amp;amp; Database Engineering Enthusiast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m documenting my journey learning SQL, databases, and the systems behind real-world data platforms in public.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
      <category>beginners</category>
    </item>
    <item>
      <title>ROLLUP vs CUBE: How I Finally Understood SQL Subtotals</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Mon, 26 Jan 2026 01:36:08 +0000</pubDate>
      <link>https://forem.com/mazinocodes/rollup-vs-cube-how-i-finally-understood-sql-subtotals-4a49</link>
      <guid>https://forem.com/mazinocodes/rollup-vs-cube-how-i-finally-understood-sql-subtotals-4a49</guid>
      <description>&lt;p&gt;When I first ran into &lt;strong&gt;ROLLUP&lt;/strong&gt; and &lt;strong&gt;CUBE&lt;/strong&gt; in SQL, I genuinely thought I understood them.&lt;/p&gt;

&lt;p&gt;I didn’t.&lt;/p&gt;

&lt;p&gt;I knew the syntax. I could write the queries. But every time I looked at the results, they felt… random. And whenever SQL feels or seems random, it usually means I’m missing something important.&lt;/p&gt;

&lt;p&gt;This post is me being honest about what confused me at first, where my assumptions were wrong and what finally made these concepts click&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup: A Real Sales Table
&lt;/h2&gt;

&lt;p&gt;Let’s use something realistic: &lt;strong&gt;sales data&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;region&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;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;product&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;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;amount&lt;/span&gt;    &lt;span class="n"&gt;number&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="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'West'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Laptop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;);&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;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'West'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;);&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;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'West'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tablet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&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;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'East'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Laptop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1100&lt;/span&gt;&lt;span class="p"&gt;);&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;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'East'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Phone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;);&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;sales&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'North'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Laptop'&lt;/span&gt;&lt;span class="p"&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;Here’s what the data actually looks like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;product&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Tablet&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can think of this as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions&lt;/strong&gt; → region, product&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure&lt;/strong&gt; → sum(amount)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where I Was Comfortable: Basic GROUP BY
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;sum(amount)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;2600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;1800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Clear rule that made sense early on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every column in &lt;code&gt;SELECT&lt;/code&gt; that isn’t aggregated must appear in &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No drama here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Things Started to Break: Subtotals
&lt;/h2&gt;

&lt;p&gt;Then I met 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;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="k"&gt;rollup&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And immediately, I made an assumption.&lt;/p&gt;




&lt;h2&gt;
  
  
  My First Wrong Assumption
&lt;/h2&gt;

&lt;p&gt;I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“ROLLUP gives me &lt;em&gt;all possible totals&lt;/em&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;totals per (region, product)&lt;/li&gt;
&lt;li&gt;totals per region&lt;/li&gt;
&lt;li&gt;totals per product&lt;/li&gt;
&lt;li&gt;grand total&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds reasonable right? Well, It’s also wrong.&lt;/p&gt;

&lt;p&gt;That actually describes &lt;strong&gt;CUBE&lt;/strong&gt;, not ROLLUP.&lt;/p&gt;




&lt;h2&gt;
  
  
  What ROLLUP Actually Does
&lt;/h2&gt;

&lt;p&gt;ROLLUP follows a &lt;strong&gt;hierarchy&lt;/strong&gt;, and &lt;strong&gt;order matters&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="k"&gt;rollup&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQL processes this &lt;strong&gt;from right to left&lt;/strong&gt;, producing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;(region, product) → detailed rows&lt;/li&gt;
&lt;li&gt;(region) → region subtotals&lt;/li&gt;
&lt;li&gt;() → grand total&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;No product-only totals. No extra combinations.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Results Really Mean
&lt;/h2&gt;

&lt;p&gt;The output looks something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;product&lt;/th&gt;
&lt;th&gt;sum(amount)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Tablet&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;2600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;1800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;5400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This part tripped me up for a while:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NULL here does not mean missing data.&lt;br&gt;
It means this column was rolled up into a subtotal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once I understood that, the results stopped looking random.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Subtle Query That Forced Clarity
&lt;/h2&gt;

&lt;p&gt;This one confused me more than the first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;rollup&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I expected a grand total again.&lt;/p&gt;

&lt;p&gt;But that’s not what happens.&lt;/p&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ROLLUP only affects the columns inside it&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generated groupings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(region, product)&lt;/li&gt;
&lt;li&gt;(region)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the totals are &lt;strong&gt;per region&lt;/strong&gt;, not for the whole table. That detail finally made the rules feel consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where CUBE Finally Made Sense
&lt;/h2&gt;

&lt;p&gt;Once ROLLUP stopped feeling mysterious, CUBE became straightforward as it was my first misunderstanding of ROLLUPs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="k"&gt;cube&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CUBE generates every possible combination.&lt;/p&gt;

&lt;p&gt;For two columns, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(region, product)&lt;/li&gt;
&lt;li&gt;(region)&lt;/li&gt;
&lt;li&gt;(product)&lt;/li&gt;
&lt;li&gt;()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detailed rows&lt;/li&gt;
&lt;li&gt;region totals&lt;/li&gt;
&lt;li&gt;product totals&lt;/li&gt;
&lt;li&gt;grand total&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what I originally (and incorrectly) thought ROLLUP did.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Now Tell Them Apart
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ROLLUP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;follows a hierarchy&lt;/li&gt;
&lt;li&gt;order matters&lt;/li&gt;
&lt;li&gt;structured summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have &lt;code&gt;N&lt;/code&gt; columns, you get&lt;code&gt;N + 1&lt;/code&gt; grouping levels.&lt;/p&gt;

&lt;p&gt;Great for reports like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;day → month → year&lt;/li&gt;
&lt;li&gt;region → country → global&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  CUBE
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;every combination&lt;/li&gt;
&lt;li&gt;no hierarchy&lt;/li&gt;
&lt;li&gt;more rows, more flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have &lt;code&gt;N&lt;/code&gt; columns, you get &lt;code&gt;2ⁿ&lt;/code&gt;grouping levels.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;slicing data in multiple ways&lt;/li&gt;
&lt;li&gt;pivot-style reporting&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;If ROLLUP or CUBE ever feel confusing, it’s usually because you’re asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why didn’t SQL give me &lt;em&gt;this&lt;/em&gt; total?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the answer is almost always that column wasn’t rolled up or you actually wanted CUBE&lt;/p&gt;

&lt;p&gt;Feeling stuck here is normal. So know, you're not the only one.&lt;/p&gt;




&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Data &amp;amp; Database Engineering Enthusiast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m documenting my journey learning SQL, databases, and the systems behind real-world data platforms  honestly, slowly, and in public.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Old Oracle Joins vs ANSI JOINs: Why I Chose One and Stuck With It</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Fri, 23 Jan 2026 14:31:00 +0000</pubDate>
      <link>https://forem.com/mazinocodes/old-oracle-joins-vs-ansi-joins-why-i-chose-one-and-stuck-with-it-2goi</link>
      <guid>https://forem.com/mazinocodes/old-oracle-joins-vs-ansi-joins-why-i-chose-one-and-stuck-with-it-2goi</guid>
      <description>&lt;p&gt;When I first encountered &lt;strong&gt;Oracle’s old join syntax&lt;/strong&gt; using the &lt;code&gt;(+)&lt;/code&gt; operator, I was confused — not because joins were new to me, but because the &lt;em&gt;way they were written&lt;/em&gt; felt unintuitive compared to what I had already learned with ANSI SQL.&lt;/p&gt;

&lt;p&gt;So instead of treating this as “just another syntax,” I decided to properly compare &lt;strong&gt;old Oracle joins vs ANSI joins&lt;/strong&gt;, understand &lt;em&gt;why both exist&lt;/em&gt;, and then make a deliberate choice about which one I would personally stick with.&lt;/p&gt;

&lt;p&gt;This post is that comparison.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Ways to Write Joins in Oracle
&lt;/h2&gt;

&lt;p&gt;Oracle supports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Old-style Oracle joins&lt;/strong&gt; using the &lt;code&gt;(+)&lt;/code&gt; operator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ANSI-standard joins&lt;/strong&gt; using &lt;code&gt;LEFT JOIN&lt;/code&gt;, &lt;code&gt;RIGHT JOIN&lt;/code&gt;, &lt;code&gt;FULL OUTER JOIN&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both work. Both return results. But they &lt;em&gt;feel&lt;/em&gt; very different when you read and maintain them.&lt;/p&gt;




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

&lt;p&gt;Let’s say we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clients&lt;/code&gt; → people who place orders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;orders&lt;/code&gt; → what those clients buy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some clients may &lt;strong&gt;not&lt;/strong&gt; have placed any orders yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Old Oracle LEFT OUTER JOIN &lt;code&gt;(+)&lt;/code&gt;
&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;client_id&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clients&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;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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s happening here?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;(+)&lt;/code&gt; tells Oracle to &lt;em&gt;preserve rows&lt;/em&gt; from &lt;code&gt;clients&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;orders&lt;/code&gt; is optional&lt;/li&gt;
&lt;li&gt;The join logic is mixed into the &lt;code&gt;WHERE&lt;/code&gt; clause&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It’s not visually obvious which table is optional&lt;/li&gt;
&lt;li&gt;Adding filters can silently turn your outer join into an inner join&lt;/li&gt;
&lt;li&gt;This syntax &lt;strong&gt;only works in Oracle&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ANSI LEFT JOIN (The Same Query)
&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;client_id&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this reads better
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Join logic is separated from filters&lt;/li&gt;
&lt;li&gt;You can instantly see which table is optional&lt;/li&gt;
&lt;li&gt;The query reads almost like English&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Give me all clients, and their orders if they exist.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  FULL OUTER JOIN: The Big Difference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ANSI syntax
&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear. Explicit. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Old Oracle syntax
&lt;/h3&gt;

&lt;p&gt;Oracle doesn’t support &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; with &lt;code&gt;(+)&lt;/code&gt;.&lt;br&gt;
You must simulate it using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;LEFT JOIN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;RIGHT JOIN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and a &lt;code&gt;UNION&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This quickly becomes verbose and error-prone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Portability Matters
&lt;/h2&gt;

&lt;p&gt;ANSI joins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work in &lt;strong&gt;PostgreSQL, MySQL, SQL Server, Oracle, BigQuery&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Are easier to transfer between jobs and databases&lt;/li&gt;
&lt;li&gt;Are what most modern documentation and examples use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Old Oracle joins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock you into Oracle&lt;/li&gt;
&lt;li&gt;Appear mostly in legacy codebases&lt;/li&gt;
&lt;li&gt;Are harder for non-Oracle developers to read&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I Personally Chose ANSI JOINs
&lt;/h2&gt;

&lt;p&gt;After using both, I made a deliberate choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ANSI joins are &lt;strong&gt;universal&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They are easier to &lt;strong&gt;read, write, and reason about&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They scale better as queries grow&lt;/li&gt;
&lt;li&gt;They reduce accidental bugs caused by misplaced filters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when working with Oracle, I stick to ANSI syntax unless I’m forced to read legacy code.&lt;/p&gt;

&lt;p&gt;Consistency matters more than preference — and ANSI gives me consistency across databases.&lt;/p&gt;




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

&lt;p&gt;Knowing the old Oracle syntax is useful — especially when dealing with legacy systems.&lt;br&gt;
But for &lt;strong&gt;new queries&lt;/strong&gt;, &lt;strong&gt;learning projects&lt;/strong&gt;, and &lt;strong&gt;cross-database skills&lt;/strong&gt;, ANSI joins are the clear winner &lt;em&gt;for me&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Learning SQL isn’t just about knowing what works — it’s about choosing what scales with you.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Jessica Aki&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Data &amp;amp; Database Engineering Enthusiast&lt;/em&gt;&lt;/p&gt;

</description>
      <category>oracle</category>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How SQL JOINs Finally Clicked for Me (and How They Can for You Too)</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Thu, 22 Jan 2026 18:51:24 +0000</pubDate>
      <link>https://forem.com/mazinocodes/how-sql-joins-finally-clicked-for-me-and-how-they-can-for-you-too-4502</link>
      <guid>https://forem.com/mazinocodes/how-sql-joins-finally-clicked-for-me-and-how-they-can-for-you-too-4502</guid>
      <description>&lt;p&gt;Today's post is not a “perfect” explanation of SQL joins. It’s the explanation I wish I had when joins first confused and kept me in place for some time.&lt;/p&gt;

&lt;p&gt;And I’m writing this from the perspective of someone who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understood tables and primary keys&lt;/li&gt;
&lt;li&gt;Had an understanding of foreign keys&lt;/li&gt;
&lt;li&gt;Knew joins were important&lt;/li&gt;
&lt;li&gt;Understood Normalization and Denormalization&lt;/li&gt;
&lt;li&gt;But absolutely did not understand what was actually happening during a join&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If SQL joins have ever made you feel lost, overwhelmed this is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Big Block That Held Me Back from Understanding
&lt;/h2&gt;

&lt;p&gt;At first, joins felt like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Some magic SQL thing where tables are smashed together somehow.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Truly that's how it felt at the  time. I understood the idea of what it was supposed to do. Merge tables together based on a common column. &lt;/p&gt;

&lt;p&gt;I  assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The database “knew” how to join tables&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JOIN&lt;/code&gt; meant “merge tables”&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LEFT&lt;/code&gt; and &lt;code&gt;RIGHT&lt;/code&gt; were arbitrary keywords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that was wrong. All assumptions from simply just seeing the syntax and expecting my brain to follow along.&lt;/p&gt;

&lt;p&gt;The real problem?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I didn’t understand that joins are just comparisons between rows.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It wasn't magic. It was just row-by-row matching with rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: Two Simple Tables
&lt;/h2&gt;

&lt;p&gt;Let's take a very a very simple and normal business scenario: &lt;strong&gt;customers placing orders&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customers
&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;customer_name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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



&lt;div class="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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;product&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Headphones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this table we have:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Customer &lt;strong&gt;4&lt;/strong&gt; does not exist and Customer &lt;strong&gt;3&lt;/strong&gt; has no orders&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CROSS JOIN — Why This One Feels Wrong (and That’s Normal)
&lt;/h2&gt;

&lt;p&gt;Cross joins confused me at first but after actually applying it and seeing what it does. I finally understood that it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pairs every row in the first table with every row in the second. &lt;br&gt;
This is something called a &lt;em&gt;Cartesian Product&lt;/em&gt;.&lt;br&gt;
For example:&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;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;cross&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query produces 12 rows: How?&lt;/p&gt;

&lt;p&gt;3 customers × 4 orders = &lt;strong&gt;12 rows&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  INNER JOIN — The Join That made Joins To Finally Make Sense
&lt;/h2&gt;

&lt;p&gt;This was my biggest breakthrough.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rule
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Only keep rows where the join condition matches in both tables&lt;/strong&gt;&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;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;inner&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What happens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Alice → has an order → keep&lt;/li&gt;
&lt;li&gt;Bob → has orders → keep&lt;/li&gt;
&lt;li&gt;Charlie → no orders → gone&lt;/li&gt;
&lt;li&gt;Order with customer_id 4 → gone&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;INNER JOIN is &lt;strong&gt;strict&lt;/strong&gt;. So if there's no match, the row is completely dropped.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  LEFT JOIN
&lt;/h2&gt;

&lt;p&gt;LEFT JOIN clicked faster for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rule
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep &lt;strong&gt;everything&lt;/strong&gt; from the left table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case it's the customer table that's on the left. So we keep the left table in place and match the customer_id of both tables and then join them into one table. So in this join table, any order that doesn't match the left table is filled in with Nulls&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;customers&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;All customers appear&lt;/li&gt;
&lt;li&gt;Orders appear if they exist&lt;/li&gt;
&lt;li&gt;No order? → &lt;code&gt;NULL&lt;/code&gt; values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Charlie finally shows up — with NULLs.&lt;/p&gt;

&lt;p&gt;LEFT JOIN answers questions like: &lt;em&gt;“Show me all customers, even those who haven’t ordered yet.”&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  RIGHT JOIN
&lt;/h2&gt;

&lt;p&gt;RIGHT JOIN is just LEFT JOIN from the other direction. So it keeps the right table in place, matches based on the join condition and joins the table and any row on the left that doesn't match the condition is filled out with Nulls.&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;orders&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;on&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  FULL OUTER JOIN
&lt;/h2&gt;

&lt;p&gt;This is where both Sides Matter. FULL JOIN keeps all matching rows and all unmatched rows from both tables.&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;customers&lt;/span&gt;
&lt;span class="k"&gt;full&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be useful for data reconciliation or finding missing or orphaned records&lt;/p&gt;




&lt;h2&gt;
  
  
  JOIN Conditions Are Not Always Equality
&lt;/h2&gt;

&lt;p&gt;This surprised me as most of the examples I'd seen were equalities. Rather I discovered you can join on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Equal values&lt;/li&gt;
&lt;li&gt;Ranges&lt;/li&gt;
&lt;li&gt;Expressions&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="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;c&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;length&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;customer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Everything came together but there was a Mistake I Discovered I Was Making
&lt;/h2&gt;

&lt;p&gt;So I kept practicing and learning and I started thinking of filtering results from the join tables and well I discovered how a lack of understanding of how SQL  works could cause silent bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mistake
&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;c&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Laptop'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns your LEFT JOIN into an INNER JOIN.&lt;/p&gt;

&lt;p&gt;This is because the &lt;code&gt;WHERE&lt;/code&gt; runs after the join and the rows with NULL orders are filtered out. But that was not what I was trying to query from my data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix I learnt
&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;c&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;and&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Laptop'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unmatched customers are preserved.&lt;br&gt;
Adding the filtering to the join ensures that I get the left join needed but I also filter for the data need.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Conclusions
&lt;/h2&gt;

&lt;p&gt;Joins are not scary. I kept pushing my learning of SQL at joins because most of the tutorials I used weren't explaining it in a way I understood. But after getting over my fears, I'm  so happy that I was able to fully understand this concept so well.&lt;/p&gt;

&lt;p&gt;They are simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Row-by-row comparisons with rules about what to keep.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Quick Summary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CROSS JOIN → everything × everything&lt;/li&gt;
&lt;li&gt;INNER JOIN → only matches&lt;/li&gt;
&lt;li&gt;LEFT JOIN → keep left, fill NULLs&lt;/li&gt;
&lt;li&gt;RIGHT JOIN → keep right, fill nulls&lt;/li&gt;
&lt;li&gt;FULL JOIN → keep everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If joins ever made you feel lost, that’s normal. It doesn’t mean you’re bad at SQL. It means you’re finally learning how it actually works.&lt;/p&gt;




&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast&lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms. I would love you to join me on this.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>postgres</category>
      <category>beginners</category>
    </item>
    <item>
      <title>SQL Dates Finally Made Sense: NOW, AGE, EXTRACT, and Why Time Is Weird</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Wed, 21 Jan 2026 09:20:00 +0000</pubDate>
      <link>https://forem.com/mazinocodes/sql-dates-finally-made-sense-now-age-extract-and-why-time-is-weird-39on</link>
      <guid>https://forem.com/mazinocodes/sql-dates-finally-made-sense-now-age-extract-and-why-time-is-weird-39on</guid>
      <description>&lt;p&gt;Up until now, SQL felt very… solid.&lt;/p&gt;

&lt;p&gt;Numbers are numbers. Strings are strings. GROUP BY either works or it doesn’t.&lt;/p&gt;

&lt;p&gt;Dates?&lt;br&gt;
Dates humbled me.&lt;/p&gt;

&lt;p&gt;Today I spent time learning how SQL handles time and I finally understand why people say dates are one of the hardest parts of databases.&lt;/p&gt;

&lt;p&gt;This post is me documenting what clicked, what confused me, and what I’ll definitely need to revisit.&lt;/p&gt;


&lt;h2&gt;
  
  
  The First Surprise: Dates Are Not Just Numbers or strings
&lt;/h2&gt;

&lt;p&gt;At first, I thought dates were just formatted numbers .&lt;/p&gt;

&lt;p&gt;Turns out they’re not.&lt;br&gt;
Dates come with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;time zones&lt;/li&gt;
&lt;li&gt;hidden time components&lt;/li&gt;
&lt;li&gt;built-in functions that behave differently depending on context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I accepted that, things started to make more sense.&lt;/p&gt;


&lt;h2&gt;
  
  
  NOW() — The Current Moment
&lt;/h2&gt;

&lt;p&gt;The simplest place to start was &lt;code&gt;NOW()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the current timestamp**, that is, it's not just the date, but the exact moment the query runs.&lt;/p&gt;

&lt;p&gt;What clicked for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NOW()&lt;/code&gt; includes date &lt;strong&gt;and&lt;/strong&gt; time, including the time zone&lt;/li&gt;
&lt;li&gt;It’s commonly used for logs, audits, and tracking events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seeing a real timestamp come back made SQL feel a lot more like a backend system, not just a learning exercise.&lt;/p&gt;




&lt;h2&gt;
  
  
  AGE() — Time Between Two Dates
&lt;/h2&gt;

&lt;p&gt;This function confused me at first, mostly because of the example usage i saw.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_of_birth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example was where a users date of birth was passed into the function and the age was produced, but what I didn't understand at the time was that it's more flexible than just getting the difference between now and that day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2025-10-10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-10'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;-- The amount of time I spent in Univeristy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What &lt;code&gt;AGE()&lt;/code&gt; actually does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Returns the &lt;strong&gt;time difference&lt;/strong&gt; between two timestamps&lt;/li&gt;
&lt;li&gt;The result is an &lt;strong&gt;interval&lt;/strong&gt;, not a number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;Instead of giving me days or seconds, it returns something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;4 years 9 mons &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This made sense for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How old an account is&lt;/li&gt;
&lt;li&gt;How long ago something happened&lt;/li&gt;
&lt;li&gt;Time since last activity&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  EXTRACT() — Pulling Out Pieces of Time
&lt;/h2&gt;

&lt;p&gt;This is where things started clicking properly.&lt;/p&gt;

&lt;p&gt;Sometimes you don’t want the full date.&lt;br&gt;
You just want &lt;em&gt;part&lt;/em&gt; of it.&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;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt;  &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2025-10-10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-10'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;--4&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt;  &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2025-10-10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-10'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;--9 &lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt;  &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2025-10-10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-10'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;--0&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOW&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt;  &lt;span class="s1"&gt;'2025-10-10'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;--5 (Friday)&lt;/span&gt;
&lt;span class="c1"&gt;-- DOW is Day of the week, it returns a value between 0 and 6 to represent the day of the week with Sunday being 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What finally made sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EXTRACT&lt;/code&gt; doesn’t change the date&lt;/li&gt;
&lt;li&gt;It reads a specific part of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grouping by year or month&lt;/li&gt;
&lt;li&gt;filtering specific periods&lt;/li&gt;
&lt;li&gt;reporting trends over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I saw it as “reading metadata from time”, not transforming it, it felt less magical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Intervals — Time as a Data Type
&lt;/h2&gt;

&lt;p&gt;This part surprised me.&lt;/p&gt;

&lt;p&gt;Time differences aren’t just numbers, rather  SQL treats them as &lt;strong&gt;intervals&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&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;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 month'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means SQL understands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;days&lt;/li&gt;
&lt;li&gt;months&lt;/li&gt;
&lt;li&gt;years&lt;/li&gt;
&lt;li&gt;hours, minutes, seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it handles calendar logic for you.&lt;/p&gt;

&lt;p&gt;No manual math. No guessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Still Feels Weird
&lt;/h2&gt;

&lt;p&gt;I’m being honest, some things still feel slippery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time zones (I know this will bite me later)&lt;/li&gt;
&lt;li&gt;Comparing dates vs timestamps&lt;/li&gt;
&lt;li&gt;Understanding when time gets truncated or rounded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But compared to yesterday, I’m no longer scared of date functions.&lt;/p&gt;

&lt;p&gt;That’s progress.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dates are usually taught quickly, like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s NOW(). Here’s EXTRACT(). Moving on.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as a beginner, this isn’t about syntax.&lt;br&gt;
It’s about understanding how databases &lt;em&gt;think about time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If dates confused you too,  you’re not behind.&lt;br&gt;
They confuse everyone.&lt;/p&gt;




&lt;p&gt;I’m learning SQL slowly, properly, and in public  and documenting both the clarity and the confusion.&lt;/p&gt;

&lt;p&gt;If you’re on the same path, you’re not alone. You can join me on this series and learn with me.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>postgres</category>
      <category>beginners</category>
    </item>
    <item>
      <title>SQL Nulls Finally Made Sense: COALESCE, NULLIF, and Safer Calculations</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Tue, 20 Jan 2026 16:52:44 +0000</pubDate>
      <link>https://forem.com/mazinocodes/sql-nulls-finally-made-sense-coalesce-nullif-and-safer-calculations-440e</link>
      <guid>https://forem.com/mazinocodes/sql-nulls-finally-made-sense-coalesce-nullif-and-safer-calculations-440e</guid>
      <description>&lt;p&gt;This was one of those learning days where SQL didn’t look hard… but kept quietly breaking my queries.&lt;/p&gt;

&lt;p&gt;Not with errors.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;NULLs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Today I focused on understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;COALESCE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NULLIF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;basic arithmetic operations&lt;/li&gt;
&lt;li&gt;and how functions like &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;ROUND&lt;/code&gt;, and &lt;code&gt;MOD&lt;/code&gt; behave when NULL is involved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  These are things I’ve seen mentioned casually in tutorials, but I didn’t really feel why they mattered until I started doing real calculations.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Problem I Kept Running Into
&lt;/h2&gt;

&lt;p&gt;I’d write what looked like a simple query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calculate totals&lt;/li&gt;
&lt;li&gt;divide one column by another&lt;/li&gt;
&lt;li&gt;show numeric results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And suddenly…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;values disappeared&lt;/li&gt;
&lt;li&gt;calculations returned &lt;code&gt;NULL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;or worse everything broke because of a divide-by-zero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when I realised:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL is extremely strict about missing or unsafe data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And honestly? That’s a good thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  COALESCE
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;COALESCE&lt;/code&gt; was the first function that really changed how I thought about NULLs.&lt;/p&gt;

&lt;p&gt;What it does is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return the &lt;strong&gt;first non-null value&lt;/strong&gt; in a list.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Email Unavailable'&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;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means in plain English:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;email&lt;/code&gt; exists → show it&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;email&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt; → replace it with &lt;code&gt;'Email Unavailable'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the first time SQL felt… practical.&lt;/p&gt;

&lt;p&gt;Instead of letting missing data break my output, I could &lt;strong&gt;control what users actually see&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That mental shift mattered.&lt;/p&gt;




&lt;h2&gt;
  
  
  NULLIF (The Function I Didn’t Understand at First)
&lt;/h2&gt;

&lt;p&gt;At first, &lt;code&gt;NULLIF&lt;/code&gt; felt pointless.&lt;/p&gt;

&lt;p&gt;Why would I &lt;em&gt;turn&lt;/em&gt; something into &lt;code&gt;NULL&lt;/code&gt; on purpose?&lt;/p&gt;

&lt;p&gt;Then I hit this problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dividing by zero.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SQL doesn’t allow it. And it shouldn’t.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;NULLIF&lt;/code&gt; finally made sense.&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="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NULLIF(0, 0)&lt;/code&gt; → returns &lt;code&gt;NULL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;10 / NULL&lt;/code&gt; → returns &lt;code&gt;NULL&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No crash. No error. Just a safe result.&lt;/p&gt;

&lt;p&gt;Now combine it with &lt;code&gt;COALESCE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try the calculation&lt;/li&gt;
&lt;li&gt;If it fails (returns NULL)&lt;/li&gt;
&lt;li&gt;Fallback to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when it clicked.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;NULLIF&lt;/code&gt; prevents bad math. &lt;code&gt;COALESCE&lt;/code&gt; cleans up the result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They work &lt;strong&gt;together&lt;/strong&gt;, not separately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Arithmetic Operations
&lt;/h2&gt;

&lt;p&gt;I also revisited basic arithmetic in SQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt; addition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt; subtraction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt; multiplication&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; division&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%&lt;/code&gt; or &lt;code&gt;MOD()&lt;/code&gt; for remainders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!&lt;/code&gt; factorial &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What stood out wasn’t the syntax — it was the behaviour.&lt;/p&gt;

&lt;p&gt;Any arithmetic with &lt;code&gt;NULL&lt;/code&gt; results in &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will quietly return &lt;code&gt;NULL&lt;/code&gt; if either column is missing.&lt;/p&gt;

&lt;p&gt;Which again reinforces why &lt;code&gt;COALESCE&lt;/code&gt; matters so much.&lt;/p&gt;




&lt;h2&gt;
  
  
  SUM, ROUND, and MOD
&lt;/h2&gt;

&lt;p&gt;Once I handled NULLs properly, aggregate and numeric functions finally behaved the way I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  SUM
&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="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works but only if you understand how NULLs are treated.&lt;/p&gt;

&lt;h3&gt;
  
  
  ROUND
&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="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&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;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was satisfying. Seeing clean, rounded values made the output feel presentation-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  MOD
&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="k"&gt;MOD&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;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Simple, but useful when you actually need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Today's Takeaway
&lt;/h2&gt;

&lt;p&gt;The big takeaway from today wasn’t memorising functions.&lt;/p&gt;

&lt;p&gt;It was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL assumes missing data is dangerous unless you explicitly say otherwise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COALESCE&lt;/code&gt; lets you decide what “missing” means&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NULLIF&lt;/code&gt; lets you avoid invalid operations&lt;/li&gt;
&lt;li&gt;arithmetic becomes predictable once NULLs are handled intentionally&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Still Feels Weird
&lt;/h2&gt;

&lt;p&gt;I’m still adjusting to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remembering that NULL isn’t zero&lt;/li&gt;
&lt;li&gt;spotting where calculations might silently fail&lt;/li&gt;
&lt;li&gt;knowing when to fix data vs fix the query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But compared to before? Huge progress.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;NULL-handling is usually glossed over in beginner tutorials.&lt;/p&gt;

&lt;p&gt;But in real databases?&lt;/p&gt;

&lt;p&gt;It’s everywhere.&lt;/p&gt;

&lt;p&gt;If you’re learning SQL and your queries sometimes &lt;em&gt;technically work&lt;/em&gt; but return confusing results, this might be why.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Next up, I’m diving into &lt;strong&gt;dates and time&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DATE&lt;/code&gt; vs &lt;code&gt;TIMESTAMP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGE()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INTERVAL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXTRACT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re learning alongside me, feel free to follow the journey and check out the other posts in this series.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Querying &amp; Filtering in Oracle Databases: What Actually Clicked for Me As a Beginner</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Mon, 19 Jan 2026 22:59:00 +0000</pubDate>
      <link>https://forem.com/mazinocodes/querying-filtering-in-oracle-databases-what-actually-clicked-for-me-as-a-beginner-5di2</link>
      <guid>https://forem.com/mazinocodes/querying-filtering-in-oracle-databases-what-actually-clicked-for-me-as-a-beginner-5di2</guid>
      <description>&lt;p&gt;Today felt like one of those quiet but important SQL days. No new tables. No fancy joins. Just learning how to ask better questions of the data I already have.&lt;/p&gt;

&lt;p&gt;This lesson was all about querying and filtering rows basically learning how to tell the database exactly what I want back, and nothing more.&lt;/p&gt;

&lt;p&gt;I learnt this through a simple &lt;code&gt;toys&lt;/code&gt; table, which honestly helped a lot. Oracle courses teach in a weird but funny manner which allows you to learn and have fun while you do. They make very daunting topics look less intimidating by approaching them with easy to understand and relatable concepts.&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;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;toys&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;toy_name&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;colour&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;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;price&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;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;toys&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Sir Stripypants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="p"&gt;);&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;toys&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Miss Smelly_bottom'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&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="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;toys&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Cuteasaurus'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="p"&gt;);&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;toys&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Mr Bunnykins'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="p"&gt;);&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;toys&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Baby Turtle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&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;commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Selecting Rows (and Why &lt;code&gt;SELECT *&lt;/code&gt; Is a Trap)
&lt;/h2&gt;

&lt;p&gt;The very first thing was learning that &lt;code&gt;SELECT&lt;/code&gt; really has two jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FROM&lt;/strong&gt; → where the data lives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SELECT&lt;/strong&gt; → what columns I actually want back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, &lt;code&gt;SELECT * FROM toys;&lt;/code&gt; is  very convenient but only when your database is small. Imagine a bigger database with over 10000 rows. A select * isn't going to help you find Mr BunnyKins in there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;toys&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forces you to &lt;strong&gt;think about what you actually need&lt;/strong&gt;, and it also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sends less data over the network&lt;/li&gt;
&lt;li&gt;Breaks less when columns change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That alone already changed how I write queries. Be specific and effective.&lt;/p&gt;




&lt;h2&gt;
  
  
  Filtering Rows with &lt;code&gt;WHERE&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;So to start being more effective instead of getting everything in the table, you can start asking questions like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Only show me the red toys”&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;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;toys&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Give me just one specific row”&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;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;toys&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Sir Stripypants'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, but this is the foundation of almost every real query.&lt;/p&gt;




&lt;h2&gt;
  
  
  Combining Conditions: &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, and Confusion
&lt;/h2&gt;

&lt;p&gt;This part tripped me up more than I expected.&lt;/p&gt;

&lt;p&gt;At first glance, this feels logical:&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;toy_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Mr Bunnykins'&lt;/span&gt;
&lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Baby Turtle'&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the results weren’t what I expected.&lt;/p&gt;

&lt;p&gt;That’s when I understood that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AND runs before OR&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which means SQL doesn’t read conditions left to right the way my brain wants it to.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Use parentheses &lt;code&gt;()&lt;/code&gt;. Always.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Mr Bunnykins'&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Baby Turtle'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the query does exactly what it looks like it should do. This alone has saved me from future bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lists of Values with &lt;code&gt;IN&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of writing 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;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;
&lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;
&lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;
&lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yellow'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can write 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;colour&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yellow'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much cleaner. Much easier to read and very effective. This feels like one of those features you don’t appreciate until you &lt;em&gt;really&lt;/em&gt; need it. Imagine the 100,000 rows in the table and I want just a handful that meet some conditions. it's more effective to use the IN than to write multiple OR statements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ranges with &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, and &lt;code&gt;BETWEEN&lt;/code&gt;
&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;where&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important detail I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BETWEEN&lt;/code&gt; &lt;strong&gt;includes&lt;/strong&gt; both ends&lt;/li&gt;
&lt;li&gt;If you want strict boundaries, you must write them yourself
&lt;/li&gt;
&lt;/ul&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;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's the small details that make the big differences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wildcards and Pattern Matching (&lt;code&gt;LIKE&lt;/code&gt;)
&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;where&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'b%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finds anything starting with &lt;code&gt;b&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;toy_name&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%B%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finds toy names containing uppercase &lt;strong&gt;B&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So wildcards: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_&lt;/code&gt; matches &lt;strong&gt;exactly one character&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%&lt;/code&gt; matches &lt;strong&gt;zero or more characters&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you actually want to search for &lt;code&gt;_&lt;/code&gt; or &lt;code&gt;%&lt;/code&gt; themselves… you need &lt;code&gt;ESCAPE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That’s one of those things you won’t know until it breaks something.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;NULL&lt;/code&gt; Is… Weird (But Makes Sense)
&lt;/h2&gt;

&lt;p&gt;This line returning &lt;strong&gt;nothing&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;NULL&lt;/code&gt; isn’t a value — it’s &lt;em&gt;unknown&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you must write:&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;price&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the opposite:&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;price&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Negation: Saying “NOT This”
&lt;/h2&gt;

&lt;p&gt;You can flip most conditions using &lt;code&gt;NOT&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or by using &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But again — &lt;strong&gt;NULL is special&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To exclude nulls, you &lt;em&gt;must&lt;/em&gt; use:&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;colour&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s no shortcut here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This lesson didn’t feel flashy but it felt important. &lt;/p&gt;

&lt;p&gt;Everything else in SQL builds on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aggregations&lt;/li&gt;
&lt;li&gt;Joins&lt;/li&gt;
&lt;li&gt;Subqueries&lt;/li&gt;
&lt;li&gt;Real-world analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can’t filter data confidently, everything else feels fragile.&lt;/p&gt;

&lt;p&gt;I’m learning to slow down, be explicit, and write queries that are readable and effective.&lt;/p&gt;

&lt;p&gt;If you’re also learning SQL and sometimes feel silly getting tripped up by &lt;code&gt;WHERE&lt;/code&gt; clauses… you’re not alone.&lt;/p&gt;

&lt;p&gt;I’ll keep documenting this journey, the confusion, clarity, and all.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>oracle</category>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Table Design: Database Fundamentals to Know as a Beginner</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Mon, 19 Jan 2026 13:51:33 +0000</pubDate>
      <link>https://forem.com/mazinocodes/table-design-databases-for-developers-as-a-beginner-451c</link>
      <guid>https://forem.com/mazinocodes/table-design-databases-for-developers-as-a-beginner-451c</guid>
      <description>&lt;p&gt;This post is part of my &lt;strong&gt;Learning Databases Beyond SQL&lt;/strong&gt; journey. I’m starting to realize that writing SQL queries is only half the story and &lt;em&gt;how tables are designed&lt;/em&gt; matters just as much.&lt;/p&gt;

&lt;p&gt;This is my beginner-friendly walkthrough of &lt;strong&gt;data modelling&lt;/strong&gt;: how you go from vague requirements to actual database tables.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Data modelling is the process of taking real-world requirements and translating them into database tables.&lt;/p&gt;

&lt;p&gt;I quickly learned this is a huge topic and easily a full course on its own. This post isn’t meant to make anyone an expert. It’s just a clear, high-level view of the steps involved and why they matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Overview: The Big Picture
&lt;/h2&gt;

&lt;p&gt;Designing a database usually follows these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture requirements&lt;/li&gt;
&lt;li&gt;Build a conceptual model&lt;/li&gt;
&lt;li&gt;Design a logical model&lt;/li&gt;
&lt;li&gt;Create the physical model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing that stood out to me: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this is not a one-pass process&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is iterative and you constantly go back and adjust things as you understand the problem better, ideally while checking in with the people who will actually use the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Capturing Requirements
&lt;/h2&gt;

&lt;p&gt;This step is about answering one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What information does the system need to store?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You get this by talking to users , that is the customers, staff, or anyone who will interact with the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Hospital Appointment System
&lt;/h3&gt;

&lt;p&gt;Imagine we’re building a system that lets patients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View available appointment times&lt;/li&gt;
&lt;li&gt;Book an appointment&lt;/li&gt;
&lt;li&gt;View upcoming appointments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To support this, we need to store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Appointment date and time&lt;/li&gt;
&lt;li&gt;Clinic location&lt;/li&gt;
&lt;li&gt;Patient name&lt;/li&gt;
&lt;li&gt;Consultant name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;functional requirements&lt;/strong&gt;, these describe what the system must do.&lt;/p&gt;

&lt;p&gt;There are also &lt;strong&gt;non-functional requirements&lt;/strong&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages must load in under two seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These don’t describe &lt;em&gt;features&lt;/em&gt;, but they still affect how the database is designed. They describe how the system should behave while performing its functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Conceptual Model
&lt;/h2&gt;

&lt;p&gt;The conceptual model is a &lt;strong&gt;high-level view&lt;/strong&gt; of the data. At this stage, we only care about the &lt;em&gt;main entities&lt;/em&gt; , that is the real-world things the system tracks.&lt;/p&gt;

&lt;p&gt;For the hospital system, those entities are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Patient&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consultant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Appointment&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No columns yet. Just identifying what exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Logical Model
&lt;/h2&gt;

&lt;p&gt;This is where things start to feel more “database-like”.&lt;/p&gt;

&lt;p&gt;The logical model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines the attributes of each entity&lt;/li&gt;
&lt;li&gt;Shows relationships between entities&lt;/li&gt;
&lt;li&gt;Is often drawn as an &lt;strong&gt;Entity Relationship Diagram (ERD)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  First pass
&lt;/h3&gt;

&lt;p&gt;We might start with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patient&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consultant&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Appointment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;date &amp;amp; time&lt;/li&gt;
&lt;li&gt;clinic name &amp;amp; address&lt;/li&gt;
&lt;li&gt;patient&lt;/li&gt;
&lt;li&gt;consultant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Spotting problems (normalization begins)
&lt;/h3&gt;

&lt;p&gt;This is where I dove a bit deeper into the art and science of designing database and tables.&lt;/p&gt;

&lt;p&gt;Every appointment at the same clinic will have the same address. Storing the address in every appointment row means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicated data&lt;/li&gt;
&lt;li&gt;higher chance of errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead, we introduce a new entity:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clinic&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clinic name&lt;/li&gt;
&lt;li&gt;address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now &lt;strong&gt;Appointment&lt;/strong&gt; just references the clinic.&lt;/p&gt;

&lt;p&gt;This process of removing duplicated data is called &lt;strong&gt;normalization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At this stage, we also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose data types&lt;/li&gt;
&lt;li&gt;define constraints (e.g. appointment dates must be in the future)&lt;/li&gt;
&lt;li&gt;identify primary keys&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Normalization (At a High Level)
&lt;/h2&gt;

&lt;p&gt;Normalization  in simple terms means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Store each fact once, and only once.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Normalization means storing each real-world fact in one place and linking everything else to it and there are different &lt;strong&gt;normal forms&lt;/strong&gt; (1NF, 2NF, 3NF, etc.). Each one has rules that reduce redundancy and anomalies.&lt;/p&gt;

&lt;p&gt;A full deep dive is out of scope here, but the key takeaway for me was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalization improves data quality&lt;/li&gt;
&lt;li&gt;it makes systems easier to maintain&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Physical Model
&lt;/h2&gt;

&lt;p&gt;The physical model is where the design turns into &lt;strong&gt;actual SQL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CREATE TABLE&lt;/code&gt; statements&lt;/li&gt;
&lt;li&gt;data types&lt;/li&gt;
&lt;li&gt;performance decisions (indexes, partitions, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example tables might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;consultants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;consultant_id&lt;/span&gt;   &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;consultant_name&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="p"&gt;);&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;patients&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;patient_id&lt;/span&gt;   &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;patient_name&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="p"&gt;);&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;clinics&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;clinic_name&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;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;address&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;1000&lt;/span&gt;&lt;span class="p"&gt;)&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;table&lt;/span&gt; &lt;span class="n"&gt;appointments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;appointment_id&lt;/span&gt;       &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;appointment_datetime&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;clinic_name&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;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;consultant_id&lt;/span&gt;        &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;patient_id&lt;/span&gt;           &lt;span class="nb"&gt;integer&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where non-functional requirements (like performance) really start to matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Supertypes and Subtypes
&lt;/h2&gt;

&lt;p&gt;At this point, something interesting shows up.&lt;/p&gt;

&lt;p&gt;Both &lt;strong&gt;patients&lt;/strong&gt; and &lt;strong&gt;consultants&lt;/strong&gt; are people — and people have names.&lt;/p&gt;

&lt;p&gt;If someone can be both a patient &lt;em&gt;and&lt;/em&gt; a consultant, storing their name in two tables can cause inconsistencies and makes querying the data a bit more complex. Imagine you wanted to edit the name of a patient who is also a doctor, you'd have to edit both tables and ensure that there are no inconsistencies. &lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing a supertype
&lt;/h3&gt;

&lt;p&gt;So we can create a &lt;strong&gt;people&lt;/strong&gt; table and generalize the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;person_id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;full_name&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;people&lt;/code&gt; is the &lt;strong&gt;supertype&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;patients&lt;/code&gt; and &lt;code&gt;consultants&lt;/code&gt; are &lt;strong&gt;subtypes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subtypes store only what’s specific to that role.&lt;/p&gt;

&lt;p&gt;This structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avoids duplication&lt;/li&gt;
&lt;li&gt;keeps shared data consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you &lt;em&gt;need&lt;/em&gt; supertypes depends on your requirements and that decision often changes over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Relational vs. Document Storage
&lt;/h2&gt;

&lt;p&gt;It can be tempting to skip all of this and store everything as a single JSON document. These formats for me are more familiar due to my previous experiences.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appointmentDatetime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018-09-01 10:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PHYSIO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1 Hospital Way"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consultant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doctor Awesome"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Miss Sick"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks convenient but it has downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;harder to query&lt;/li&gt;
&lt;li&gt;duplicated data&lt;/li&gt;
&lt;li&gt;inconsistent values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, these shortcuts usually create more problems than they solve.&lt;/p&gt;

&lt;p&gt;Relational design takes longer upfront, but it tends to produce systems that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easier to query&lt;/li&gt;
&lt;li&gt;easier to maintain&lt;/li&gt;
&lt;li&gt;more flexible long-term&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;What stood out to me most is that &lt;strong&gt;good database design is about thinking, not syntax&lt;/strong&gt;. SQL is just the final expression of decisions made much earlier.&lt;/p&gt;

&lt;p&gt;I’m still very early in learning this, but even this high-level understanding already changes how I think about tables.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Columns &amp; Data Types in Oracle: What Actually Stuck on Day 2 (As a Beginner)</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Sat, 17 Jan 2026 23:24:00 +0000</pubDate>
      <link>https://forem.com/mazinocodes/columns-data-types-in-oracle-what-actually-stuck-on-day-2-as-a-beginner-3k95</link>
      <guid>https://forem.com/mazinocodes/columns-data-types-in-oracle-what-actually-stuck-on-day-2-as-a-beginner-3k95</guid>
      <description>&lt;p&gt;Day 2 of learning Oracle SQL was less about writing fancy queries and more about &lt;em&gt;understanding what I’m actually storing in a database&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Columns and data types sound basic — and they are — but this was one of those topics where I realised I’d been memorising names without really thinking about &lt;strong&gt;why&lt;/strong&gt; they matter.&lt;/p&gt;

&lt;p&gt;This post is me documenting what finally clicked, what confused me, and what I think beginners should actually focus on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Columns Are Rules, Not Just Labels
&lt;/h2&gt;

&lt;p&gt;Before now, I thought of columns like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Just names for values in a table. Or features of data.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But in Oracle, columns are more like &lt;strong&gt;rules&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What kind of data is allowed here?&lt;/li&gt;
&lt;li&gt;How big can it be?&lt;/li&gt;
&lt;li&gt;Can it be empty?&lt;/li&gt;
&lt;li&gt;Will Oracle try to convert it automatically?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I started seeing columns as &lt;em&gt;constraints on reality&lt;/em&gt;, data types suddenly felt more important than before.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Practiced Today
&lt;/h2&gt;

&lt;p&gt;I spent most of the day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating tables manually&lt;/li&gt;
&lt;li&gt;Choosing data types on purpose&lt;/li&gt;
&lt;li&gt;Seeing what breaks when I choose the wrong one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple things, but very revealing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Oracle Data Types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  VARCHAR2
&lt;/h3&gt;

&lt;p&gt;This is the one I’ll probably use the most.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;name&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What clicked for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VARCHAR2&lt;/code&gt; is &lt;strong&gt;variable-length text&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;50&lt;/code&gt; means &lt;em&gt;maximum&lt;/em&gt;, not fixed size&lt;/li&gt;
&lt;li&gt;Oracle doesn’t waste space if the value is shorter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So emails, names, cities are all perfect for &lt;code&gt;VARCHAR2&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  NUMBER
&lt;/h3&gt;

&lt;p&gt;At first, I assumed numbers had to be very specific.&lt;/p&gt;

&lt;p&gt;But Oracle surprised me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;
&lt;span class="n"&gt;age&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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;balance&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What stuck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NUMBER&lt;/code&gt; alone is very flexible&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;NUMBER(10,2)&lt;/code&gt; means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 total digits (Significant figures)&lt;/li&gt;
&lt;li&gt;2 after the decimal (Decimal places)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This suddenly explained &lt;strong&gt;why money fields behave the way they do&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  DATE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;date_of_birth&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I expected it to store &lt;em&gt;just&lt;/em&gt; a date.&lt;/p&gt;

&lt;p&gt;But Oracle &lt;code&gt;DATE&lt;/code&gt; actually stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Time (hours, minutes, seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you don’t see the time, it’s there. Which explains why date comparisons can behave oddly if you’re not careful.&lt;/p&gt;




&lt;h3&gt;
  
  
  CHAR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="nb"&gt;CHAR&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I learnt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CHAR&lt;/code&gt; is &lt;strong&gt;fixed length&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Oracle pads unused space with blanks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s useful for things like flags (&lt;code&gt;Y/N&lt;/code&gt;), but for most text, &lt;code&gt;VARCHAR2&lt;/code&gt; just makes more sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating a Table (Putting It Together)
&lt;/h2&gt;

&lt;p&gt;This was one of the tables I created while practicing:&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;user_id&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;email&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;age&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;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;date_joined&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seeing this helped me understand that Columns define &lt;em&gt;structure&lt;/em&gt; and  Data types define &lt;em&gt;behavior&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Still Feels Weird
&lt;/h2&gt;

&lt;p&gt;I’m still adjusting to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing the &lt;em&gt;right&lt;/em&gt; size for text fields&lt;/li&gt;
&lt;li&gt;Knowing when precision actually matters&lt;/li&gt;
&lt;li&gt;Remembering Oracle-specific names (&lt;code&gt;VARCHAR2&lt;/code&gt; instead of &lt;code&gt;VARCHAR&lt;/code&gt;, though both exist as datatypes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But compared to Day 1? This already feels more intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I’m Sharing This
&lt;/h2&gt;

&lt;p&gt;Columns and data types are usually taught as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here are the types. Memorise them.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as a beginner, this is about &lt;strong&gt;thinking ahead&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Future queries&lt;/li&gt;
&lt;li&gt;Data integrity&lt;/li&gt;
&lt;li&gt;Avoiding silent bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re new to Oracle and this topic felt boring — same here at first.&lt;br&gt;
But it’s one of those foundations that quietly makes everything else easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;I’m continuing my slow, honest approach to learning databases one concept at a time.&lt;/p&gt;

&lt;p&gt;If you’re also learning SQL from scratch, you’re not alone. I’m documenting every step, confusion included.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>oracle</category>
      <category>sql</category>
      <category>beginners</category>
      <category>database</category>
    </item>
    <item>
      <title>Learning Databases Beyond SQL: My First Day with Oracle Tables</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Fri, 16 Jan 2026 23:12:56 +0000</pubDate>
      <link>https://forem.com/mazinocodes/learning-databases-beyond-sql-my-first-day-with-oracle-tables-33ig</link>
      <guid>https://forem.com/mazinocodes/learning-databases-beyond-sql-my-first-day-with-oracle-tables-33ig</guid>
      <description>&lt;p&gt;Well today didn’t go the way I expected.&lt;/p&gt;

&lt;p&gt;I planned to continue my usual PostgreSQL + SQL practice, but I ended up starting a free Oracle course for developers instead. I only completed the first module, but it introduced me to something I hadn’t really thought about before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tables are not just tables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same word. Very different behaviors.&lt;/p&gt;

&lt;p&gt;This post is me documenting what I learned about table types in Oracle, what made sense immediately, and what feels advanced but interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tables Aren’t Always Just “A Place to Store Rows”
&lt;/h2&gt;

&lt;p&gt;Before today, my mental model of a table was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You create it → insert rows → query it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oracle kind of blew that up right in front of me.&lt;/p&gt;

&lt;p&gt;Apparently, how a table stores data physically matters a lot, and Oracle gives you several options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heap Tables
&lt;/h2&gt;

&lt;p&gt;The Default one everyone's familiar with. Heap tables are what I’m already used to. This basically means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rows go wherever there’s free space&lt;/li&gt;
&lt;li&gt;No guaranteed order&lt;/li&gt;
&lt;li&gt;General-purpose, most common type
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table toys_heap (
  toy_name varchar2(100)
) organization heap;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honestly, this feels like the “normal” table I’ve always used, just with a formal name now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index-Organized Tables
&lt;/h2&gt;

&lt;p&gt;Another familiar one but now having a formal name to identify by. Where the heap table store rows randomly, the index-organized tables on the other hand  have rows where everything is physically sorted by the primary key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table toys_iot (
  toy_id   integer primary key,
  toy_name varchar2(100)
) organization index;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;You always search by primary key&lt;/li&gt;
&lt;li&gt;You want fast lookups without jumping between table + index&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  External Tables
&lt;/h2&gt;

&lt;p&gt;This one surprised me as they're tables That Aren’t Really tables. External tables let you query files as if they were tables like CSVs (Comma Separated Values).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table toys_ext (
  toy_name varchar2(100)
) organization external (
  default directory tmp
  location ('toys.csv')
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important thing I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file lives on the database server&lt;/li&gt;
&lt;li&gt;You’re not importing data&lt;/li&gt;
&lt;li&gt;You’re reading it in place&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Temporary Tables
&lt;/h2&gt;

&lt;p&gt;For Session-Only Data. There are two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global Temporary Tables
Everyone can see the table but only you can see the rows you insert.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create global temporary table toys_gtt (
  toy_name varchar2(100)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What confused me at first for this type was that the table definition is permanent but the data is session-specific. So in the environment, if you query the list of user tables, it is displayed but it you terminate the session, the data goes with the session.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private Temporary Tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one feels almost secretive because even the table is private. Only your session can see the table while other sessions don’t even know it exists. Oracle doesn’t store it in the data dictionary&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create private temporary table ora$ptt_toys (
  toy_name varchar2(100)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Partitioned Tables
&lt;/h2&gt;

&lt;p&gt;Partitioning was the most “enterprise” concept today. The idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Split one big table into smaller ones based on a key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oracle supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Range (e.g. dates)&lt;/li&gt;
&lt;li&gt;List (specific values)&lt;/li&gt;
&lt;li&gt;Hash (even distribution)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table books_hash (
  toy_name varchar2(100)
) partition by hash ( toy_name ) partitions 4;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What clicked here was that queries can scan only the relevant partition and this matters a lot at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Clusters
&lt;/h2&gt;

&lt;p&gt;Storing Related Tables Together. This one felt advanced but fascinating. You can physically store rows from different tables together if they share a key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create cluster toy_cluster (
  toy_name varchar2(100)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table toys_cluster_tab (
  toy_name varchar2(100)
) cluster toy_cluster ( toy_name );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Took Away From Today's Session
&lt;/h2&gt;

&lt;p&gt;Today wasn’t about writing complex queries. It was about learning and realizing core concepts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Databases care deeply about how data is stored and tables are a design decision, not just syntax and SQL is only part of the story.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some of this felt familiar and Some of it felt intimidating but All of it felt useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Continue the Oracle database for developers modules. Keep grounding concepts in why they exist. Slowly connect this to PostgreSQL and data engineering concepts.&lt;/p&gt;

&lt;p&gt;I’m still very much a beginner, but days like this make the database world feel bigger and more interesting.&lt;/p&gt;

&lt;p&gt;If you’re also learning databases and everything feels like “too much” sometimes, you’re not alone. I’m figuring it out one module at a time.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>oracle</category>
      <category>beginners</category>
      <category>database</category>
    </item>
    <item>
      <title>SQL Aggregations Finally Made Sense: GROUP BY, HAVING, MIN, MAX, AVG</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Tue, 13 Jan 2026 23:02:48 +0000</pubDate>
      <link>https://forem.com/mazinocodes/sql-aggregations-finally-made-sense-group-by-having-min-max-avg-2n44</link>
      <guid>https://forem.com/mazinocodes/sql-aggregations-finally-made-sense-group-by-having-min-max-avg-2n44</guid>
      <description>&lt;p&gt;Today was one of those SQL days where things look simple… until you actually write the query.&lt;/p&gt;

&lt;p&gt;I focused on GROUP BY, HAVING, and the basic aggregate functions: MIN, MAX, and AVG. These are things I’ve heard about for a long time, but I realised I didn’t truly understand them until I tried using them with real questions.&lt;/p&gt;

&lt;p&gt;This post is me documenting what finally made sense, and what confused me at first.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Shift: From Rows to Groups
&lt;/h2&gt;

&lt;p&gt;Up until now, most of my SQL queries felt like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Show me rows that match X condition”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But GROUP BY changes the game. Instead of thinking about rows, you start thinking about groups of rows. That was the first uncomfortable part. SO let me show you what I did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Practiced Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I worked with queries that asked questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many users per country?&lt;/li&gt;
&lt;li&gt;What’s the average salary per department?&lt;/li&gt;
&lt;li&gt;Which department has the highest average score?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these queries required grouping. Not a simple get rows that fulfil this conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  GROUP BY (What Finally Clicked)
&lt;/h2&gt;

&lt;p&gt;At first, I kept getting errors like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;column must appear in the GROUP BY clause or be used in an aggregate function&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That error message annoyed me… until I started thinking more and trying to understand the message and task better.&lt;/p&gt;

&lt;p&gt;What helped me:&lt;/p&gt;

&lt;p&gt;If a column is not inside an aggregate function, it must be in the GROUP BY.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT country, COUNT(*) 
FROM users
GROUP BY country;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;country&lt;/code&gt; → used for grouping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COUNT(*)&lt;/code&gt; → summarizes each group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But this does NOT work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT country, email
FROM users
GROUP BY country;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because now SQL is like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Which email do you want me to pick for each country??”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was my first real “okay… fair enough” moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  MIN, MAX, and AVG (Straightforward but Powerful)
&lt;/h2&gt;

&lt;p&gt;Once grouping made sense, these felt more natural.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;MIN&lt;/code&gt;&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 department, MIN(salary)
FROM employees
GROUP BY department;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;→ lowest salary per department&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;MAX&lt;/code&gt;&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 department, MAX(salary)
FROM employees
GROUP BY department;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;→ highest salary per department&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AVG&lt;/code&gt;&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 department, AVG(salary)
FROM employees
GROUP BY department;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;→ average salary per department&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing fancy but seeing real numbers come out per group made SQL feel more real-world.&lt;/p&gt;




&lt;h2&gt;
  
  
  HAVING (The Part I Confused with WHERE)
&lt;/h2&gt;

&lt;p&gt;This is where I stumbled a bit.&lt;/p&gt;

&lt;p&gt;At first, I kept writing queries like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) &amp;gt; 50000
GROUP BY department;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And SQL basically said nope. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Difference That Finally Stuck:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WHERE filters rows before grouping&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HAVING filters groups after grouping&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the correct version was supposed to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING AVG(salary) &amp;gt; 50000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I saw HAVING as “WHERE for grouped data”, it stopped feeling magical though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order of Execution
&lt;/h2&gt;

&lt;p&gt;I then learnt the order of execution of SQL queries and understanding  the order made everything clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; (filter rows)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GROUP BY&lt;/code&gt; (create groups)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HAVING&lt;/code&gt; (filter groups)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explains why HAVING exists, WHERE simply runs too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Still Feels Weird
&lt;/h2&gt;

&lt;p&gt;I’m still adjusting to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thinking in groups instead of rows&lt;/li&gt;
&lt;li&gt;Knowing when I actually need GROUP BY&lt;/li&gt;
&lt;li&gt;Reading grouped queries without mentally getting lost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But compared to yesterday? This is progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I’m Sharing This
&lt;/h2&gt;

&lt;p&gt;GROUP BY and HAVING are usually taught quickly, like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s the syntax, move on.”&lt;br&gt;
But as a beginner, this is a mental shift, not just syntax.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re also learning SQL and GROUP BY felt confusing at first, same here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;I’m learning SQL slowly, properly, and honestly and I’m documenting the process so other beginners don’t feel like they’re the only ones struggling.&lt;/p&gt;

&lt;p&gt;If you’re on the same path, feel free to follow along.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>beginners</category>
      <category>sql</category>
    </item>
    <item>
      <title>I Built a PostgreSQL User Analytics System — Here’s What I Learned About Real SQL</title>
      <dc:creator>Jessica Aki</dc:creator>
      <pubDate>Fri, 09 Jan 2026 23:24:34 +0000</pubDate>
      <link>https://forem.com/mazinocodes/i-built-a-postgresql-user-analytics-system-heres-what-i-learned-about-real-sql-11mm</link>
      <guid>https://forem.com/mazinocodes/i-built-a-postgresql-user-analytics-system-heres-what-i-learned-about-real-sql-11mm</guid>
      <description>&lt;p&gt;Today, I built a User Analytics Database in PostgreSQL. The goal was to simulate a real-world business scenario where we need to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many users registered in the last month?&lt;/li&gt;
&lt;li&gt;Which countries have the most users?&lt;/li&gt;
&lt;li&gt;How many users use Gmail, Yahoo, or corporate emails?&lt;/li&gt;
&lt;li&gt;Who are the youngest and oldest users?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project helped me combine database design, SQL query practice, and realistic dataset handling into one hands-on exercise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Mockaroo (to generate realistic user data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database Schema — The users Table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Column Name Data Type   Constraint
id BIGSERIAL PRIMARY KEY, NOT NULL
first_name VARCHAR(50)  NOT NULL
last_name VARCHAR(50)   
email VARCHAR(150)  UNIQUE
gender VARCHAR(10)  
country VARCHAR(50) 
date_of_birth DATE  
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single table already allowed me to answer dozens of meaningful business questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Queries I Practiced
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total Number of Users&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT COUNT(*) AS "Total Users" FROM users;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Top 10 Countries by Users&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT country, COUNT(*) AS "Total Users"
FROM users
GROUP BY country
ORDER BY COUNT(country) DESC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gender Distribution&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT gender, COUNT(gender) AS "Total Users"
FROM users
GROUP BY gender
ORDER BY gender;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users Registered in Last 30 Days&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM users
WHERE registered_at &amp;gt;= NOW() - INTERVAL '30 days';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oldest &amp;amp; Youngest Users&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Oldest
SELECT first_name, last_name, EXTRACT(YEAR FROM AGE(NOW(), date_of_birth)) AS age
FROM users
WHERE date_of_birth = (SELECT MIN(date_of_birth) FROM users);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Youngest
SELECT first_name, last_name, date_of_birth
FROM users
WHERE date_of_birth = (SELECT MAX(date_of_birth) FROM users);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email Provider Analysis&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT COUNT(email)
FROM users
WHERE email ILIKE '%gmail%' OR email ILIKE '%yahoo%' OR email ILIKE '%facebook%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users Born Between 1990 and 2000&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM users
WHERE date_of_birth BETWEEN DATE '1990-01-01' AND '2000-12-31';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Countries with More Than 50 Users&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT country, COUNT(*)
FROM users
GROUP BY country
HAVING COUNT(*) &amp;gt; 50;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most Recent 20 Registrations&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM users ORDER BY registered_at DESC LIMIT 20;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned This Week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Importing real datasets into PostgreSQL using \i and COPY.&lt;/li&gt;
&lt;li&gt;Executing queries in correct order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY.&lt;/li&gt;
&lt;li&gt;Using intervals and EXTRACT to calculate ages and filter by date ranges.&lt;/li&gt;
&lt;li&gt;Reinforcing constraints, primary keys, and uniqueness for data integrity.&lt;/li&gt;
&lt;li&gt;Understanding practical SQL for real business insights — not just exercises.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Screenshots / Proof
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Schema&lt;/strong&gt;&lt;br&gt;
\d users table schema&lt;br&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%2Fhw0p0kh9bi2cbfohie2j.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%2Fhw0p0kh9bi2cbfohie2j.png" alt=" " width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Total Users&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fpsuif0mvojc8ypqmupve.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%2Fpsuif0mvojc8ypqmupve.png" alt=" " width="680" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Total Users in the top 10 countries&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2F3x7wp8zfd50xv9n8ncpu.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%2F3x7wp8zfd50xv9n8ncpu.png" alt=" " width="723" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gender Distribution&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2Fuaay4f3d8fjzaevwy2x9.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%2Fuaay4f3d8fjzaevwy2x9.png" alt=" " width="687" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Users Registered in the last 30 days&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2Fsf12b27i8a7pslt6n7v7.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%2Fsf12b27i8a7pslt6n7v7.png" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Oldest User&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2F5l5twrnwog2f0o7kiy18.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%2F5l5twrnwog2f0o7kiy18.png" alt=" " width="626" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Youngest User&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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%2Fcddxztum60m8i6jtzxdi.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%2Fcddxztum60m8i6jtzxdi.png" alt=" " width="724" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Run This Project Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clone the &lt;a href="https://github.com/MazinoLabs/User-Analytics-Database" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open PostgreSQL and connect to your server.&lt;/li&gt;
&lt;li&gt;Run schema.sql to create the database and table.&lt;/li&gt;
&lt;li&gt;Run data_import.sql to load Mockaroo-generated users.&lt;/li&gt;
&lt;li&gt;Run queries.sql to execute analytics queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Explore your own analysis and see what insights you can generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;What surprised me most is how extendable this project is. Without changing much, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add an orders or transactions table&lt;/li&gt;
&lt;li&gt;Link it with a foreign key (user_id)&lt;/li&gt;
&lt;li&gt;Start answering questions like:

&lt;ul&gt;
&lt;li&gt;Which countries generate the most revenue?&lt;/li&gt;
&lt;li&gt;Do younger users register more frequently?&lt;/li&gt;
&lt;li&gt;How long after registration do users become active?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The foundation is already there.&lt;/p&gt;

&lt;p&gt;That’s when you realize:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even “simple” SQL projects can grow into real systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project isn’t advanced and that’s the point. It proves that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don’t need complex schemas to practice real SQL&lt;/li&gt;
&lt;li&gt;You don’t need fancy tools to get meaningful insights&lt;/li&gt;
&lt;li&gt;You can build something useful earlier than you think&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m continuing to build, extend, and document this system as I learn, one step at a time. I hope to you see you on this journey and I would love to hear your opinions.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;Jessica Aki&lt;/em&gt;&lt;br&gt;
Data and Database Engineering Enthusiast  &lt;/p&gt;

&lt;p&gt;I’m documenting my journey into data engineering learning SQL, databases, and the systems behind modern data platforms.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>beginners</category>
      <category>database</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
