<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Neha Christina</title>
    <description>The latest articles on DEV Community by Neha Christina (@neha_christina_1ac8651819).</description>
    <link>https://dev.to/neha_christina_1ac8651819</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3809390%2Fc2a6f0e9-4e87-436c-9536-9d1201ba2541.PNG</url>
      <title>DEV Community: Neha Christina</title>
      <link>https://dev.to/neha_christina_1ac8651819</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neha_christina_1ac8651819"/>
    <language>en</language>
    <item>
      <title>SQL Query Execution Order: Why Your Query Doesn't Run Top-to-Bottom</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:46:33 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/sql-query-execution-order-why-your-query-doesnt-run-top-to-bottom-34pk</link>
      <guid>https://dev.to/neha_christina_1ac8651819/sql-query-execution-order-why-your-query-doesnt-run-top-to-bottom-34pk</guid>
      <description>&lt;p&gt;If you've ever written a query like this and gotten a baffling error, this post is for you:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&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&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&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;customer_id&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 plaintext"&gt;&lt;code&gt;ERROR: column "total" does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your first reaction is probably "but I just defined &lt;code&gt;total&lt;/code&gt; right there in the SELECT." Totally reasonable reaction — and totally wrong assumption about how SQL works under the hood.&lt;/p&gt;

&lt;p&gt;SQL reads like English, top to bottom: &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;. But that's the order you &lt;strong&gt;write&lt;/strong&gt; it in, not the order the database &lt;strong&gt;executes&lt;/strong&gt; it in. Once you internalize the real execution order, a whole category of beginner SQL errors stops being mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order you write vs. the order SQL runs
&lt;/h2&gt;

&lt;p&gt;Here's the logical processing order most SQL engines (Postgres, MySQL, SQL Server, Snowflake, etc.) actually follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;FROM / JOIN&lt;/strong&gt; — figure out which tables you're working with and build the initial row set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHERE&lt;/strong&gt; — filter individual rows, before any grouping happens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GROUP BY&lt;/strong&gt; — collapse the remaining rows into groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HAVING&lt;/strong&gt; — filter the groups (this is where aggregates like &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt; become usable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SELECT&lt;/strong&gt; — compute the actual output columns, including aliases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DISTINCT&lt;/strong&gt; — remove duplicate rows from the result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ORDER BY&lt;/strong&gt; — sort the final result set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LIMIT / OFFSET&lt;/strong&gt; — cut the result down to the rows requested&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice &lt;code&gt;SELECT&lt;/code&gt; is step 5, not step 1. That single fact is the root cause of most of the "why doesn't this work" confusion below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WHERE can't see your alias (or your aggregates)
&lt;/h2&gt;

&lt;p&gt;Go back to the broken query:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&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&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;          &lt;span class="c1"&gt;-- ❌ fails&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;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;By the logical order above, &lt;code&gt;WHERE&lt;/code&gt; (step 2) runs &lt;em&gt;before&lt;/em&gt; &lt;code&gt;SELECT&lt;/code&gt; (step 5). At the point &lt;code&gt;WHERE&lt;/code&gt; executes, the alias &lt;code&gt;total&lt;/code&gt; simply doesn't exist yet — it hasn't been computed. &lt;code&gt;WHERE&lt;/code&gt; also runs before &lt;code&gt;GROUP BY&lt;/code&gt; (step 3), so it has no concept of aggregated values at all. It only ever sees one raw row at a time.&lt;/p&gt;

&lt;p&gt;This is also why you can't write &lt;code&gt;WHERE SUM(amount) &amp;gt; 100&lt;/code&gt; — there's no group to sum yet when &lt;code&gt;WHERE&lt;/code&gt; runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: filter the group with HAVING
&lt;/h2&gt;

&lt;p&gt;If you want to filter on an aggregated value, you need &lt;code&gt;HAVING&lt;/code&gt;, which runs &lt;em&gt;after&lt;/em&gt; &lt;code&gt;GROUP BY&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;-- ✅ works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HAVING&lt;/code&gt; operates on groups, not individual rows, so it has access to aggregate functions. Note that most databases require you to repeat the aggregate expression in &lt;code&gt;HAVING&lt;/code&gt; rather than reference the &lt;code&gt;SELECT&lt;/code&gt; alias — because, again, &lt;code&gt;SELECT&lt;/code&gt; hasn't run yet at that point in the logical order. (Some engines, including Snowflake, are lenient about allowing the alias in &lt;code&gt;HAVING&lt;/code&gt; as an extension — but relying on standard behavior will keep your SQL portable.)&lt;/p&gt;

&lt;p&gt;The rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering raw rows, before grouping → &lt;code&gt;WHERE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Filtering aggregated groups, after grouping → &lt;code&gt;HAVING&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So why does ORDER BY get to use the alias?
&lt;/h2&gt;

&lt;p&gt;Here's the part that trips people up next: if aliases don't exist until &lt;code&gt;SELECT&lt;/code&gt; runs, why does this work fine?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="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="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;-- ✅ works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;ORDER BY&lt;/code&gt; is step 7 — it runs &lt;em&gt;after&lt;/em&gt; &lt;code&gt;SELECT&lt;/code&gt; (step 5). By the time sorting happens, the column alias &lt;code&gt;total&lt;/code&gt; has already been computed and is sitting right there in the result set, ready to be referenced. Same underlying rule, opposite outcome, because of where each clause sits in the execution order.&lt;/p&gt;

&lt;p&gt;A quick note on &lt;code&gt;GROUP BY&lt;/code&gt; and aliases: standard SQL technically doesn't guarantee alias support in &lt;code&gt;GROUP BY&lt;/code&gt; either, since it also runs before &lt;code&gt;SELECT&lt;/code&gt;. Several engines (Snowflake included) allow it as a convenience, but don't assume every database will.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does SELECT * change any of this?
&lt;/h2&gt;

&lt;p&gt;No — &lt;code&gt;SELECT *&lt;/code&gt; is still step 5 in the logical order, it just means "give me every column that exists at that point" instead of a specific list. The execution order itself doesn't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together: the full flow
&lt;/h2&gt;

&lt;p&gt;Here's a slightly bigger example that touches every clause, with the logical order annotated:&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="c1"&gt;-- 5. compute columns/aliases&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;                         &lt;span class="c1"&gt;-- 1. start with this table&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;USING&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;span class="c1"&gt;-- 1. combine rows&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;    &lt;span class="c1"&gt;-- 2. filter raw rows&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;customer_id&lt;/span&gt;                &lt;span class="c1"&gt;-- 3. collapse into groups&lt;/span&gt;
&lt;span class="k"&gt;HAVING&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;            &lt;span class="c1"&gt;-- 4. filter groups&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;                 &lt;span class="c1"&gt;-- 7. sort final result&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                           &lt;span class="c1"&gt;-- 8. trim to top 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though you type &lt;code&gt;SELECT&lt;/code&gt; first and &lt;code&gt;LIMIT&lt;/code&gt; last, the database works through this roughly as: build the joined rows → keep only matching rows → group them → keep only matching groups → compute the output → sort it → trim it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Need to filter individual rows before grouping? Use &lt;code&gt;WHERE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Need to filter aggregated groups? Use &lt;code&gt;HAVING&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Want to reference a &lt;code&gt;SELECT&lt;/code&gt; alias? Safe in &lt;code&gt;ORDER BY&lt;/code&gt; (and often &lt;code&gt;GROUP BY&lt;/code&gt;, engine-dependent) — never safe in &lt;code&gt;WHERE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Written order: &lt;code&gt;SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Logical execution order: &lt;code&gt;FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This is one of those SQL fundamentals that feels obvious in hindsight but trips up almost every junior dev at least once — usually right before an interview or a production deploy. Once the logical order clicks, errors like "column does not exist" or "aggregate functions are not allowed in WHERE" stop feeling random and start making total sense.&lt;/p&gt;

&lt;p&gt;If this was useful, I post SQL, Python, and Snowflake breakdowns like this regularly over on Instagram &lt;a href="https://instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;@techqueen.codes&lt;/a&gt; — come say hi.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>dataengineering</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>dbt Macros &amp; Jinja Explained (With Real Examples)</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Sun, 11 Oct 2026 07:26:09 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/dbt-macros-jinja-explained-with-real-examples-3k16</link>
      <guid>https://dev.to/neha_christina_1ac8651819/dbt-macros-jinja-explained-with-real-examples-3k16</guid>
      <description>&lt;p&gt;If you've worked in a dbt project for more than a few weeks, you've probably hit this moment: you need the same small piece of SQL logic — a unit conversion, a status mapping, a date bucket — in five or six different models. You copy it. You paste it. You move on.&lt;/p&gt;

&lt;p&gt;Then someone finds a bug in that logic, and now you're grepping the whole repo for every place you pasted it, hoping you don't miss one.&lt;/p&gt;

&lt;p&gt;This is exactly the problem dbt macros exist to solve. This post walks through what a macro actually is, how Jinja fits in, a working example you can copy into your own project, and the one gotcha that trips up almost every junior dbt dev the first time a macro breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a macro actually is
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;macro&lt;/strong&gt; is a reusable piece of SQL, written using &lt;strong&gt;Jinja&lt;/strong&gt; — a templating language originally built for Python web frameworks, which dbt uses as its templating layer on top of plain SQL.&lt;/p&gt;

&lt;p&gt;The important mental model: a macro is not a database function. Snowflake (or whatever warehouse you're on) never sees your macro definition, and it never evaluates any Jinja. Instead, dbt reads your &lt;code&gt;.sql&lt;/code&gt; files, finds the Jinja templating in them (macro calls, &lt;code&gt;ref()&lt;/code&gt;, &lt;code&gt;source()&lt;/code&gt;, control-flow tags, etc.), and &lt;strong&gt;renders that Jinja into plain SQL&lt;/strong&gt; before anything is sent to the warehouse. By the time your query reaches Snowflake, every &lt;code&gt;{{ }}&lt;/code&gt; and &lt;code&gt;{% %}&lt;/code&gt; tag is gone — it's just a &lt;code&gt;SELECT&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;That's the whole trick: dbt macros let you template SQL, not extend SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining a macro
&lt;/h2&gt;

&lt;p&gt;Macros live in your project's &lt;code&gt;macros/&lt;/code&gt; directory, in &lt;code&gt;.sql&lt;/code&gt; files. A macro definition looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- macros/cents_to_dollars.sql&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;macro&lt;/span&gt; &lt;span class="n"&gt;cents_to_dollars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;({{&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endmacro&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a macro called &lt;code&gt;cents_to_dollars&lt;/code&gt; that takes one argument, &lt;code&gt;column_name&lt;/code&gt;, and returns the SQL expression to convert a cents value to a float dollar amount. Nothing runs yet — this is just a definition, the same way defining a Python function doesn't execute its body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling a macro
&lt;/h2&gt;

&lt;p&gt;You call a macro from any model using the same double-curly-brace syntax you already use for &lt;code&gt;ref()&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="c1"&gt;-- models/marts/fct_orders.sql&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;cents_to_dollars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount_cents'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;amount_dollars&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stg_orders'&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;When dbt compiles this model, it swaps &lt;code&gt;{{ cents_to_dollars('amount_cents') }}&lt;/code&gt; for the macro's rendered body. The compiled SQL that actually hits Snowflake looks 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;select&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount_cents&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;amount_dollars&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stg_orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine you need that same conversion in &lt;code&gt;fct_refunds&lt;/code&gt;, &lt;code&gt;fct_payouts&lt;/code&gt;, and &lt;code&gt;rpt_revenue&lt;/code&gt;. Instead of pasting the three-line cast into each one, you call &lt;code&gt;{{ cents_to_dollars('some_column') }}&lt;/code&gt; in each model. If the rounding logic ever needs to change — say, finance wants you to round instead of truncate — you change it in &lt;strong&gt;one file&lt;/strong&gt;, and every model that calls the macro picks up the fix the next time it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jinja gives you real control flow
&lt;/h2&gt;

&lt;p&gt;Because macros are Jinja, you're not limited to simple text substitution. You get &lt;code&gt;{% if %}&lt;/code&gt; conditionals, &lt;code&gt;{% for %}&lt;/code&gt; loops, and &lt;code&gt;{% set %}&lt;/code&gt; for variables — none of which exist in plain SQL.&lt;/p&gt;

&lt;p&gt;A common use case is generating repetitive &lt;code&gt;CASE WHEN&lt;/code&gt; columns from a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- macros/pivot_payment_methods.sql&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;macro&lt;/span&gt; &lt;span class="n"&gt;payment_method_columns&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;method&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;'bank_transfer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'credit_card'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gift_card'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;payment_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{{ method }}'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;method&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="nv"&gt;","&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;last&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endfor&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endmacro&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Called inside a &lt;code&gt;select&lt;/code&gt;, this loop generates one &lt;code&gt;sum(case when...)&lt;/code&gt; column per payment method, so you're not hand-writing (and hand-maintaining) three nearly-identical lines every time a new payment method shows up.&lt;/p&gt;

&lt;p&gt;You also don't need to write every macro yourself. The &lt;a href="https://github.com/dbt-labs/dbt-utils" rel="noopener noreferrer"&gt;dbt-utils&lt;/a&gt; package — installed via &lt;code&gt;packages.yml&lt;/code&gt; and &lt;code&gt;dbt deps&lt;/code&gt; — ships a library of prebuilt macros for common jobs: &lt;code&gt;dbt_utils.date_trunc()&lt;/code&gt;, &lt;code&gt;dbt_utils.pivot()&lt;/code&gt;, &lt;code&gt;dbt_utils.generate_surrogate_key()&lt;/code&gt;, and more. Before writing a macro from scratch, it's worth checking whether dbt-utils already has it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha: macros fail at compile time, not run time
&lt;/h2&gt;

&lt;p&gt;Here's the thing that catches almost everyone the first time: &lt;strong&gt;macros are resolved before any SQL reaches the warehouse.&lt;/strong&gt; If there's a typo in a macro call, a missing argument, or a Jinja syntax error, you will not get a Snowflake error. You'll get a &lt;strong&gt;Jinja compilation error&lt;/strong&gt;, and those read very differently — they're about rendering, not about SQL semantics, which can be confusing if you're expecting a database error message.&lt;/p&gt;

&lt;p&gt;The fix is a command most juniors don't reach for soon enough: &lt;code&gt;dbt compile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbt compile &lt;span class="nt"&gt;--select&lt;/span&gt; fct_orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders all the Jinja in your project and writes the fully-resolved SQL to &lt;code&gt;target/compiled/&amp;lt;project_name&amp;gt;/models/.../fct_orders.sql&lt;/code&gt;, without running anything against the warehouse. When a macro-heavy model breaks, open that compiled file first. You'll see exactly what SQL dbt generated — which immediately tells you whether the problem is in your macro logic (fix the Jinja) or in the resulting SQL itself (fix the query, and now you can debug it like normal SQL).&lt;/p&gt;

&lt;p&gt;Making &lt;code&gt;dbt compile&lt;/code&gt; a reflex before &lt;code&gt;dbt run&lt;/code&gt; whenever a macro is involved will save you a lot of confused debugging sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick recap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A macro is reusable SQL written with Jinja templating, defined once in your &lt;code&gt;macros/&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;You call it from any model with &lt;code&gt;{{ macro_name(args) }}&lt;/code&gt; — the same pattern as &lt;code&gt;ref()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;dbt renders all Jinja into plain SQL at &lt;strong&gt;compile time&lt;/strong&gt;, before anything touches the warehouse&lt;/li&gt;
&lt;li&gt;Jinja gives you &lt;code&gt;{% if %}&lt;/code&gt;, &lt;code&gt;{% for %}&lt;/code&gt;, and &lt;code&gt;{% set %}&lt;/code&gt; — real logic that plain SQL doesn't have&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbt_utils&lt;/code&gt; ships prebuilt macros for common tasks — check there before writing your own&lt;/li&gt;
&lt;li&gt;When something breaks, run &lt;code&gt;dbt compile&lt;/code&gt; first to see the generated SQL before you start debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this clicks, you'll start noticing copy-pasted SQL everywhere in your project — and that's usually your cue that it belongs in a macro.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of an ongoing series on practical SQL, Python, and Snowflake skills for junior data engineers. Follow along on Instagram &lt;a href="https://instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;@techqueen.codes&lt;/a&gt; for daily breakdowns like this one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dbt</category>
      <category>sql</category>
      <category>dataengineering</category>
      <category>snowflake</category>
    </item>
    <item>
      <title>SQL Indexes Explained (For People Who Just Got Yelled At By a Slow Dashboard)</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:17:10 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/sql-indexes-explained-for-people-who-just-got-yelled-at-by-a-slow-dashboard-e1d</link>
      <guid>https://dev.to/neha_christina_1ac8651819/sql-indexes-explained-for-people-who-just-got-yelled-at-by-a-slow-dashboard-e1d</guid>
      <description>&lt;p&gt;At some point in every junior developer's career, a query that ran fine on your laptop turns into a query that times out in production. You didn't change the logic. You didn't change the data model. The only thing that changed is the data got big.&lt;/p&gt;

&lt;p&gt;Nine times out of ten, the fix is an index. This post is the explanation I wish someone had given me instead of "just add an index, it'll be fine."&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: full table scans
&lt;/h2&gt;

&lt;p&gt;Say you've got an &lt;code&gt;orders&lt;/code&gt; table with 50 million rows, and you run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="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;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;48213&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index on &lt;code&gt;customer_id&lt;/code&gt;, the database has no way to know where rows for customer &lt;code&gt;48213&lt;/code&gt; live. So it does the only thing it can do: it reads every single row in the table, checks whether &lt;code&gt;customer_id&lt;/code&gt; matches, and keeps the ones that do. That's called a &lt;strong&gt;full table scan&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On a 500-row table, a full scan is instant and nobody notices. On a 50-million-row table, it can mean seconds or minutes instead of milliseconds — which is exactly the kind of thing that turns into a Slack message asking why the dashboard is frozen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an index actually is
&lt;/h2&gt;

&lt;p&gt;An index is a separate data structure — most commonly a B-tree — that stores a sorted copy of one or more columns, along with a pointer back to where the full row lives on disk.&lt;/p&gt;

&lt;p&gt;The usual analogy is a phone book. If you want everyone with the last name "Smith," you don't read the book cover to cover — you flip straight to the S section because the names are sorted. An index gives your database the same shortcut: instead of scanning every row, it can jump straight to the matching entries and then go fetch just those rows.&lt;/p&gt;

&lt;p&gt;Once you add an index:&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;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_customer&lt;/span&gt;
&lt;span class="k"&gt;ON&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;the same &lt;code&gt;SELECT&lt;/code&gt; query above no longer needs a full scan. The database checks the index, finds exactly which rows have &lt;code&gt;customer_id = 48213&lt;/code&gt;, and reads only those. In Postgres and MySQL, this happens automatically — the query planner picks up the new index the next time that column is used in a &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, or &lt;code&gt;ORDER BY&lt;/code&gt; clause, no code changes needed on your end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clustered vs. non-clustered indexes
&lt;/h2&gt;

&lt;p&gt;In most row-oriented relational databases (Postgres, MySQL, SQL Server), you'll run into two flavors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clustered index&lt;/strong&gt; — this determines the physical order the table's rows are stored in on disk. Because the data can only be physically sorted one way, a table can have exactly one clustered index. It's usually built on the primary key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-clustered index&lt;/strong&gt; — a separate structure, stored apart from the table, that holds a sorted copy of the indexed column(s) plus a pointer back to the actual row. A table can have several non-clustered indexes, which is what lets you optimize for more than one type of lookup.&lt;/p&gt;

&lt;p&gt;A practical way to think about it: the clustered index is the order the book's pages are physically bound in. A non-clustered index is more like the index at the back of the book — a separate list that tells you which page to flip to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes are not free
&lt;/h2&gt;

&lt;p&gt;It's tempting, once you learn this trick, to start indexing everything. Don't. Every index comes with real costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writes get slower.&lt;/strong&gt; Every &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; has to update not just the table, but every index on that table too. Five indexes means five extra structures to maintain on every write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage goes up.&lt;/strong&gt; An index is a real, separate structure taking up real disk space — sometimes a significant fraction of the table's own size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-cardinality columns rarely benefit.&lt;/strong&gt; An index on a boolean column (like &lt;code&gt;is_active&lt;/code&gt;) usually doesn't help much, because there are only two possible values — the database still ends up reading a large chunk of the table either way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reasonable rule of thumb: index the columns you actually filter, join, or sort on frequently in real queries — not every column "just in case." If you're not sure, look at your slow query logs or &lt;code&gt;EXPLAIN&lt;/code&gt; output before adding an index, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Snowflake exception
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up if they learned SQL on Postgres or MySQL and then moved to Snowflake (which is the stack this blog mostly covers): &lt;strong&gt;Snowflake does not have traditional indexes.&lt;/strong&gt; There's no &lt;code&gt;CREATE INDEX&lt;/code&gt; statement in Snowflake at all.&lt;/p&gt;

&lt;p&gt;Instead, Snowflake automatically breaks every table into &lt;strong&gt;micro-partitions&lt;/strong&gt; — contiguous chunks of roughly 50–500MB of uncompressed data — and stores metadata about the min/max values in each one. When you run a query with a filter, Snowflake uses that metadata to skip micro-partitions that can't possibly contain matching rows, a process called &lt;strong&gt;pruning&lt;/strong&gt;. You get a lot of the same benefit as an index (skip the data you don't need) without creating or maintaining anything yourself.&lt;/p&gt;

&lt;p&gt;For very large tables where Snowflake's natural micro-partitioning isn't lining up well with how you query the data, you can define a &lt;strong&gt;clustering key&lt;/strong&gt; to influence how the data gets physically organized — but that's a different mechanism solving a similar problem, not an index. (We covered clustering keys in an earlier post if you want the deeper dive.)&lt;/p&gt;

&lt;p&gt;The short version: learn indexes for Postgres/MySQL and interviews — plenty of systems still use them, and understanding the concept makes you a better engineer regardless of platform. Just don't go looking for &lt;code&gt;CREATE INDEX&lt;/code&gt; in a Snowflake worksheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;No index means a full table scan on every query that filters on that column.&lt;/li&gt;
&lt;li&gt;An index is a sorted structure that points back to the real rows — it trades a bit of write overhead for much faster reads.&lt;/li&gt;
&lt;li&gt;Clustered indexes sort the table itself (one per table); non-clustered indexes are separate lookup structures (you can have several).&lt;/li&gt;
&lt;li&gt;Indexes aren't free — they slow down writes and cost storage, so index what you actually query on, not everything.&lt;/li&gt;
&lt;li&gt;Snowflake skips traditional indexing entirely in favor of automatic micro-partitioning, with clustering keys as the manual lever for huge tables.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you found this useful, I post daily breakdowns like this on &lt;a href="https://instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;@techqueen.codes&lt;/a&gt; on Instagram — SQL, Python, and Snowflake, explained for junior developers. Drop a comment with the slowest query you've ever had to debug; I might turn it into the next post.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>beginners</category>
    </item>
    <item>
      <title>GROUP BY &amp; Aggregate Functions Explained (the WHERE vs HAVING mistake almost everyone makes)</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Mon, 28 Sep 2026 09:25:48 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/group-by-aggregate-functions-explained-the-where-vs-having-mistake-almost-everyone-makes-1nfg</link>
      <guid>https://dev.to/neha_christina_1ac8651819/group-by-aggregate-functions-explained-the-where-vs-having-mistake-almost-everyone-makes-1nfg</guid>
      <description>&lt;p&gt;If you've written more than a handful of SQL queries, you've used &lt;code&gt;GROUP BY&lt;/code&gt;. But there's a difference between writing a &lt;code&gt;GROUP BY&lt;/code&gt; that happens to run and actually understanding what it's doing — and that gap is exactly where a lot of junior developers get tripped up in interviews and in code review.&lt;/p&gt;

&lt;p&gt;This post walks through &lt;code&gt;GROUP BY&lt;/code&gt; and the five core aggregate functions from the ground up, then covers the mistake that catches almost everyone at least once: mixing up &lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem GROUP BY solves
&lt;/h2&gt;

&lt;p&gt;Say you've got an &lt;code&gt;orders&lt;/code&gt; table with one row per order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;region&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;product&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;category&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;sales&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;date&lt;/span&gt;
&lt;span class="err"&gt;---------+--------+-------------------+-------+------------&lt;/span&gt;
&lt;span class="mf"&gt;1&lt;/span&gt;        &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;West&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Electronics&lt;/span&gt;       &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;120&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="ld"&gt;2026-01-03&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;        &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;East&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Furniture&lt;/span&gt;         &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;340&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="ld"&gt;2026-01-04&lt;/span&gt;
&lt;span class="mf"&gt;3&lt;/span&gt;        &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;West&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Electronics&lt;/span&gt;       &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;90&lt;/span&gt;    &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="ld"&gt;2026-01-05&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this table has 40,000 rows, nobody wants to read all 40,000 of them. What people actually ask for is a summary: total sales per region, average order value per customer, number of orders per day. That's the entire point of &lt;code&gt;GROUP BY&lt;/code&gt; — it collapses many rows into one row per group, so an aggregate function has something meaningful to compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five aggregate functions you'll use constantly
&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;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;-- how many rows&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="c1"&gt;-- total of a column&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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;-- mean of a column&lt;/span&gt;
&lt;span class="k"&gt;MIN&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="c1"&gt;-- smallest value&lt;/span&gt;
&lt;span class="k"&gt;MAX&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="c1"&gt;-- largest value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these takes many rows as input and returns a single value. Used without &lt;code&gt;GROUP BY&lt;/code&gt;, an aggregate function collapses the &lt;em&gt;entire&lt;/em&gt; result set down to one row:&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;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_orders&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;p&gt;That returns exactly one row — the total order count across the whole table. Add &lt;code&gt;GROUP BY&lt;/code&gt;, and instead of collapsing everything into one row, SQL collapses the rows &lt;em&gt;within each group&lt;/em&gt; into one row per group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing your first 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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&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 happens under the hood: SQL scans the table and buckets every row by its &lt;code&gt;region&lt;/code&gt; value. Then, separately for each bucket, it runs &lt;code&gt;SUM(sales)&lt;/code&gt;. The result has one row per distinct region — not one row per order.&lt;/p&gt;

&lt;p&gt;This is the mental model that matters: &lt;strong&gt;GROUP BY defines the buckets, aggregate functions summarize what's inside each bucket.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  WHERE vs HAVING — the mistake that catches everyone
&lt;/h2&gt;

&lt;p&gt;This is the part that trips up almost every junior developer at least once, so it's worth slowing down for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales&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;order_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; filters individual &lt;strong&gt;rows&lt;/strong&gt;, and it runs &lt;em&gt;before&lt;/em&gt; grouping happens. In the query above, any order placed before January 1st is thrown out before the grouping and summing even start.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HAVING&lt;/code&gt; filters entire &lt;strong&gt;groups&lt;/strong&gt;, and it runs &lt;em&gt;after&lt;/em&gt; aggregation. Here, it drops any region whose total sales didn't clear $10,000 — but only after &lt;code&gt;SUM(sales)&lt;/code&gt; has already been calculated for every region.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the mistake: try writing &lt;code&gt;WHERE SUM(sales) &amp;gt; 10000&lt;/code&gt; instead of using &lt;code&gt;HAVING&lt;/code&gt;, and most databases — including Snowflake and Postgres — will throw an error. The reason is order of operations: at the point &lt;code&gt;WHERE&lt;/code&gt; is evaluated, grouping hasn't happened yet, so there's no per-group &lt;code&gt;SUM(sales)&lt;/code&gt; for &lt;code&gt;WHERE&lt;/code&gt; to check against. The aggregate simply doesn't exist yet at that stage of query execution. &lt;code&gt;HAVING&lt;/code&gt; exists specifically because SQL needed a filter clause that runs &lt;em&gt;after&lt;/em&gt; aggregation.&lt;/p&gt;

&lt;p&gt;A useful rule of thumb: if the condition references a raw column (&lt;code&gt;order_date&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;), it goes in &lt;code&gt;WHERE&lt;/code&gt;. If it references an aggregate function (&lt;code&gt;SUM(...)&lt;/code&gt;, &lt;code&gt;COUNT(...)&lt;/code&gt;, &lt;code&gt;AVG(...)&lt;/code&gt;), it goes in &lt;code&gt;HAVING&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grouping by more than one column
&lt;/h2&gt;

&lt;p&gt;You're not limited to grouping by a single column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="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_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;orders&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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This groups by every unique &lt;em&gt;combination&lt;/em&gt; of &lt;code&gt;region&lt;/code&gt; and &lt;code&gt;product_category&lt;/code&gt;. "West / Electronics" and "West / Furniture" become two separate rows in the output, even though they share the same region — because the grouping key is the pair of columns together, not each column independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha that breaks queries
&lt;/h2&gt;

&lt;p&gt;This is the error message you'll eventually run into if you haven't already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Will not run in Snowflake, Postgres, or most standards-compliant databases&lt;/span&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;customer_name&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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: &lt;code&gt;customer_name&lt;/code&gt; appears in the &lt;code&gt;SELECT&lt;/code&gt; list, but it's neither wrapped in an aggregate function nor included in &lt;code&gt;GROUP BY&lt;/code&gt;. Once you group by &lt;code&gt;region&lt;/code&gt;, each output row represents &lt;em&gt;many&lt;/em&gt; orders from potentially many different customers — so which &lt;code&gt;customer_name&lt;/code&gt; should SQL display for that row? There's no single correct answer, so Snowflake and Postgres refuse to run the query at all.&lt;/p&gt;

&lt;p&gt;The fix is either to aggregate the column or add it to the &lt;code&gt;GROUP BY&lt;/code&gt; list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Aggregate it&lt;/span&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;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;unique_customers&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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Or group by it&lt;/span&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;customer_name&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;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing worth knowing if you work across different database engines: MySQL has historically been more lenient here and will sometimes let a non-aggregated, non-grouped column through, silently picking an arbitrary value for it per group. That's rarely what you actually want, and it's part of why Snowflake and Postgres's stricter behavior is the safer default to reason about — it forces you to be explicit rather than getting a query that runs but returns a value you didn't intend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order of operations, all in one place
&lt;/h2&gt;

&lt;p&gt;It helps to know the actual evaluation order of a query with all these clauses, since it's not the same as the order you type them in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;FROM&lt;/code&gt; — pick the source table(s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; — filter individual rows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GROUP BY&lt;/code&gt; — bucket the remaining rows into groups&lt;/li&gt;
&lt;li&gt;Aggregate functions run — one value computed per group&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HAVING&lt;/code&gt; — filter out entire groups based on the aggregated values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SELECT&lt;/code&gt; — choose which columns/expressions to return&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; — sort the final result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping this order in your head is what makes &lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt; make sense: &lt;code&gt;WHERE&lt;/code&gt; runs at step 2, before groups exist, and &lt;code&gt;HAVING&lt;/code&gt; runs at step 5, after they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few practice queries to try
&lt;/h2&gt;

&lt;p&gt;If you've got access to any table with a few thousand rows and at least one categorical column, try these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Count of rows per category&lt;/li&gt;
&lt;li&gt;Average of some numeric column per category&lt;/li&gt;
&lt;li&gt;Categories where the average exceeds some threshold (this one requires &lt;code&gt;HAVING&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Grouping by two columns instead of one&lt;/li&gt;
&lt;li&gt;Combining a &lt;code&gt;WHERE&lt;/code&gt; filter and a &lt;code&gt;HAVING&lt;/code&gt; filter in the same query&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Working through all five is usually enough to make the &lt;code&gt;WHERE&lt;/code&gt;/&lt;code&gt;HAVING&lt;/code&gt; distinction permanent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GROUP BY&lt;/code&gt; and aggregate functions are some of the most-used tools in SQL, and they're also one of the most common sources of small, confusing errors for people early in their SQL journey. The fix isn't memorizing syntax — it's holding onto the mental model: &lt;code&gt;GROUP BY&lt;/code&gt; defines the buckets, aggregate functions summarize what's inside them, &lt;code&gt;WHERE&lt;/code&gt; filters before that happens, and &lt;code&gt;HAVING&lt;/code&gt; filters after.&lt;/p&gt;

&lt;p&gt;If this was useful, I post SQL, Python, and Snowflake breakdowns like this regularly on Instagram &lt;a href="https://instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;@techqueen.codes&lt;/a&gt; — feel free to follow along there for the shorter, visual version of posts like this one.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Snowflake Zero-Copy Cloning Explained</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Sat, 26 Sep 2026 10:35:27 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/snowflake-zero-copy-cloning-explained-79g</link>
      <guid>https://dev.to/neha_christina_1ac8651819/snowflake-zero-copy-cloning-explained-79g</guid>
      <description>&lt;h2&gt;
  
  
  Zero-Copy Cloning in Snowflake: Full Environments Without the Storage Bill
&lt;/h2&gt;

&lt;p&gt;If you've worked with a traditional database, you know the drill: someone needs a copy of a production table for testing, and now you're looking at a multi-hour copy job and a storage bill that just doubled. Snowflake has a feature that makes that entire problem disappear, and it's one of the most underrated tools in the platform: &lt;strong&gt;zero-copy cloning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post covers what cloning actually does, why it's nearly free, how to use it, and the gotchas that catch people the first time they rely on it.&lt;/p&gt;

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

&lt;p&gt;Say you have a 4TB &lt;code&gt;orders&lt;/code&gt; table in production and your team wants a realistic dataset to test a migration against. In most databases, your options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a full &lt;code&gt;INSERT INTO ... SELECT *&lt;/code&gt; and wait for it to move every row&lt;/li&gt;
&lt;li&gt;Provision new storage and pay for a second full copy of the data&lt;/li&gt;
&lt;li&gt;Settle for a stale, partial, or synthetic sample instead of the real thing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiply that by every engineer who wants their own sandbox, or every QA environment your team spins up, and the storage cost adds up fast — even though most of that "new" data is identical to what already exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  What zero-copy cloning actually does
&lt;/h3&gt;

&lt;p&gt;Snowflake stores table data as immutable &lt;strong&gt;micro-partitions&lt;/strong&gt; — compressed, columnar chunks of data that, once written, never change. Table metadata (a layer that's stored separately from the raw data) just keeps a set of pointers to which micro-partitions make up that table's current state.&lt;/p&gt;

&lt;p&gt;A clone works at the metadata layer, not the data layer. When you 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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders_dev&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;orders_prod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snowflake creates a brand-new table object with its own metadata, but that metadata just points at the same micro-partitions the source table already has. No data is read, copied, or moved. That's why the operation completes in about a second regardless of whether the source table is 10MB or 40TB, and why it doesn't cost you additional storage the moment you run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy-on-write: why it stays cheap after you edit it
&lt;/h3&gt;

&lt;p&gt;The clone isn't a read-only alias — you can insert into it, update it, delete from it, run DDL against it, all independently of the source. The reason this doesn't quietly turn into a full duplicate is Snowflake's &lt;strong&gt;copy-on-write&lt;/strong&gt; model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unchanged data stays shared between the original and the clone — both tables' metadata point at the same underlying micro-partitions.&lt;/li&gt;
&lt;li&gt;The moment you modify a row in the clone, Snowflake writes a new micro-partition for just that change and updates the clone's metadata to point at it. The original table's micro-partitions, and its metadata, are untouched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, this means a clone that sits mostly unmodified costs you close to nothing in storage, and a clone that diverges heavily from its source starts to accrue storage costs proportional to how much of it has actually changed — not proportional to its total size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloning is not limited to tables
&lt;/h3&gt;

&lt;p&gt;You can clone at any level of the object hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- clone a single table&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;orders_dev&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;orders_prod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- clone an entire schema (all tables, views, etc. inside it)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;analytics_dev&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;analytics_prod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- clone an entire database&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;app_qa&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;app_prod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloning a schema or database recursively clones the objects inside it, using the same copy-on-write mechanics all the way down. This is what makes it realistic to give every engineer on a team their own full-size copy of a production database for local development, without your storage bill scaling linearly with headcount.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combine it with Time Travel for point-in-time snapshots
&lt;/h3&gt;

&lt;p&gt;Snowflake's Time Travel feature keeps historical versions of table data available for a retention period (1 day by default, configurable up to 90 days on Enterprise Edition and above). You can clone a table as it existed at a specific point in the past, or right before a specific statement ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- clone as of a specific timestamp&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;orders_snapshot&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
  &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-25 14:00:00'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- clone as it looked immediately before a specific statement&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;orders_safe&lt;/span&gt;
&lt;span class="n"&gt;CLONE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
  &lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;STATEMENT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'01af2b3c-0000-1234-0000-abcdef012345'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combination is genuinely useful before running a risky backfill, migration, or schema change: take an instant snapshot first, run your change, and if something goes wrong you have an exact, queryable copy of the "before" state to compare against or restore from — without having taken a separate backup in advance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where this shows up in real day-to-day work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-engineer dev databases.&lt;/strong&gt; Instead of everyone sharing one dev schema (and stepping on each other's test data), each engineer gets their own full clone of production to work against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-migration safety nets.&lt;/strong&gt; Clone the table or schema right before a risky &lt;code&gt;ALTER&lt;/code&gt;, backfill, or dbt model rebuild, so you have an instant rollback point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QA and staging environments.&lt;/strong&gt; Stand up a QA environment from current production data on demand, run your test suite, and drop it when you're done — the teardown is as cheap as the setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-off experiments.&lt;/strong&gt; Want to try a schema change or a new transformation logic without touching real data? Clone it, break it, throw it away.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The gotchas
&lt;/h3&gt;

&lt;p&gt;Cloning is close to free and close to instant, but it isn't magic, and there are two things that regularly trip people up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A clone is a snapshot, not a live sync.&lt;/strong&gt; Once you run &lt;code&gt;CREATE TABLE ... CLONE&lt;/code&gt;, the clone is a point-in-time copy. It does not stay updated as the source table changes afterward. If you need current production data, you have to clone again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grants aren't cloned by default.&lt;/strong&gt; Permissions on the source object (who can select from it, insert into it, and so on) are not automatically carried over to the clone in most configurations — you'll need to explicitly grant access on the new object, or your teammates will hit a permissions wall the moment they try to query their shiny new clone.&lt;/p&gt;

&lt;p&gt;It's also worth knowing that Time Travel data itself has a retention window — if you're relying on &lt;code&gt;AT&lt;/code&gt; or &lt;code&gt;BEFORE&lt;/code&gt; clauses to clone historical states, that history is only available as far back as your table's Time Travel retention period allows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;Zero-copy cloning is one of those Snowflake features that sounds like a marketing bullet point until you actually need it — and then it quietly saves your team hours of copy jobs and a meaningful chunk of your storage bill. If you're setting up dev environments, running risky schema changes, or just tired of asking for a "small" copy of a production table, this is the tool for the job.&lt;/p&gt;

&lt;p&gt;If you found this useful, I write daily SQL, Python, and Snowflake breakdowns for junior data engineers over at &lt;a href="https://instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;@techqueen.codes&lt;/a&gt; on Instagram — come say hi.&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>zerocopycloning</category>
      <category>developers</category>
      <category>programming</category>
    </item>
    <item>
      <title>What is dbt and Why is it in Every Data Engineering Job Listing?</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:30:07 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/what-is-dbt-and-why-is-it-in-every-data-engineering-job-listing-3jkc</link>
      <guid>https://dev.to/neha_christina_1ac8651819/what-is-dbt-and-why-is-it-in-every-data-engineering-job-listing-3jkc</guid>
      <description>&lt;p&gt;If you've been in data for more than five minutes recently, you've heard of dbt.&lt;/p&gt;

&lt;p&gt;It shows up in job listings constantly. Data engineers rave about it. But nobody ever explains what it actually &lt;em&gt;does&lt;/em&gt; in plain English.&lt;/p&gt;

&lt;p&gt;This post fixes that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is dbt?
&lt;/h2&gt;

&lt;p&gt;dbt stands for &lt;strong&gt;data build tool&lt;/strong&gt;. It transforms your raw data into clean, analysis-ready tables — inside your data warehouse.&lt;/p&gt;

&lt;p&gt;Here's the analogy that makes it click:&lt;/p&gt;

&lt;p&gt;Your data warehouse (Snowflake, BigQuery, Redshift) is the kitchen. Raw data is the ingredients. dbt is the recipe book — it tells the kitchen exactly how to transform those raw ingredients into clean, ready-to-eat tables.&lt;/p&gt;

&lt;p&gt;Three things make dbt different from just writing SQL yourself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You write SQL — dbt handles the rest.&lt;/strong&gt; No new language to learn. If you know SELECT, you can write dbt models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in testing.&lt;/strong&gt; Data quality checks that run automatically. Bad data gets caught before it reaches dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-generated documentation.&lt;/strong&gt; dbt builds a full documentation site from your code and YAML descriptions, so your whole team knows what every table means.&lt;/p&gt;




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

&lt;p&gt;Before dbt, data teams had a messy set of problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL everywhere with no structure.&lt;/strong&gt; Analysts copy-pasted the same transformation logic into 10 different dashboards. When the logic changed, you had to update it in 10 places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No way to test your data.&lt;/strong&gt; You only found out your data was wrong when a stakeholder spotted something odd in a report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero documentation.&lt;/strong&gt; Nobody knew what the &lt;code&gt;revenue&lt;/code&gt; column actually meant or where it came from. Institutional knowledge lived in people's heads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No version control.&lt;/strong&gt; SQL lived inside BI tools, not in Git. Changes weren't tracked. You couldn't review them, roll them back, or collaborate on them properly.&lt;/p&gt;

&lt;p&gt;dbt was built specifically to solve all four of these problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  How dbt Works
&lt;/h2&gt;

&lt;p&gt;A dbt model is just a &lt;code&gt;.sql&lt;/code&gt; file. You write a SELECT statement, save it, and dbt turns it into a table or view in your warehouse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- models/customers.sql&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="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_orders&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'raw_customers'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'raw_orders'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="k"&gt;USING&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;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{{ ref() }}&lt;/code&gt; syntax is how dbt knows which models depend on each other. It builds a dependency graph automatically — so if &lt;code&gt;raw_customers&lt;/code&gt; needs to be built before &lt;code&gt;customers&lt;/code&gt;, dbt figures that out without you having to specify it.&lt;/p&gt;

&lt;p&gt;Then you just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbt run    &lt;span class="c"&gt;# builds all your models&lt;/span&gt;
dbt &lt;span class="nb"&gt;test&lt;/span&gt;   &lt;span class="c"&gt;# runs all your data tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. dbt compiles your SQL, runs it against your warehouse, and creates the tables.&lt;/p&gt;




&lt;h2&gt;
  
  
  5 dbt Terms You'll Hear Everywhere
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt; — SQL files that define a transformation. Each one becomes a table or view in your warehouse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt; — Your raw tables, declared in YAML so dbt knows where the data comes from and can test it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt; — Checks like &lt;code&gt;not_null&lt;/code&gt;, &lt;code&gt;unique&lt;/code&gt;, &lt;code&gt;accepted_values&lt;/code&gt; — run automatically with &lt;code&gt;dbt test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs&lt;/strong&gt; — An auto-generated documentation site built from your models and YAML descriptions. Searchable, linkable, always up to date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lineage graph&lt;/strong&gt; — A visual map showing how data flows through all your models. dbt builds this automatically from your &lt;code&gt;ref()&lt;/code&gt; calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  dbt Core vs dbt Cloud
&lt;/h2&gt;

&lt;p&gt;There are two ways to use dbt:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt Core&lt;/strong&gt; is free and open source. You run it from your terminal or CI/CD pipeline (GitLab, GitHub Actions, etc.). You get full control and all the core features. You manage the infrastructure yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt Cloud&lt;/strong&gt; is the managed platform from the company behind dbt. It adds a browser-based IDE, scheduling, CI/CD, and a polished UI. There's a free tier for individuals, with paid plans for teams.&lt;/p&gt;

&lt;p&gt;For most engineers starting out, dbt Core is the right place to begin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Should You Learn dbt?
&lt;/h2&gt;

&lt;p&gt;Yes. Here's why:&lt;/p&gt;

&lt;p&gt;It's in thousands of job listings for data engineer, analytics engineer, and data analyst roles right now.&lt;/p&gt;

&lt;p&gt;If you know SQL, you already know 80% of dbt. The learning curve is much lower than people expect.&lt;/p&gt;

&lt;p&gt;It makes your SQL reviewable, testable, and version controlled — exactly how software engineers treat their code.&lt;/p&gt;

&lt;p&gt;It's used by Airbnb, GitLab, JetBlue, and thousands of data teams worldwide.&lt;/p&gt;

&lt;p&gt;And dbt Core is completely free. You can start today.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;Install dbt Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;dbt-snowflake   &lt;span class="c"&gt;# or dbt-bigquery, dbt-redshift&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then follow the official quickstart at docs.getdbt.com — it's one of the best pieces of technical documentation out there.&lt;/p&gt;




&lt;p&gt;Does your team use dbt? Drop a comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 🔥&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>sql</category>
      <category>beginners</category>
      <category>dbt</category>
    </item>
    <item>
      <title>CTEs vs Subqueries in SQL: Which One Should You Use?</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Thu, 02 Apr 2026 10:44:25 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/ctes-vs-subqueries-in-sql-which-one-should-you-use-3p4e</link>
      <guid>https://dev.to/neha_christina_1ac8651819/ctes-vs-subqueries-in-sql-which-one-should-you-use-3p4e</guid>
      <description>&lt;p&gt;If you've written SQL for more than a week, you've hit this question.&lt;/p&gt;

&lt;p&gt;Do I use a CTE or a subquery here?&lt;/p&gt;

&lt;p&gt;Both can solve the same problem. Both produce the same result. So which one is actually better?&lt;/p&gt;

&lt;p&gt;The honest answer: it depends. But there are clear rules for when to use each — and once you know them, you'll never second-guess yourself again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Subquery?
&lt;/h2&gt;

&lt;p&gt;A subquery is a query nested inside another query. It runs inline, right where it's written.&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- This is the subquery&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&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;salary&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;employees&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner &lt;code&gt;SELECT AVG(salary)&lt;/code&gt; runs first, produces a single value, and the outer query uses that value in its &lt;code&gt;WHERE&lt;/code&gt; clause.&lt;/p&gt;

&lt;p&gt;Subqueries can live in &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, and &lt;code&gt;HAVING&lt;/code&gt; clauses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Quick to write for simple filters. No extra syntax needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Gets hard to read when nested more than one level deep. Can't reuse the result elsewhere in the same query.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a CTE?
&lt;/h2&gt;

&lt;p&gt;A CTE (Common Table Expression) is a named temporary result set defined at the top of your query using &lt;code&gt;WITH … AS ()&lt;/code&gt;. You reference it by name below — just like a real table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Define the CTE first&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;avg_salary&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- Now use it like a table&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;avg_salary&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_sal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CTE is defined once at the top and can be referenced multiple times in the main query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Very readable — complex logic is broken into named steps. Can be referenced more than once within the same query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Slightly more verbose. Not all databases support recursive CTEs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Same Query, Two Ways
&lt;/h2&gt;

&lt;p&gt;Here's the same problem — find employees earning above the average salary — written both ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subquery version:&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CTE version:&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;WITH&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same result. Different readability. The CTE version makes the intent clearer — especially as queries get more complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use a Subquery
&lt;/h2&gt;

&lt;p&gt;Use a subquery when the logic is simple and short, you only need it in one place, and you're filtering in &lt;code&gt;WHERE&lt;/code&gt; or &lt;code&gt;HAVING&lt;/code&gt;. For quick one-liners, subqueries are perfectly fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use a CTE
&lt;/h2&gt;

&lt;p&gt;Use a CTE when you need to reference the result more than once, the query is complex and needs to be readable, or you're building step-by-step transformations where each step builds on the last.&lt;/p&gt;

&lt;p&gt;CTEs are especially powerful when chained together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;
&lt;span class="n"&gt;step_one&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;dept&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;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&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;dept&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;step_two&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;step_one&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;70000&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;step_two&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each CTE becomes a building block. Much easier to debug than deeply nested subqueries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do CTEs Run Faster?
&lt;/h2&gt;

&lt;p&gt;This is the question most people get wrong.&lt;/p&gt;

&lt;p&gt;In most modern databases — including Snowflake and BigQuery — the query optimiser treats CTEs and subqueries almost identically. Neither is consistently faster.&lt;/p&gt;

&lt;p&gt;The exception: older versions of Redshift and PostgreSQL sometimes materialise CTEs (compute them separately and store the result), which can make CTEs slower for complex queries. If you're on one of these, test both and measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real rule:&lt;/strong&gt; optimise for readability first. Only swap to subqueries for performance if you've actually measured a difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Subquery&lt;/th&gt;
&lt;th&gt;CTE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Syntax&lt;/td&gt;
&lt;td&gt;Inline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WITH … AS ()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readability&lt;/td&gt;
&lt;td&gt;Gets messy&lt;/td&gt;
&lt;td&gt;Very readable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse in query?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursive?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Simple filters&lt;/td&gt;
&lt;td&gt;Complex logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Performance varies by database — always test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on reusability:&lt;/strong&gt; A CTE can be referenced multiple times within the single query it's defined in. It does not persist across queries — for that you'd need a temporary table or a view.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Simple Decision Rule
&lt;/h2&gt;

&lt;p&gt;If the logic fits in 2–3 lines and you only need it once → subquery.&lt;/p&gt;

&lt;p&gt;If the logic is complex, reused, or needs to be understood by someone else → CTE.&lt;/p&gt;

&lt;p&gt;When in doubt, use a CTE. Readability is worth more than brevity in team environments.&lt;/p&gt;




&lt;p&gt;Which do YOU prefer — CTEs or subqueries? Drop a comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💙&lt;/p&gt;

</description>
      <category>sql</category>
      <category>dataengineering</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>5 Python Built-ins You’re Not Using (But Should Be)</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Fri, 27 Mar 2026 18:36:43 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/5-python-built-ins-youre-not-using-but-should-be-930</link>
      <guid>https://dev.to/neha_christina_1ac8651819/5-python-built-ins-youre-not-using-but-should-be-930</guid>
      <description>&lt;p&gt;You don’t need a library. You don’t need to &lt;code&gt;pip install&lt;/code&gt; anything. These five tools are already sitting inside Python, waiting for you to use them.&lt;/p&gt;

&lt;p&gt;Most junior developers don’t know they exist. Senior developers reach for them every single day.&lt;/p&gt;

&lt;p&gt;Let’s fix that.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. zip()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Loop over multiple lists at the same time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How most beginners do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Carol&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How you should do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&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;score&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&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;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zip()&lt;/code&gt; pairs up elements from two (or more) iterables and lets you loop over them together. It stops when the shortest one runs out.&lt;/p&gt;

&lt;p&gt;You can also use it to combine lists into a dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;name_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {'Alice': 95, 'Bob': 87, 'Carol': 92}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. any() and all()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Check conditions across a list without writing a loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The verbose way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;has_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;has_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clean way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;has_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;all_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;any()&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; if at least one item meets the condition. &lt;code&gt;all()&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; only if every item does. Both short-circuit — they stop checking as soon as the answer is known, which makes them fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. enumerate()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Get the index AND value without range(len()).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The clunky version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fruits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Pythonic version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to start counting from 1 instead of 0?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;range(len())&lt;/code&gt; is considered a code smell in Python. If you find yourself writing it, reach for &lt;code&gt;enumerate()&lt;/code&gt; instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. map()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Apply a function to every item in a list.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The loop version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;carol&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;capitalised&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;capitalised&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;map()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;capitalised&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with a lambda for custom logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;doubled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;map()&lt;/code&gt; is great for simple, single-function transformations. For more complex logic involving conditions or multiple steps, a list comprehension is usually more readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;capitalised&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are valid — pick whichever reads more clearly for your use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. collections
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Counter, defaultdict, deque — all built in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The manual counting approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dog&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bird&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Counter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Counter({'cat': 3, 'dog': 1, 'bird': 1})
&lt;/span&gt;
&lt;span class="n"&gt;top_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;most_common&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="c1"&gt;# [('cat', 3), ('dog', 1)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;collections&lt;/code&gt; also gives you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;defaultdict&lt;/strong&gt; — like a regular dict, but never raises a &lt;code&gt;KeyError&lt;/code&gt;. Automatically creates a default value for missing keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# No KeyError!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;deque&lt;/strong&gt; — a double-ended queue. Much faster than a list for appending or popping from both ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;namedtuple&lt;/strong&gt; — a tuple where you can access items by name instead of index.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Built-in&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zip()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pairs up multiple iterables&lt;/td&gt;
&lt;td&gt;Looping over two lists together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;any()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True if at least one item passes&lt;/td&gt;
&lt;td&gt;Checking if any condition is met&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;all()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True if every item passes&lt;/td&gt;
&lt;td&gt;Validating a whole list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enumerate()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gives index + value&lt;/td&gt;
&lt;td&gt;When you need both in a loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applies a function to each item&lt;/td&gt;
&lt;td&gt;Simple transformations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Counter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counts occurrences&lt;/td&gt;
&lt;td&gt;Frequency analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;defaultdict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dict with default values&lt;/td&gt;
&lt;td&gt;Avoiding KeyErrors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;None of these require installing anything. They’re all part of Python’s standard library, designed specifically for the problems you solve every day.&lt;/p&gt;

&lt;p&gt;The difference between a junior and senior Python developer often isn’t the complex stuff — it’s knowing these small tools exist and reaching for them instead of writing loops from scratch.&lt;/p&gt;




&lt;p&gt;Which one did you NOT know about before reading this? Comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💚&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>programming</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>A Day in the Life of a Data Engineer (Real Talk, No Filter)</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Tue, 24 Mar 2026 11:15:14 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/a-day-in-the-life-of-a-data-engineer-real-talk-no-filter-18cm</link>
      <guid>https://dev.to/neha_christina_1ac8651819/a-day-in-the-life-of-a-data-engineer-real-talk-no-filter-18cm</guid>
      <description>&lt;p&gt;If you've ever wondered what data engineers actually do all day — this one's for you.&lt;/p&gt;

&lt;p&gt;Not the LinkedIn version. The real version.&lt;/p&gt;

&lt;p&gt;I've been in tech for 10+ years and I still get asked "so what do you actually &lt;em&gt;do&lt;/em&gt;?" at family dinners. This post is my answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  9:00 AM — Standup &amp;amp; Morning Triage
&lt;/h2&gt;

&lt;p&gt;First thing I do is &lt;strong&gt;not&lt;/strong&gt; open Slack.&lt;/p&gt;

&lt;p&gt;I open my monitoring dashboard.&lt;/p&gt;

&lt;p&gt;Most pipeline failures happen overnight while nobody's watching. If something broke at 3am, I want to know before the business analyst in another timezone notices their dashboard is showing yesterday's data.&lt;/p&gt;

&lt;p&gt;Once I've checked the alerts I jump into standup. Ours is 15 minutes max. What did I do yesterday, what am I doing today, what's blocking me. That's it.&lt;/p&gt;

&lt;p&gt;Then I triage tickets. Some days I have three things to do. Some days I have fifteen. Prioritising is a skill they don't teach you in bootcamp but you learn fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  9:30 AM — Pipeline Review
&lt;/h2&gt;

&lt;p&gt;This is the unglamorous part of the job that nobody talks about.&lt;/p&gt;

&lt;p&gt;Every morning I check whether last night's ETL jobs completed successfully. Did the data land in Snowflake? Are the row counts what we'd expect? Did any data quality checks fail?&lt;/p&gt;

&lt;p&gt;If something broke, I investigate. This means reading logs, checking error messages, and tracing back through the pipeline to find where it fell over. Sometimes it's a network timeout. Sometimes it's a schema change upstream that nobody told us about. Sometimes it's my own code from two weeks ago coming back to bite me.&lt;/p&gt;

&lt;p&gt;Most pipeline fires happen overnight. Mornings are for damage control before anyone notices.&lt;/p&gt;




&lt;h2&gt;
  
  
  11:00 AM — Deep Work (Headphones On)
&lt;/h2&gt;

&lt;p&gt;This is my favourite part of the day.&lt;/p&gt;

&lt;p&gt;I block this time aggressively. No meetings if I can help it. Headphones on. Do Not Disturb on. This is when actual building happens.&lt;/p&gt;

&lt;p&gt;What does building look like?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing Python ETL scripts.&lt;/strong&gt; Reading data from an API, transforming it, loading it into Snowflake. Handling edge cases. Writing tests so future-me doesn't break it accidentally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building SQL transformations.&lt;/strong&gt; Using dbt to model data in the warehouse — turning raw event data into clean, business-friendly tables that analysts can actually use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging.&lt;/strong&gt; Always debugging. Something that worked last week mysteriously doesn't work today. This is the job.&lt;/p&gt;




&lt;h2&gt;
  
  
  12:30 PM — Code Review
&lt;/h2&gt;

&lt;p&gt;Before I eat lunch I try to clear my code review queue.&lt;/p&gt;

&lt;p&gt;Code review is one of the most valuable things a senior engineer can do for a team. Reading someone else's PR carefully, leaving a thoughtful comment, catching a bug before it hits production — that's real impact.&lt;/p&gt;

&lt;p&gt;I also get my own code reviewed. Fresh eyes catch things you're blind to after staring at the same function for two hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  2:00 PM — Stakeholder Meetings
&lt;/h2&gt;

&lt;p&gt;Yes, data engineers go to meetings. A lot of them.&lt;/p&gt;

&lt;p&gt;The afternoon tends to be meeting-heavy. This might be a sync with the data analytics team to tell them a new dataset is ready. It might be a session with a product manager to understand the business requirements for a new pipeline. It might be a cross-team call about an upcoming data model change that'll affect three different teams.&lt;/p&gt;

&lt;p&gt;The best data engineers I've worked with are great communicators. They can explain a technical concept to a non-technical stakeholder without talking down to them. That skill is worth as much as knowing Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  3:30 PM — Documentation &amp;amp; Mentoring
&lt;/h2&gt;

&lt;p&gt;Nobody loves writing documentation but everyone loves finding it when they need it.&lt;/p&gt;

&lt;p&gt;I try to keep a rule: if I build it, I document it. What does it do, where does the data come from, what should you do if it breaks. Future me and my teammates will thank present me.&lt;/p&gt;

&lt;p&gt;This slot also tends to be when junior engineers come to me with questions. Debugging sessions, code reviews, architecture questions. I was that junior engineer once. Paying it forward matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  4:00 PM — Wrap Up
&lt;/h2&gt;

&lt;p&gt;The last hour is for closing out.&lt;/p&gt;

&lt;p&gt;I look at my ticket list. What did I actually complete today? What's moving to tomorrow? Are there any overnight jobs I need to leave notes about?&lt;/p&gt;

&lt;p&gt;I clear my Slack backlog — not the other way around. Slack is not a real-time obligation. I check it a few times a day, not constantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  4:30 PM — Learning Time (Protected)
&lt;/h2&gt;

&lt;p&gt;This is the 30 minutes most people skip and then wonder why they feel stuck.&lt;/p&gt;

&lt;p&gt;I keep this time for growth. Reading a technical blog post. Watching a conference talk. Experimenting with a new Snowflake feature. Trying out a new Python library.&lt;/p&gt;

&lt;p&gt;The data engineering landscape changes fast. If you're not learning you're falling behind.&lt;/p&gt;




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

&lt;p&gt;Here's what I actually use every day:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowflake&lt;/strong&gt; — cloud data warehouse, where most of our data lives and gets queried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; — ETL scripts, data transformation, automation, anything that needs logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitLab&lt;/strong&gt; — CI/CD pipelines, version control and deployments. Every pipeline change is tracked, reviewed and deployed through GitLab. (You'll see Apache Airflow in a lot of job listings for this role — GitLab is what we use at my company, but Airflow is worth learning too.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git / GitHub&lt;/strong&gt; — version control for everything. Every change tracked, every deployment reviewed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt&lt;/strong&gt; — transforms raw data inside Snowflake using SQL. Makes data modelling collaborative and testable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt; — where everything actually happens. For better or worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Truth Nobody Tells You
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;60% of the job is debugging pipelines that worked yesterday.&lt;/strong&gt; The other 40% is writing the pipelines that will break tomorrow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Communication skills matter as much as coding skills.&lt;/strong&gt; You can write the most elegant Python in the world but if you can't explain your data model to a product manager, you'll be invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll never stop learning.&lt;/strong&gt; The tools change every year. That's frustrating and exciting at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boring days are good days.&lt;/strong&gt; A day where nothing breaks, all pipelines run, and you shipped one clean PR is a successful day. Don't let anyone tell you otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to Become a Data Engineer?
&lt;/h2&gt;

&lt;p&gt;Start with SQL. Then learn Python. Then learn one cloud data warehouse — BigQuery has the most generous free tier if you want to experiment without spending money.&lt;/p&gt;

&lt;p&gt;The path is clearer than it looks from the outside.&lt;/p&gt;




&lt;p&gt;Drop a 🙋 in the comments if you're on your way in — I'd love to know where you're starting from 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💙&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>career</category>
      <category>beginners</category>
      <category>sql</category>
    </item>
    <item>
      <title>Snowflake vs Redshift vs BigQuery: Which One Should You Use?</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Sat, 21 Mar 2026 03:44:27 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/snowflake-vs-redshift-vs-bigquery-which-one-should-you-use-52jf</link>
      <guid>https://dev.to/neha_christina_1ac8651819/snowflake-vs-redshift-vs-bigquery-which-one-should-you-use-52jf</guid>
      <description>&lt;p&gt;If you’ve ever Googled “which cloud data warehouse should I use” and ended up more confused than when you started — this post is for you.&lt;/p&gt;

&lt;p&gt;Snowflake, Redshift, and BigQuery are the three biggest names in cloud data warehousing right now. They all do similar things. They all use SQL. They all run in the cloud. So how do you choose?&lt;/p&gt;

&lt;p&gt;The answer depends on your situation. Let’s break it down.&lt;/p&gt;




&lt;h2&gt;
  
  
  What They All Have in Common
&lt;/h2&gt;

&lt;p&gt;Before the differences, here’s what all three share:&lt;/p&gt;

&lt;p&gt;They store massive amounts of data — we’re talking billions of rows — and let you query it with standard SQL. They all run entirely in the cloud, so there are no servers to buy or maintain. And they’re all used by major companies at scale.&lt;/p&gt;

&lt;p&gt;The differences come down to three things: &lt;strong&gt;cost model&lt;/strong&gt;, &lt;strong&gt;ecosystem&lt;/strong&gt;, and &lt;strong&gt;who they’re built for&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ❄️ Snowflake
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The cloud-agnostic powerhouse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Snowflake is unique because it runs on AWS, Azure, AND Google Cloud. No other major data warehouse does this. That means if your company uses multiple clouds — or might switch clouds in the future — Snowflake gives you flexibility nobody else does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes Snowflake stand out:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separation of storage and compute.&lt;/strong&gt; This is Snowflake’s key architectural insight. Storage and compute are completely independent, so you can scale each one separately and only pay for what you actually use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-cluster warehouses.&lt;/strong&gt; 100 analysts running queries at the same time? Each gets their own compute cluster. No one slows anyone else down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best-in-class data sharing.&lt;/strong&gt; You can share live data with partners or other teams without copying it. This is genuinely impressive and something the other two struggle to match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time Travel.&lt;/strong&gt; Accidentally deleted a table? You can recover it — up to 90 days back depending on your plan. This has saved countless data teams from disaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-Copy Cloning.&lt;/strong&gt; Clone an entire database instantly with no extra storage cost. Perfect for test environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Credit-based, per second of compute. Easy to control once you understand it, but can surprise you if you leave warehouses running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that work across multiple clouds, organisations that need to share data externally, and companies that want maximum flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔴 Redshift
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The AWS native. Powerful if you’re already there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazon Redshift is AWS’s data warehouse, and if your company is already deep in the AWS ecosystem, it’s a natural fit. The integration with S3, Lambda, Glue, Kinesis, and the rest of the AWS stack is seamless in a way that Snowflake and BigQuery simply can’t replicate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes Redshift stand out:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep AWS integration.&lt;/strong&gt; If you’re already using S3 for storage, IAM for access control, or Kinesis for streaming — Redshift plugs in with minimal friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redshift Serverless.&lt;/strong&gt; The newer serverless option means you don’t have to manage clusters anymore. It scales automatically and you pay only for what you use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strong performance for large structured datasets.&lt;/strong&gt; Redshift uses columnar storage and is highly optimised for analytical queries across large volumes of structured data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise security.&lt;/strong&gt; Deep IAM integration, VPC support, encryption at rest and in transit. If your company has strict security requirements, Redshift ticks all the boxes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Traditionally priced per hour for provisioned clusters, which can get expensive at scale. Redshift Serverless is more flexible but costs can still add up. Reserved instances help significantly if you have predictable workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Companies already deeply invested in AWS who want tight integration with the rest of their AWS infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔵 BigQuery
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Google’s beast. Serverless and stupidly fast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BigQuery is Google Cloud’s data warehouse, and it’s genuinely impressive. It’s the most truly serverless of the three — there’s no infrastructure to think about whatsoever. You just query data and Google handles everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes BigQuery stand out:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Truly serverless.&lt;/strong&gt; No clusters, no warehouses, no infrastructure decisions. Just write SQL and run it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generous free tier.&lt;/strong&gt; 10GB of storage and 1TB of query processing free every month. This makes it the best option for learning or for small projects where you don’t want to spend anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BigQuery ML.&lt;/strong&gt; You can train and run machine learning models using SQL — no Python required. This is genuinely unique and incredibly powerful for teams that want ML without a dedicated data science team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looker Studio integration.&lt;/strong&gt; Connects directly to Google’s BI and visualisation tools with zero setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Pay per query based on data scanned. This sounds great until someone writes a poorly optimised query on a huge table and gets a surprise bill. Always use &lt;code&gt;LIMIT&lt;/code&gt; and partition your tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; GCP-native teams, data science and ML workloads, anyone who wants to start for free, and teams already using Google Workspace or Looker.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;❄️ Snowflake&lt;/th&gt;
&lt;th&gt;🔴 Redshift&lt;/th&gt;
&lt;th&gt;🔵 BigQuery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Any (AWS/Azure/GCP)&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;td&gt;GCP only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Per second&lt;/td&gt;
&lt;td&gt;Per hour&lt;/td&gt;
&lt;td&gt;Per query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;30-day trial&lt;/td&gt;
&lt;td&gt;2 months&lt;/td&gt;
&lt;td&gt;Always free (1TB/mo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Very easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data sharing&lt;/td&gt;
&lt;td&gt;Best in class&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Which One Should You Pick?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick Snowflake if&lt;/strong&gt; you work across multiple clouds, your team needs to share data externally, or you want maximum long-term flexibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Redshift if&lt;/strong&gt; your company is already all-in on AWS and you need tight integration with the rest of the AWS ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick BigQuery if&lt;/strong&gt; you’re on GCP, you want built-in ML capabilities, or you want to start completely free.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;If you’re just starting out and want to learn, &lt;strong&gt;BigQuery&lt;/strong&gt; is the easiest entry point — free tier, no setup, and you can have your first query running in minutes.&lt;/p&gt;

&lt;p&gt;If you’re job hunting in data engineering, &lt;strong&gt;Snowflake&lt;/strong&gt; is the one showing up most in job listings right now. It’s worth learning regardless of which one your company uses.&lt;/p&gt;

&lt;p&gt;If you’re at an AWS-heavy company, &lt;strong&gt;Redshift&lt;/strong&gt; is likely already in your stack — and getting good at it will make you immediately useful to your team.&lt;/p&gt;




&lt;p&gt;Which one does your company use? Drop a comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💙&lt;/p&gt;

</description>
      <category>sql</category>
      <category>beginners</category>
      <category>snowflake</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>What is Snowflake? A Beginner's Guide to the Cloud Data Warehouse Everyone's Talking About</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Wed, 11 Mar 2026 15:31:16 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/what-is-snowflake-a-beginners-guide-to-the-cloud-data-warehouse-everyones-talking-about-42j3</link>
      <guid>https://dev.to/neha_christina_1ac8651819/what-is-snowflake-a-beginners-guide-to-the-cloud-data-warehouse-everyones-talking-about-42j3</guid>
      <description>&lt;p&gt;If you've seen "Snowflake" in job listings and had no idea what it meant — you're not alone.&lt;/p&gt;

&lt;p&gt;It shows up everywhere in data engineering, data analytics, and even business intelligence roles. But nobody ever explains what it actually &lt;em&gt;is&lt;/em&gt; in plain English.&lt;/p&gt;

&lt;p&gt;Until now.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Snowflake?
&lt;/h2&gt;

&lt;p&gt;Snowflake is a &lt;strong&gt;cloud-based data warehouse&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the technical answer. Here's the human one:&lt;/p&gt;

&lt;p&gt;Think of a regular database like a filing cabinet in your office. It works fine when you have a few thousand files. But what happens when you have &lt;em&gt;billions&lt;/em&gt; of files, hundreds of people searching at the same time, and the cabinet needs to grow overnight?&lt;/p&gt;

&lt;p&gt;It breaks.&lt;/p&gt;

&lt;p&gt;Snowflake is the solution. It's a giant, intelligent filing warehouse that lives in the cloud — it can grow instantly, never slows down under pressure, and lets hundreds of people query it simultaneously without fighting over resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  How is it Different from a Regular Database?
&lt;/h2&gt;

&lt;p&gt;Traditional databases like MySQL or PostgreSQL were designed for a different era. They work great for small-to-medium workloads, but they hit a wall when data gets big.&lt;/p&gt;

&lt;p&gt;Here's what happens with traditional databases at scale:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They get slow.&lt;/strong&gt; Query a table with billions of rows and you might be waiting minutes — or hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're hard to scale.&lt;/strong&gt; Need more capacity? You have to physically buy and set up new servers. That takes time and money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They struggle with many users.&lt;/strong&gt; The more people running queries at once, the slower it gets for everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're expensive to maintain.&lt;/strong&gt; You need a dedicated database administrator just to keep things running smoothly.&lt;/p&gt;

&lt;p&gt;Snowflake was built from the ground up to solve all four of these problems at once.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Snowflake Solves It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Massively parallel processing.&lt;/strong&gt; When you run a query, Snowflake doesn't run it on one machine. It splits the work across thousands of servers simultaneously. A query that would take 10 minutes on a traditional database can take seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-scaling.&lt;/strong&gt; Need more compute power? Snowflake spins up additional capacity in seconds — automatically. When you're done, it scales back down. You only pay for what you use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-cluster warehouses.&lt;/strong&gt; Snowflake can run multiple compute clusters at the same time. 100 analysts running queries simultaneously? Each gets their own resources. No one slows anyone else down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separation of storage and compute.&lt;/strong&gt; This is the key architectural insight that makes everything else possible. In a traditional database, storage and compute are tightly coupled. In Snowflake, they're completely separate — which means you can scale each one independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  5 Snowflake Terms You'll Hear Everywhere
&lt;/h2&gt;

&lt;p&gt;Once you start working with Snowflake, these five terms come up constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual Warehouse&lt;/strong&gt; — This is the compute engine. When you run a query, a virtual warehouse does the actual processing. You can have multiple virtual warehouses for different teams or workloads, and they don't interfere with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database &amp;amp; Schema&lt;/strong&gt; — Just like in any SQL database, you organize your data into databases and schemas (think of them as folders and subfolders). Your tables live inside schemas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time Travel&lt;/strong&gt; — If someone accidentally deletes a table or overwrites data, you can travel back in time and recover it — up to 90 days back on some plans. This has saved many data teams from disaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-Copy Cloning&lt;/strong&gt; — You can clone an entire database, schema, or table instantly with no additional storage cost. Snowflake doesn't duplicate the actual data; it just creates a pointer to the same underlying data. Perfect for creating test environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowpipe&lt;/strong&gt; — Snowflake's continuous data ingestion service. Instead of loading data in batches, Snowpipe automatically loads new data into Snowflake as soon as it arrives in your cloud storage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Should You Learn Snowflake?
&lt;/h2&gt;

&lt;p&gt;Short answer: yes.&lt;/p&gt;

&lt;p&gt;If you're working in data — or want to work in data — Snowflake is one of the most valuable skills you can add to your toolkit right now.&lt;/p&gt;

&lt;p&gt;Here's why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's in thousands of job listings.&lt;/strong&gt; Search for data engineer, data analyst, or analytics engineer on any job board and Snowflake shows up constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's growing fast.&lt;/strong&gt; Snowflake is now used by over 8,000 companies including Netflix, Adobe, Capital One, DoorDash, and Pfizer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your SQL already works.&lt;/strong&gt; If you know &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, and &lt;code&gt;GROUP BY&lt;/code&gt; — you already know how to query Snowflake. The learning curve is much lower than people expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It commands higher salaries.&lt;/strong&gt; Roles that list Snowflake as a requirement consistently pay more than equivalent roles that don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;The best way to start is free:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to app.snowflake.com and sign up for a 30-day free trial — no credit card required&lt;/li&gt;
&lt;li&gt;Create a virtual warehouse (just click through the setup wizard)&lt;/li&gt;
&lt;li&gt;Load a sample dataset (Snowflake provides several built-in ones)&lt;/li&gt;
&lt;li&gt;Start querying with SQL you already know&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Within an hour you'll have run your first Snowflake query and the whole thing will feel far less intimidating.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In a future post I'll cover how Snowflake compares to other cloud data warehouses like BigQuery and Redshift — and when to use each one.&lt;/p&gt;

&lt;p&gt;For now: Snowflake is not as scary as it sounds, and if you already know SQL, you're more than halfway there.&lt;/p&gt;




&lt;p&gt;Which Snowflake feature surprised you most? Drop a comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💙&lt;/p&gt;

</description>
      <category>snowflake</category>
      <category>dataengineering</category>
      <category>sql</category>
      <category>beginners</category>
    </item>
    <item>
      <title>SQL Window Functions Don't Have to Be Scary 🪟</title>
      <dc:creator>Neha Christina</dc:creator>
      <pubDate>Mon, 09 Mar 2026 11:01:18 +0000</pubDate>
      <link>https://dev.to/neha_christina_1ac8651819/sql-window-functions-dont-have-to-be-scary-146d</link>
      <guid>https://dev.to/neha_christina_1ac8651819/sql-window-functions-dont-have-to-be-scary-146d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you've ever seen &lt;code&gt;OVER (PARTITION BY ... ORDER BY ...)&lt;/code&gt; in a SQL query and quietly closed the tab — this post is for you.&lt;/p&gt;

&lt;p&gt;Window functions are one of those topics that look intimidating at first but become incredibly powerful once they click. And once you learn them, you'll wonder how you ever wrote SQL without them.&lt;/p&gt;

&lt;p&gt;By the end of this post you'll understand 5 essential window functions, when to use each one, and exactly how they differ from each other.&lt;/p&gt;

&lt;p&gt;Let's go.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Even Is a Window Function?
&lt;/h2&gt;

&lt;p&gt;A regular aggregate function like &lt;code&gt;SUM()&lt;/code&gt; or &lt;code&gt;COUNT()&lt;/code&gt; collapses your rows into a single result. Window functions do calculations &lt;strong&gt;across rows — without collapsing them.&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="c1"&gt;-- Regular aggregate: collapses all rows into one&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;dept&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;salary&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;employees&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;dept&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Window function: keeps all rows, adds a calculated column&lt;/span&gt;
&lt;span class="k"&gt;SELECT&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;dept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dept_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference: &lt;code&gt;GROUP BY&lt;/code&gt; gives you one row per group. Window functions give you &lt;strong&gt;all your rows back&lt;/strong&gt;, plus a new calculated column alongside them.&lt;/p&gt;

&lt;p&gt;The syntax always follows this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt;   &lt;span class="c1"&gt;-- optional: defines the group&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt;       &lt;span class="c1"&gt;-- optional: defines row order within the group&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Function #1: ROW_NUMBER()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assigns a unique sequential number to each row within a partition. No ties — every row gets a different number.&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;row_num&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;dept&lt;/th&gt;
&lt;th&gt;salary&lt;/th&gt;
&lt;th&gt;row_num&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Eng&lt;/td&gt;
&lt;td&gt;90k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Eng&lt;/td&gt;
&lt;td&gt;85k&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;70k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;65k&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even if Alice and Bob had the same salary, they'd still get different row numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the most recent record per user (filter where &lt;code&gt;row_num = 1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Deduplicate data&lt;/li&gt;
&lt;li&gt;Paginate query results&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Function #2: RANK()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Like ROW_NUMBER, but ties get the SAME rank — and the next rank skips.&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;name&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="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;scores&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;name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice rank 2 is missing. Alice and Bob both scored 95 so they share rank 1 — and the next rank jumps to 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ranking students by exam score&lt;/li&gt;
&lt;li&gt;Ranking products by sales volume&lt;/li&gt;
&lt;li&gt;Any scenario where ties are meaningful and gaps are acceptable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Function #3: DENSE_RANK()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Like RANK, but NO gaps after ties.&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;name&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="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dense_rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;scores&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;name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same data — but now Carol gets rank 2, not rank 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RANK vs DENSE_RANK — the simple rule:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;RANK()&lt;/code&gt; when gaps make sense (sports — if two people tie for gold, no one gets silver)&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;DENSE_RANK()&lt;/code&gt; when gaps look wrong (medal tables, internal employee levels)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Function #4: LAG()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lets you look at the value from the PREVIOUS row.&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="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prev_revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&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;month&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;th&gt;prev_revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jan&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb&lt;/td&gt;
&lt;td&gt;12k&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mar&lt;/td&gt;
&lt;td&gt;11k&lt;/td&gt;
&lt;td&gt;12k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr&lt;/td&gt;
&lt;td&gt;15k&lt;/td&gt;
&lt;td&gt;11k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;January has &lt;code&gt;NULL&lt;/code&gt; for &lt;code&gt;prev_revenue&lt;/code&gt; because there's no row before it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Month-over-month or year-over-year comparisons&lt;/li&gt;
&lt;li&gt;Calculating the change between consecutive rows&lt;/li&gt;
&lt;li&gt;Detecting gaps or jumps in a time series&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Function #5: LEAD()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Like LAG — but looks at the NEXT row instead.&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="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;next_revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&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;month&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;th&gt;next_revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jan&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;td&gt;12k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb&lt;/td&gt;
&lt;td&gt;12k&lt;/td&gt;
&lt;td&gt;11k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mar&lt;/td&gt;
&lt;td&gt;11k&lt;/td&gt;
&lt;td&gt;15k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr&lt;/td&gt;
&lt;td&gt;15k&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;April has &lt;code&gt;NULL&lt;/code&gt; because there's no row after it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Ties&lt;/th&gt;
&lt;th&gt;Gaps&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Different numbers&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Deduplication, pagination, latest record per group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same rank&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Sports rankings, exams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same rank&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Leaderboards, levels, medal tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LAG()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Compare to previous row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LEAD()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Compare to next row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  One Thing to Remember
&lt;/h2&gt;

&lt;p&gt;All window functions follow the same structure:&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;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;-- your "group by" (optional)&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;       &lt;span class="c1"&gt;-- row order within the group (optional)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; is like &lt;code&gt;GROUP BY&lt;/code&gt; — but it doesn't collapse your rows. &lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER()&lt;/code&gt; controls how rows are ordered &lt;strong&gt;within each window&lt;/strong&gt;, independently of the query's own &lt;code&gt;ORDER BY&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Once you're comfortable with these 5, explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SUM()&lt;/code&gt;, &lt;code&gt;AVG()&lt;/code&gt;, &lt;code&gt;COUNT()&lt;/code&gt; as window functions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NTILE()&lt;/code&gt; for splitting rows into buckets&lt;/li&gt;
&lt;li&gt;Frame clauses like &lt;code&gt;ROWS BETWEEN&lt;/code&gt; for rolling calculations&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Which window function confused you the most before reading this? Drop a comment below 👇&lt;/p&gt;

&lt;p&gt;Follow me on Instagram at &lt;a href="https://www.instagram.com/techqueen.codes" rel="noopener noreferrer"&gt;https://www.instagram.com/techqueen.codes&lt;/a&gt; for visual SQL, Python and Snowflake tips every week 💙&lt;/p&gt;

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