<?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: Slava Rozhnev</title>
    <description>The latest articles on DEV Community by Slava Rozhnev (@rozhnev).</description>
    <link>https://dev.to/rozhnev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F980305%2F419de9d3-4790-4b03-9eca-596e6070dc1d.jpg</url>
      <title>DEV Community: Slava Rozhnev</title>
      <link>https://dev.to/rozhnev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rozhnev"/>
    <language>en</language>
    <item>
      <title>Why `SUM() OVER (ORDER BY ...)` Sometimes Feels Wrong: A Practical Guide to SQL Window Frames</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Wed, 11 Mar 2026 19:06:35 +0000</pubDate>
      <link>https://dev.to/rozhnev/why-sum-over-order-by-sometimes-feels-wrong-a-practical-guide-to-sql-window-frames-1iga</link>
      <guid>https://dev.to/rozhnev/why-sum-over-order-by-sometimes-feels-wrong-a-practical-guide-to-sql-window-frames-1iga</guid>
      <description>&lt;p&gt;Window functions in SQL can make you feel productive very quickly. You learn &lt;code&gt;PARTITION BY&lt;/code&gt;, add &lt;code&gt;ORDER BY&lt;/code&gt;, use &lt;code&gt;ROW_NUMBER()&lt;/code&gt;, &lt;code&gt;RANK()&lt;/code&gt;, and running totals, and it feels like you already have the mental model.&lt;/p&gt;

&lt;p&gt;That was exactly my mistake.&lt;/p&gt;

&lt;p&gt;For a while, I thought I understood window functions well enough because my queries were working and the results looked plausible. The confusion only started later, when I began getting results that were syntactically correct but did not match what I expected logically.&lt;/p&gt;

&lt;p&gt;A classic example looks like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You expect a normal running total. Instead, the result suddenly jumps by multiple rows at once. No SQL error. No broken query. The database is doing exactly what you asked.&lt;/p&gt;

&lt;p&gt;The missing piece is usually the same: &lt;strong&gt;the window frame&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The frame determines &lt;strong&gt;which rows around the current row are actually included in the calculation&lt;/strong&gt;. Until that part clicks, window functions are easy to copy from memory but hard to control precisely.&lt;/p&gt;

&lt;p&gt;For me, understanding frames was the point where window functions stopped feeling like a bag of handy tricks and started feeling like a consistent system.&lt;/p&gt;

&lt;p&gt;When I want to test behavior like this quickly, I usually run the queries in a live environment. For quick experiments I use &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;sqlize.online&lt;/a&gt;, and for more structured SQL practice and lessons I publish material on &lt;a href="https://sqltest.online" rel="noopener noreferrer"&gt;sqltest.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I want to walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what a window frame actually is;&lt;/li&gt;
&lt;li&gt;the difference between &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;how boundaries like &lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt;, &lt;code&gt;CURRENT ROW&lt;/code&gt;, and &lt;code&gt;n FOLLOWING&lt;/code&gt; work;&lt;/li&gt;
&lt;li&gt;why the default frame can surprise you;&lt;/li&gt;
&lt;li&gt;how to write running totals and moving averages without hidden assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a window frame is
&lt;/h2&gt;

&lt;p&gt;When we write a window function, we usually see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;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;payment_date&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first started using queries like this, I mentally translated them as: “the function is calculated over the whole &lt;code&gt;customer_id&lt;/code&gt; partition in &lt;code&gt;payment_date&lt;/code&gt; order.”&lt;/p&gt;

&lt;p&gt;That is not quite right.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; defines &lt;strong&gt;which partition the window works inside&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ORDER BY&lt;/code&gt; defines &lt;strong&gt;the order of rows inside that partition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;window frame&lt;/strong&gt; defines &lt;strong&gt;which subset of rows from that partition is used for the current row’s calculation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The full shape 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="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="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="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="p"&gt;...]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;GROUPS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;AND&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;So the mental model is really three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the partition.&lt;/li&gt;
&lt;li&gt;Define the ordering inside that partition.&lt;/li&gt;
&lt;li&gt;For each row, define the frame: where it starts and where it ends.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;The same &lt;code&gt;SUM()&lt;/code&gt; can mean completely different things depending on the frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a running total from the start of the partition to the current row;&lt;/li&gt;
&lt;li&gt;a 3-row sliding window;&lt;/li&gt;
&lt;li&gt;an average across the current row and future rows;&lt;/li&gt;
&lt;li&gt;a full-partition aggregate without collapsing rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the outside, these queries can look very similar. Their behavior is not similar at all. That is why window functions can seem simple right up until you hit the first result that looks weird in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame boundaries
&lt;/h2&gt;

&lt;p&gt;A frame is usually defined with &lt;code&gt;BETWEEN ... AND ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The available boundaries are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt; — start from the first row in the partition;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n PRECEDING&lt;/code&gt; — go &lt;code&gt;n&lt;/code&gt; rows or logical steps backward;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CURRENT ROW&lt;/code&gt; — the current row;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n FOLLOWING&lt;/code&gt; — go &lt;code&gt;n&lt;/code&gt; rows or logical steps forward;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt; — continue to the last row in the partition.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the classic running-total frame: from the start of the partition up to the current row.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt;: the real difference
&lt;/h2&gt;

&lt;p&gt;This is where things usually become interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ROWS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ROWS&lt;/code&gt; works with &lt;strong&gt;physical rows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you write:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;that always means exactly three rows:&lt;/p&gt;

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

&lt;p&gt;It does not matter whether those rows have the same &lt;code&gt;ORDER BY&lt;/code&gt; value or not. The count is based on row positions.&lt;/p&gt;

&lt;p&gt;This is the most predictable mode. In most practical analytics work, especially when I need a fixed-width sliding window, &lt;code&gt;ROWS&lt;/code&gt; is usually where I start.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;RANGE&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RANGE&lt;/code&gt; works with a &lt;strong&gt;logical value range&lt;/strong&gt;, not physical rows.&lt;/p&gt;

&lt;p&gt;If multiple rows share the same &lt;code&gt;ORDER BY&lt;/code&gt; value, they can enter the frame together as one logical group.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;RANGE&lt;/code&gt; often surprises people.&lt;/p&gt;

&lt;p&gt;The most important detail is this: if you specify &lt;code&gt;ORDER BY&lt;/code&gt; but do &lt;strong&gt;not&lt;/strong&gt; define a frame explicitly, many databases use this default:&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;RANGE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the calculation includes not only all rows before the current row, but also &lt;strong&gt;all rows that share the same ordering value as the current row&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;ORDER BY&lt;/code&gt; column contains duplicates, the result can jump more than you expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;GROUPS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GROUPS&lt;/code&gt; works with &lt;strong&gt;peer groups&lt;/strong&gt; of equal &lt;code&gt;ORDER BY&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;ROWS&lt;/code&gt; counts rows and &lt;code&gt;RANGE&lt;/code&gt; thinks in logical value ranges, &lt;code&gt;GROUPS&lt;/code&gt; counts groups of equal values.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;GROUPS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means: take the current peer group and the previous peer group as whole units.&lt;/p&gt;

&lt;p&gt;This is useful when your mental model is based on equal-value groups rather than individual rows. PostgreSQL supports it. Support in MySQL and MariaDB is more limited depending on version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: running total
&lt;/h2&gt;

&lt;p&gt;Let’s start with the most common case.&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="n"&gt;payment_date&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;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="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;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;payment_date&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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;1&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;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows are partitioned by &lt;code&gt;customer_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;rows inside the partition are ordered by &lt;code&gt;payment_date&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;for each row, the sum runs from the first row in the partition up to the current row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the explicit, correct running-total pattern.&lt;/p&gt;

&lt;p&gt;In my own queries, I almost always write this frame explicitly, even if the database would happen to return the expected result without it. Writing the frame makes the behavior obvious and protects you from subtle surprises later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: moving average
&lt;/h2&gt;

&lt;p&gt;Now let’s use a fixed-width window:&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="n"&gt;payment_date&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="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;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;payment_date&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;moving_avg_3&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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;1&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;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each row, the frame includes:&lt;/p&gt;

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

&lt;p&gt;So the maximum frame width is three rows.&lt;/p&gt;

&lt;p&gt;This is a typical case where &lt;code&gt;ROWS&lt;/code&gt; is the right choice and &lt;code&gt;RANGE&lt;/code&gt; is not. The goal is a fixed number of rows, not a logical expansion around equal values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: looking forward
&lt;/h2&gt;

&lt;p&gt;Window functions can look ahead as well:&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="n"&gt;payment_date&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="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;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;payment_date&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;forward_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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;1&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;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of frame is useful for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smoothing;&lt;/li&gt;
&lt;li&gt;local trend analysis;&lt;/li&gt;
&lt;li&gt;short forward-looking comparisons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Near the end of the partition, the frame naturally shrinks because there are no future rows left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 4: full-partition aggregate without &lt;code&gt;GROUP BY&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to keep row-level detail and still show a partition-level aggregate next to each 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="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&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="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;customer_id&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This behaves a bit like &lt;code&gt;GROUP BY customer_id&lt;/code&gt;, except rows are not collapsed. You still see every row, with the partition average attached to each one.&lt;/p&gt;

&lt;p&gt;That pattern is useful when you want to compare a row against its wider context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deviation from average;&lt;/li&gt;
&lt;li&gt;share of total;&lt;/li&gt;
&lt;li&gt;comparison with a partition maximum or minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The main trap: &lt;code&gt;ROWS&lt;/code&gt; and &lt;code&gt;RANGE&lt;/code&gt; can produce different running totals
&lt;/h2&gt;

&lt;p&gt;Suppose you have multiple rows with the same &lt;code&gt;amount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compare these two expressions:&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="n"&gt;amount&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="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;amount&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sum_rows&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="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;amount&lt;/span&gt;
        &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sum_range&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;amount = 11.99&lt;/code&gt; appears multiple times, the behavior changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ROWS&lt;/code&gt; counts one physical row at a time;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RANGE&lt;/code&gt; includes all rows with the same &lt;code&gt;amount&lt;/code&gt; together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why a running total based on &lt;code&gt;RANGE&lt;/code&gt; can jump several rows at once when there are duplicates in the ordering column.&lt;/p&gt;

&lt;p&gt;This is one of the most common sources of confusion I see with window functions. The query is valid. The database is right. The expectation was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I use &lt;code&gt;ROWS&lt;/code&gt; vs &lt;code&gt;RANGE&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My rule of thumb is simple.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;ROWS&lt;/code&gt; when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;row-by-row running totals;&lt;/li&gt;
&lt;li&gt;moving averages based on a fixed number of rows;&lt;/li&gt;
&lt;li&gt;predictable incremental behavior;&lt;/li&gt;
&lt;li&gt;analytics where each physical row matters separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;RANGE&lt;/code&gt; when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calculations based on logical value ranges;&lt;/li&gt;
&lt;li&gt;tied &lt;code&gt;ORDER BY&lt;/code&gt; values to be treated together;&lt;/li&gt;
&lt;li&gt;behavior tied to the ordering value itself rather than row count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;GROUPS&lt;/code&gt; when the right mental model is “peer groups as units.”&lt;/p&gt;

&lt;p&gt;If I am not sure, I almost always start with &lt;code&gt;ROWS&lt;/code&gt;. It is the most predictable option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Named windows
&lt;/h2&gt;

&lt;p&gt;When the same window definition is reused several times in one query, things get noisy fast. That is where the &lt;code&gt;WINDOW&lt;/code&gt; clause helps:&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="n"&gt;payment_date&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;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="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_total&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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_avg&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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;payment_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&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;1&lt;/span&gt;
&lt;span class="k"&gt;WINDOW&lt;/span&gt; &lt;span class="n"&gt;w&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;PARTITION&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;payment_date&lt;/span&gt;
    &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why I like this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less duplication;&lt;/li&gt;
&lt;li&gt;lower chance of mistakes;&lt;/li&gt;
&lt;li&gt;easier maintenance;&lt;/li&gt;
&lt;li&gt;the logic of the window lives in one place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a query contains several window functions, named windows usually make it much easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical reporting pattern: daily sales
&lt;/h2&gt;

&lt;p&gt;One of the most useful patterns for reporting and dashboards looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&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;payment_day&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;daily_total&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;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="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cumulative_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&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;rolling_7day_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&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;payment_day&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you two very useful metrics immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cumulative_total&lt;/code&gt; for the running total;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rolling_7day_avg&lt;/code&gt; for a 7-day moving average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the &lt;code&gt;SUM(SUM(amount)) OVER (...)&lt;/code&gt; pattern: first we aggregate by day, then we apply a window function over the grouped result.&lt;/p&gt;

&lt;p&gt;I like this example because it shows the practical value of frames very quickly. In one query, you get accumulation, smoothing, and a solid base for a chart or dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this topic kept breaking for me
&lt;/h2&gt;

&lt;p&gt;If I reduce my own mistakes here to a short list, they usually came from three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mentally substituting “the whole partition” where only the current frame was actually used;&lt;/li&gt;
&lt;li&gt;forgetting that &lt;code&gt;RANGE&lt;/code&gt; does not behave like &lt;code&gt;ROWS&lt;/code&gt; when &lt;code&gt;ORDER BY&lt;/code&gt; values are duplicated;&lt;/li&gt;
&lt;li&gt;relying too long on defaults instead of writing the frame explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I started asking one extra question for every window query, most of the confusion went away:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which exact rows should be included in the calculation for the current row?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question alone removes a lot of the magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is worth remembering
&lt;/h2&gt;

&lt;p&gt;A window frame is not about the partition itself and not about ordering by itself. It is specifically about &lt;strong&gt;the boundaries of rows included in the current calculation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PARTITION BY&lt;/code&gt; splits data into partitions;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; defines order inside a partition;&lt;/li&gt;
&lt;li&gt;the frame defines which rows around the current row are included in the calculation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three most useful patterns to remember are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running total:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;full partition:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;3-row sliding window:
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;p&gt;And the most important trap is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if you have &lt;code&gt;ORDER BY&lt;/code&gt; but no explicit frame, many databases will use &lt;code&gt;RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which means duplicate ordering values can change the result in ways that are easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Window functions only became intuitive for me once I stopped thinking in terms of just partitions and ordering and started thinking in terms of &lt;strong&gt;frames&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want to write analytical SQL with confidence, it is worth building one habit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not rely on the default frame;&lt;/li&gt;
&lt;li&gt;write it explicitly;&lt;/li&gt;
&lt;li&gt;choose between &lt;code&gt;ROWS&lt;/code&gt; and &lt;code&gt;RANGE&lt;/code&gt; on purpose;&lt;/li&gt;
&lt;li&gt;remember that ties in &lt;code&gt;ORDER BY&lt;/code&gt; change frame behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I had to leave one practical takeaway from this entire article, it would be this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you write a window function, do not think only about &lt;code&gt;PARTITION BY&lt;/code&gt; and &lt;code&gt;ORDER BY&lt;/code&gt;. Ask one more question: which exact rows should participate in the calculation for the current row?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that answer is explicit, window queries become much more reliable and much easier to reason about.&lt;/p&gt;

&lt;p&gt;If you want to experiment with &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt; directly, &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;sqlize.online&lt;/a&gt; is the easiest place to test queries quickly. If you want a more structured way to study SQL through lessons and practice, I publish that work on &lt;a href="https://sqltest.online" rel="noopener noreferrer"&gt;sqltest.online&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Make Your Technical Tutorials Interactive with SQLize Embed</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 20 Jan 2026 13:19:48 +0000</pubDate>
      <link>https://dev.to/rozhnev/make-your-technical-tutorials-interactive-with-sqlize-embed-1eo3</link>
      <guid>https://dev.to/rozhnev/make-your-technical-tutorials-interactive-with-sqlize-embed-1eo3</guid>
      <description>&lt;p&gt;If you've ever written a SQL tutorial or database documentation, you know the struggle. You provide a beautiful code snippet, but for your readers to actually &lt;em&gt;see&lt;/em&gt; it in action, they have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the code.&lt;/li&gt;
&lt;li&gt;Open their local terminal or a heavy IDE.&lt;/li&gt;
&lt;li&gt;Set up a database schema.&lt;/li&gt;
&lt;li&gt;Run the code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most readers won't do it. They just keep scrolling.&lt;/p&gt;

&lt;p&gt;Today, I'm excited to introduce &lt;strong&gt;SQLize Embed&lt;/strong&gt;—a lightweight, responsive, and powerful way to embed live SQL sandboxes directly into your blog, documentation, or educational site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SQLize Embed?
&lt;/h2&gt;

&lt;p&gt;SQLize Embed is a client-side library that transforms static &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements into fully functional SQL editors. Powered by the &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt; engine, it allows users to write and execute SQL against real database instances without leaving your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;20+ Database Engines:&lt;/strong&gt; Supports everything from the classics like &lt;strong&gt;MySQL 8.0/9.3&lt;/strong&gt;, &lt;strong&gt;PostgreSQL (14-18)&lt;/strong&gt;, and &lt;strong&gt;SQLite 3&lt;/strong&gt;, to enterprise giants like &lt;strong&gt;MS SQL Server (2017-2025)&lt;/strong&gt; and &lt;strong&gt;Oracle 23ai&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ready-to-Use Datasets:&lt;/strong&gt; Want to demo a JOIN? Use preloaded databases like &lt;strong&gt;Sakila (MySQL/MariaDB)&lt;/strong&gt;, &lt;strong&gt;OpenFlights&lt;/strong&gt;, or &lt;strong&gt;AdventureWorks (MS SQL)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Modern Editor Experience:&lt;/strong&gt; Powered by the &lt;strong&gt;Ace Editor&lt;/strong&gt;, providing syntax highlighting, auto-indentation, and a professional coding feel.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Responsive &amp;amp; Lightweight:&lt;/strong&gt; Works seamlessly on mobile and desktop.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Read-Only Mode:&lt;/strong&gt; Perfect for strictly showing examples that you want users to run but not modify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started in 30 Seconds
&lt;/h2&gt;

&lt;p&gt;Adding a live SQL sandbox to your site is as easy as adding a YouTube video.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Include the Script
&lt;/h3&gt;

&lt;p&gt;Add this script tag to your site's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; or before the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://sqlize.online/js/sqlize-embed.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add Your Sandbox
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;div&lt;/code&gt; with the &lt;code&gt;data-sqlize-editor&lt;/code&gt; attribute. Specify your preferred database version and initial code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-sqlize-editor&lt;/span&gt; &lt;span class="na"&gt;data-sql-version=&lt;/span&gt;&lt;span class="s"&gt;"mysql80"&lt;/span&gt; &lt;span class="na"&gt;code-rows=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
-- Create a sample table
CREATE TABLE dev_to_fans (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100)
);

-- Insert data
INSERT INTO dev_to_fans (username) VALUES ('awesome_dev'), ('sql_ninja');

-- Run it!
SELECT * FROM dev_to_fans;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Configuration
&lt;/h2&gt;

&lt;p&gt;You can customize the appearance and behavior of the sandbox using simple HTML attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-sql-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DB engine (e.g., &lt;code&gt;psql17&lt;/code&gt;, &lt;code&gt;mssql2025&lt;/code&gt;, &lt;code&gt;clickhouse&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mysql80&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code-rows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The height of the editor in lines&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;result-rows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The height of the result area&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-read-only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;true&lt;/code&gt; to disable editing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Interactive Learning:&lt;/strong&gt; Build a "SQL 101" course where users solve challenges directly in the browser.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Documentation:&lt;/strong&gt; Stop using screenshots of tables. Let users run &lt;code&gt;DESCRIBE table&lt;/code&gt; themselves.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Technical Blogs:&lt;/strong&gt; Show off complex PostgreSQL window functions or the new MariaDB Vector types with live examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try the Live Demo
&lt;/h2&gt;

&lt;p&gt;Check out the live documentation and various examples here: &lt;br&gt;
👉 &lt;a href="https://sqlize.online/embed-example" rel="noopener noreferrer"&gt;SQLize Embed Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 &lt;a href="https://querynomic.one/#/resources/core" rel="noopener noreferrer"&gt;SQLize Embed Showcase&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Join the Community!
&lt;/h3&gt;

&lt;p&gt;We are constantly adding support for new database versions (we already have MS SQL Server 2025!). If you have a specific database or dataset you'd like to see, let us know in the comments!&lt;/p&gt;

&lt;p&gt;Happy coding! 💻&lt;/p&gt;

&lt;h1&gt;
  
  
  sql #database #webdev #tutorial #productivity #programming
&lt;/h1&gt;

</description>
      <category>sql</category>
      <category>documentation</category>
      <category>mariadb</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Debunking the Myth: Is JOIN Always Faster Than Correlated Subqueries?</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 11 Nov 2025 19:18:20 +0000</pubDate>
      <link>https://dev.to/rozhnev/debunking-the-myth-is-join-always-faster-than-correlated-subqueries-1m05</link>
      <guid>https://dev.to/rozhnev/debunking-the-myth-is-join-always-faster-than-correlated-subqueries-1m05</guid>
      <description>&lt;p&gt;Hey there, fellow developers! If you've ever dabbled in SQL, you've probably heard the golden rule: "Never use correlated subqueries in SELECT—they're a recipe for N+1 disasters!" Instead, we're told to always opt for JOINs because they're set-based, efficient, and lightning-fast.&lt;/p&gt;

&lt;p&gt;But is this rule set in stone? I decided to put it to the test across four popular database systems: MySQL 8.0, Oracle 23c, PostgreSQL 16, and SQLite 3.45. Spoiler alert: The results were eye-opening. Sometimes, the "bad" correlated subquery outperformed the "good" JOIN. Let's dive in and see why.&lt;/p&gt;

&lt;p&gt;The Test Setup: Customers and Orders&lt;br&gt;
To keep things fair, I used a simple schema with two tables:&lt;/p&gt;

&lt;p&gt;customers: A small table with 25 rows of customer data.&lt;br&gt;
orders: A larger table with 1,000 rows of orders, linked via a foreign key.&lt;br&gt;
The goal? Count the number of orders per customer, including those with zero orders.&lt;/p&gt;

&lt;p&gt;Here's the schema (using MySQL syntax for reference):&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;-- Customers 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;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Orders 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&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Data was populated with random values to simulate real-world scenarios.&lt;/p&gt;

&lt;p&gt;The Two Queries: JOIN vs. Correlated Subquery&lt;br&gt;
I compared two approaches to achieve the same result.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "Good" Way – JOIN + GROUP BY
This is the set-based, relational approach everyone loves:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;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;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;orders_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; 
    &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: Handles all customers, even those without orders.&lt;/li&gt;
&lt;li&gt;Theory: One optimized operation to join and aggregate.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;The "Bad" Way – Correlated Subquery
This is the row-by-row method we're warned against:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&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;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="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="n"&gt;o&lt;/span&gt; 
     &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_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;orders_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: Also includes customers with zero orders.&lt;/li&gt;
&lt;li&gt;Theory: Executes a subquery for each customer—classic N+1 problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing Across Databases: The Results&lt;br&gt;
I ran both queries on online SQL testers (links provided below) and analyzed execution times and plans using EXPLAIN. Here's what happened.&lt;/p&gt;

&lt;p&gt;MySQL 8.0: Subquery Wins!&lt;br&gt;
Execution Times: Subquery ~14 ms vs. JOIN ~16 ms.&lt;br&gt;
Why? The subquery triggered a Nested Loop plan with fast index lookups (25 quick searches). JOIN used Hash Join + Aggregate, which was overkill for small data.&lt;br&gt;
Key Insight: With an index on orders.customer_id, the subquery wasn't N+1—it was efficient Nested Loops.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/1a" rel="noopener noreferrer"&gt;MySQL Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oracle 23c: Subquery Dominates!&lt;br&gt;
Execution Times: Subquery ~2.4 ms vs. JOIN ~15 ms.&lt;br&gt;
Why? Similar to MySQL—Nested Loop for subquery vs. Hash Join for JOIN. The subquery avoided heavy aggregation overhead.&lt;br&gt;
Key Insight: Indexes are crucial; without them, Oracle falls back to full scans.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/va" rel="noopener noreferrer"&gt;Oracle Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL 16: JOIN Takes the Lead&lt;br&gt;
Execution Times: JOIN ~0.6 ms vs. Subquery ~1.9 ms.&lt;br&gt;
Why? PostgreSQL's optimizer rewrote the subquery into a JOIN-like plan, but the explicit JOIN was slightly faster. Subquery showed 25 sub-plan executions (mild N+1).&lt;br&gt;
Key Insight: PostgreSQL is smart—indexes level the playing field.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/ua" rel="noopener noreferrer"&gt;PostgreSQL Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SQLite 3.45: A Tie!&lt;br&gt;
Execution Times: Both ~1 ms.&lt;br&gt;
Why? Plans were nearly identical: SCAN on customers + SEARCH on orders via index. No N+1 effect.&lt;br&gt;
Key Insight: SQLite's simplicity made both queries efficient; choose based on readability.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/la" rel="noopener noreferrer"&gt;SQLite Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key Takeaways: No Silver Bullet&lt;br&gt;
The "JOIN is always faster" myth crumbles because performance depends on:&lt;/p&gt;

&lt;p&gt;Database Optimizer: PostgreSQL rewrites queries; MySQL/Oracle follow your syntax more literally.&lt;br&gt;
Data Size: Small outer tables (like our 25 customers) favor Nested Loops; large ones benefit from Hash Joins.&lt;br&gt;
Indexes: Without an index on orders.customer_id, subqueries tank. With it, they shine.&lt;br&gt;
Bottom Line: Don't blindly follow rules. Always run EXPLAIN (or EXPLAIN ANALYZE) to see the actual execution plan. Test with your data!&lt;/p&gt;

&lt;p&gt;What are your experiences with JOINs vs. subqueries? Drop a comment below!&lt;/p&gt;

&lt;p&gt;This article is based on real testing and analysis. Links to testers are provided for you to verify the results.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>performance</category>
      <category>database</category>
    </item>
    <item>
      <title>SQL Tricks: Generate Calendar Table</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 19 Jul 2025 19:34:49 +0000</pubDate>
      <link>https://dev.to/rozhnev/sql-tricks-generate-calendar-table-3lcn</link>
      <guid>https://dev.to/rozhnev/sql-tricks-generate-calendar-table-3lcn</guid>
      <description>&lt;p&gt;Creating a "calendar table" or "date dimension" is a common task in SQL, especially for reporting, data warehousing, or when you need to perform calculations based on dates that might not exist in your actual data (e.g., finding days with no sales). While a full-fledged calendar table usually contains many attributes (day of week, week number, quarter, holiday flags, etc.), sometimes you just need a simple list of dates for a specific period, like the current month.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how to dynamically generate a table containing all dates for the current month across different popular RDBMS dialects: MySQL, PostgreSQL, MS SQL Server, and Oracle. This approach avoids hardcoding dates and ensures your script always works for the current period.&lt;/p&gt;

&lt;p&gt;Why generate a calendar table?&lt;br&gt;
Before we dive into the code, let's briefly touch upon why this is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filling Gaps: If your transaction data only records days with activity, a calendar table can help you identify days with no activity (e.g., zero sales).&lt;/li&gt;
&lt;li&gt;Time-Series Analysis: Essential for analyses that require a continuous timeline, even when data is sparse.&lt;/li&gt;
&lt;li&gt;Simplifying Joins: You can join your data to a calendar table to easily group by specific date parts or filter by continuous date ranges.&lt;/li&gt;
&lt;li&gt;Reporting: Providing a complete date range for reports, even if some dates have no associated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at the solutions for each database system.&lt;/p&gt;
&lt;h2&gt;
  
  
  MySQL
&lt;/h2&gt;

&lt;p&gt;MySQL offers a few ways to generate series. We'll use a common table expression (CTE) combined with a recursive CTE or a numbers table approach to generate our dates.&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="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;dates&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;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;DAYOFMONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&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;DAY&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;dt&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&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;dates&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt;
        &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;LAST_DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;-- Last day of current month&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;dt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;dates&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;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WITH RECURSIVE dates AS (...): Defines a recursive CTE named dates.&lt;/li&gt;
&lt;li&gt;Anchor Member: SELECT DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) - 1 DAY): This calculates the first day of the current month. CURDATE() gets the current date, DAYOFMONTH(CURDATE()) gets the day of the month (e.g., 15 for July 15th), and we subtract (day - 1) days to get to the 1st of the month.&lt;/li&gt;
&lt;li&gt;Recursive Member: SELECT DATE_ADD(dt, INTERVAL 1 DAY) FROM dates WHERE dt &amp;lt; LAST_DAY(CURDATE()): This part adds one day to the previous date (dt) until it reaches the last day of the current month, which is obtained using LAST_DAY(CURDATE()).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mysql80/9adfdb9b62ed20c8ba374b1b20bd5dda/" rel="noopener noreferrer"&gt;Try this solution with SQLize.online&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has a very convenient generate_series() function which is perfect for this task.&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;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 month'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- Last day of current month&lt;/span&gt;
        &lt;span class="s1"&gt;'1 day'&lt;/span&gt;
    &lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&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;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GENERATE_SERIES(start, stop, step): Generates a series of values.&lt;/li&gt;
&lt;li&gt;DATE_TRUNC('month', CURRENT_DATE): Truncates the current date to the beginning of the month, giving us the first day.&lt;/li&gt;
&lt;li&gt;(DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month' - INTERVAL '1 day'):📅 This calculates the last day of the current month. We go to the beginning of the next month (+ INTERVAL '1 month') and then subtract one day (- INTERVAL '1 day') to get the last day of the current month.&lt;/li&gt;
&lt;li&gt;'1 day': Specifies that each step in the series should be one day.&lt;/li&gt;
&lt;li&gt;:📅 Casts the resulting timestamp to a date type for a cleaner output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/psql17/f904728c3f9f4bbd0077b90046ad4674/" rel="noopener noreferrer"&gt;Test tris code on SQLize.online&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MS SQL Server
&lt;/h2&gt;

&lt;p&gt;SQL Server offers multiple ways to generate sequences. Since SQL Server 2022, the GENERATE_SERIES function provides a straightforward method, similar to PostgreSQL. For earlier versions, or if you prefer, recursive CTEs are also a good option.&lt;/p&gt;

&lt;p&gt;Using GENERATE_SERIES (SQL Server 2022+)&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&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;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EOMONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GETDATE&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="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GENERATE_SERIES(start, stop, step): This function generates a sequence of numbers from start to stop with increments of step. By default, step is 1 if omitted.&lt;/li&gt;
&lt;li&gt;DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0): This calculates the first day of the current month. This will be our base date.&lt;/li&gt;
&lt;li&gt;DAY(EOMONTH(GETDATE())) - 1: EOMONTH(GETDATE()) returns the last day of the current month. DAY() extracts the day number (e.g., 31 for July 31st). Subtracting 1 gives us the maximum number to generate for our series (from 0 to days_in_month - 1). For example, if a month has 31 days, we want to generate numbers from 0 to 30.&lt;/li&gt;
&lt;li&gt;DATEADD(day, value, ...): For each value generated by GENERATE_SERIES (which are 0, 1, 2, ... up to days_in_month - 1), we add that number of days to our first day of current month base date. This effectively generates each date of the month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mssql2022/00ab057284fa01765d7941dee2097a3b/" rel="noopener noreferrer"&gt;Try this SQL online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using Recursive CTE (Older SQL Server Versions or Alternative)&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;Dates&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&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;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&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;dt&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;Dates&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt;
        &lt;span class="n"&gt;DATEPART&lt;/span&gt;&lt;span class="p"&gt;(&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&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;dt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEPART&lt;/span&gt;&lt;span class="p"&gt;(&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;GETDATE&lt;/span&gt;&lt;span class="p"&gt;())&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;dt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;Dates&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAXRECURSION&lt;/span&gt; &lt;span class="mi"&gt;366&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;-- Set max recursion limit, 366 covers leap years&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WITH Dates AS (...): Defines a recursive CTE named Dates.&lt;/li&gt;
&lt;li&gt;Anchor Member: SELECT DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) AS dt: This is a common SQL Server idiom to get the first day of the current month.&lt;/li&gt;
&lt;li&gt;DATEDIFF(month, 0, GETDATE()): Calculates the number of month boundaries crossed between 0 (which SQL Server treats as January 1, 1900) and GETDATE().&lt;/li&gt;
&lt;li&gt;DATEADD(month, ..., 0): Adds that number of months to 0, effectively landing on the first day of the current month.&lt;/li&gt;
&lt;li&gt;Recursive Member: SELECT DATEADD(day, 1, dt) FROM Dates WHERE DATEPART(month, DATEADD(day, 1, dt)) = DATEPART(month, GETDATE()): Adds one day to dt as long as the month of the next date is still the current month.&lt;/li&gt;
&lt;li&gt;OPTION (MAXRECURSION 366): Important for recursive CTEs in SQL Server. It sets the maximum number of times the recursive part can execute. 366 is a safe number to cover all possible days in a year (including leap years).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mssql2017/70cfed98b5e5b58176222c740254868b/" rel="noopener noreferrer"&gt;Try legacy SQL Server code&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Oracle
&lt;/h2&gt;

&lt;p&gt;Oracle provides a powerful CONNECT BY LEVEL clause, often used for generating sequences.&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;TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&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;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;dual&lt;/span&gt;
&lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="n"&gt;TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;LAST_DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TRUNC(SYSDATE, 'MM'): Truncates the current date (SYSDATE) to the first day of the current month.&lt;/li&gt;
&lt;li&gt;LEVEL: A pseudo-column in hierarchical queries that returns the current level in the hierarchy (starting from 1).&lt;/li&gt;
&lt;li&gt;TRUNC(SYSDATE, 'MM') + LEVEL - 1: Generates successive dates starting from the first day of the month.&lt;/li&gt;
&lt;li&gt;When LEVEL is 1, it's first_day_of_month + 1 - 1 = first_day_of_month.&lt;/li&gt;
&lt;li&gt;When LEVEL is 2, it's first_day_of_month + 2 - 1 = first_day_of_month + 1 day.&lt;/li&gt;
&lt;li&gt;FROM dual: dual is a dummy table in Oracle, often used for selecting pseudo-columns or evaluating expressions.&lt;/li&gt;
&lt;li&gt;CONNECT BY TRUNC(SYSDATE, 'MM') + LEVEL - 1 &amp;lt;= LAST_DAY(SYSDATE): This clause acts as the loop condition. It continues generating rows as long as the generated date is less than or equal to the last day of the current month (LAST_DAY(SYSDATE)).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/oracle23/06389d165705036f32f36b9ebc80e8f0/" rel="noopener noreferrer"&gt;Oracle SQL playground online&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;As you can see, while the syntax differs across RDBMS, the core concept of generating a series of dates remains similar. With the addition of GENERATE_SERIES in SQL Server 2022, modern SQL versions are increasingly standardizing on easier ways to achieve this. Understanding these techniques is crucial for effective data manipulation and reporting in SQL. Choose the method that best suits your specific database environment and version.&lt;/p&gt;

&lt;p&gt;I hope this was helpful! Feel free to leave a comment if you have any questions or alternative approaches.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>analytics</category>
      <category>mysql</category>
      <category>database</category>
    </item>
    <item>
      <title>SQLtest.online - the site where you can test your SQL skills</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 17 Feb 2024 12:00:35 +0000</pubDate>
      <link>https://dev.to/rozhnev/sqltestonline-the-site-where-you-can-test-your-sql-skills-5b0d</link>
      <guid>https://dev.to/rozhnev/sqltestonline-the-site-where-you-can-test-your-sql-skills-5b0d</guid>
      <description>&lt;p&gt;Hello friends! I'll tell you about my new project - &lt;a href="https://SQLTest.online"&gt;SQLTest.online&lt;/a&gt;. This is a platform for those who are learning SQL and want to test their skills by solving practical problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w7envmj2gmrrsnjyd3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w7envmj2gmrrsnjyd3p.png" alt="Image description" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The using of the site is very simple - select a problem from the list on the left and try to solve it! So far there are more then 220 questions on MySQL, several questions on Firebird. For tests, public databases Sakila (MySQL), Bookings (PostgreSQL) and Employee (Firebird) are used. In the future, I plan to expand both the list of available databases and the number of questions for each of them.&lt;/p&gt;

&lt;p&gt;The table structure of the database relevant for each task is displayed on the right side of the screen and helps you in solving.&lt;/p&gt;

&lt;p&gt;If you are at a loss with a solution, you can use the hint or run a query and see the result. Validation of a solution is performed in two stages: a basic check using regular expressions and then checking the result against the correct solution.&lt;/p&gt;

&lt;p&gt;If you are interested in the project and decide to pass all the tests, you can log in to the site. Login is currently available via Google, Yandex and GitHub. When you log in to the site, no personal information is collected, only data about your progress in solving problems. However, even without registration, you get access to all functions of the site without restrictions!&lt;/p&gt;

&lt;p&gt;So, Happy testing with &lt;a href="https://SQLTest.online"&gt;SQLTest.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find problems on the site, write about them below in the comments to the article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Generate Date Series in popular Databases</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Wed, 23 Aug 2023 20:43:15 +0000</pubDate>
      <link>https://dev.to/rozhnev/generate-date-series-in-popular-databases-5cag</link>
      <guid>https://dev.to/rozhnev/generate-date-series-in-popular-databases-5cag</guid>
      <description>&lt;p&gt;In this post I want to answer to frequently asked question: How I can generate date series between to particular dates? &lt;/p&gt;

&lt;p&gt;Generating a date series between two particular dates can be done using different methods depending on the relational database management system (RDBMS) you are using. I'll provide examples for a few popular RDBMS systems: MySQL, PostgreSQL, and Microsoft SQL Server.&lt;/p&gt;

&lt;p&gt;Please note that the syntax might slightly differ based on the specific version of the RDBMS you're using, so you should consult the documentation for your specific version if you encounter any issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  MySQL
&lt;/h2&gt;

&lt;p&gt;Legacy MySQL (5.7.*)&lt;br&gt;
The old MySQL doesn't have built-in functions to generate a date series, so you might need to use a temporary table or a numbers table. Here's an example using a numbers table approach:&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;TEMPORARY&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Numbers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Insert numbers up to the desired range&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Numbers&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;  

&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start_date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;DAY&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;generated_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Numbers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start_date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'end_date'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Just replace 'start_date' and 'end_date' with your desired start and end dates and try it on &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Modern MySQL 8.0.*, you can use a Common Table Expression (CTE) to generate a date series between two particular dates. Here's how you can do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;DateSeries&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="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generated_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&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;DateSeries&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&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;generated_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DateSeries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The WITH RECURSIVE clause defines the CTE named DateSeries.&lt;/li&gt;
&lt;li&gt;In the initial SELECT statement within the CTE, we set the anchor value to the start date.&lt;/li&gt;
&lt;li&gt;In the recursive SELECT statement, we use the DATE_ADD function to increment the date by one day for each iteration.&lt;/li&gt;
&lt;li&gt;The WHERE clause in the recursive SELECT statement ensures that the recursion continues until the generated date is less than the end date.&lt;/li&gt;
&lt;li&gt;Finally, the outer SELECT statement selects all the generated dates from the CTE.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember that recursive queries can be resource-intensive, so use them cautiously and only when necessary. Try the query &lt;a href="https://sqlize.online/sql/mysql80/e4524eaa286ded621409bede4e41e52c/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has the generate_series function that makes this task easy:&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;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&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;generated_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace 'start_date' and 'end_date' with your desired start and end dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft SQL Server
&lt;/h2&gt;

&lt;p&gt;SQL Server also has a similar approach using the sys.dates system table and the DATEADD function:&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;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generated_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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;object_id&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="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_objects&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_objects&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since SQL Server 2022 where implemented &lt;code&gt;GENERATE_SERIES&lt;/code&gt; function you can use it for generate dates series too in next way:&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Oracle
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&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;AS&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dual&lt;/span&gt;
&lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&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;Another cool method:&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;TRUNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2023-01-01'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DUAL&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you know more methods to get date series in different RDBMS, please post in comments&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>oracle</category>
      <category>sqlserver</category>
    </item>
    <item>
      <title>Exploring PostgreSQL's EXCLUDE Operator: Advanced Data Constraints</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 30 May 2023 22:05:45 +0000</pubDate>
      <link>https://dev.to/rozhnev/exploring-postgresqls-exclude-operator-advanced-data-constraints-2k60</link>
      <guid>https://dev.to/rozhnev/exploring-postgresqls-exclude-operator-advanced-data-constraints-2k60</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the process of designing a database, as described in my previous article, I decided to utilize the EXCLUDE constraint to maintain data integrity. While contemplating this, I realized that the EXCLUDE operator deserves a dedicated article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction to the EXCLUDE Operator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL is renowned for its numerous powerful features, and one of them is the EXCLUDE operator. This operator allows you to create advanced constraints on sets of values within table columns. In this article, I want to delve into the EXCLUDE operator, provide examples of its usage, and help you understand how to leverage it to build flexible and efficient databases.&lt;/p&gt;

&lt;p&gt;Similar to the UNIQUE constraint in PostgreSQL, the EXCLUDE operator is used to define constraints on sets of values within table columns. However, unlike UNIQUE, it enables you to specify rules that determine which values cannot coexist within a particular column or set of columns. The EXCLUDE operator is often used with GiST or SP-GiST index types to ensure query efficiency, although it can also be used with a regular B-Tree index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common example of utilizing EXCLUDE is applying a constraint on overlapping time intervals, such as movie screenings in a cinema.&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;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="n"&gt;tstzrange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;no_screening_time_overlap&lt;/span&gt; &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'["2023-01-01 19:00:00", "2023-01-01 20:45:00"]'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, we create a table named "events" and insert a record with a time interval. You can check the SQL on &lt;a href="https://sqlize.online/sql/psql15/f560a464533bf811b6c8b902b9b509d8/" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt;. Afterwards, you can try inserting another row with an interval that overlaps with an existing one in the table. Most likely, it will result in an error. If you manage to succeed, let me know in the comments!&lt;/p&gt;

&lt;p&gt;Similar to UNIQUE, the EXCLUDE constraint can be applied to a group of columns. For instance, you can use the "event_start" and "event_end" columns of type timestamp and restrict time overlaps. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_start&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_end&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_start&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_end&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Constraints on numeric ranges can also be imposed using EXCLUDE. Take a look at this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ranges&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;range_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start_value&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;end_value&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int4range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;In this example, the "ranges" table is created, which contains numeric ranges. The EXCLUDE operator with a GiST index specifies that the numeric ranges in the "start_value" and "end_value" columns cannot overlap.&lt;/p&gt;

&lt;p&gt;Another significant application is constraining the intersection of geometric figures:&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;polygons&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;polygon_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;polygon_data&lt;/span&gt; &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polygon_data&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Here, the "polygons" table is created, which stores information about polygons. The EXCLUDE operator with a GiST index ensures that geometric objects in the "polygon_data" column cannot intersect or be contained within each other.&lt;/p&gt;

&lt;p&gt;In all the above examples, we utilized the EXCLUDE constraint based on a GiST index. However, for completeness, let's provide an example using an R-Tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we nearly replicated the functionality of the UNIQUE constraint with a slight modification. Our uniqueness is now case-insensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The EXCLUDE operator in PostgreSQL offers the ability to create advanced constraints on sets of values within table columns. It allows you to define rules that restrict combinations of values that cannot coexist. This is particularly useful for ensuring data integrity and performing complex checks at the database level.&lt;/p&gt;

&lt;p&gt;In this article, we explored several examples of using the EXCLUDE operator, including constraints on overlapping time intervals, prohibition of intersecting geometric objects, and constraints on non-overlapping numeric ranges. The EXCLUDE operator is a powerful tool that can be employed to build flexible and efficient databases in PostgreSQL.&lt;/p&gt;

&lt;p&gt;In your projects, utilize the EXCLUDE operator to create sophisticated constraints and ensure data integrity at the database level. This will help you maintain the structure and reliability of your database while optimizing its usage.&lt;/p&gt;

&lt;p&gt;If you found this article helpful, you can show your &lt;a href="https://ko-fi.com/phpize" rel="noopener noreferrer"&gt;support to the author&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgressql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>MySQL 8.0: Invisible Columns</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 16 May 2023 12:03:39 +0000</pubDate>
      <link>https://dev.to/rozhnev/mysql-80-invisible-columns-19o8</link>
      <guid>https://dev.to/rozhnev/mysql-80-invisible-columns-19o8</guid>
      <description>&lt;p&gt;The Oracle company ported &lt;code&gt;INVISIBLE COLUMN&lt;/code&gt; feature to it's little brother MySQL. Since version 8.0.23 the invisible column feature available for MySQL users.&lt;/p&gt;

&lt;p&gt;Let's learn how it works!&lt;/p&gt;

&lt;p&gt;You can create invisible column as regular when you create a table using &lt;code&gt;INVISIBLE&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE test_table (
  a INT,
  b DATE INVISIBLE
) ENGINE = InnoDB;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or add new column to an exists table by &lt;code&gt;ALTER TABLE&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table ADD COLUMN c INT INVISIBLE;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a column exists you can change its visibility using CHANGE, MODIFY or ALTER COLUMN command as you can see below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table CHANGE COLUMN b b DATE VISIBLE;
ALTER TABLE test_table MODIFY COLUMN b DATE INVISIBLE;
ALTER TABLE test_table ALTER COLUMN c SET VISIBLE;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you fetch table structure you will see all columns, but part of them will be marked by &lt;code&gt;INVISIBLE&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM test_table;
SHOW CREATE TABLE test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you see, the invisible column manipulation pretty simple. Now look how the visibility affects DML.&lt;/p&gt;

&lt;p&gt;First, when you try to select all columns using &lt;code&gt;SELECT *&lt;/code&gt;, the invisible columns are not appears. So if you need to see they, you must to know the column name and select it by its name. Look below SQL code examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TABLE test_table; SELECT * FROM test_table; -- column hidden

SELECT a, b, c FROM test_table; -- column visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same way, when you need to insert data to invisible column you must call it by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO test_table VALUES (1, now(), 33); -- Error
INSERT INTO test_table () VALUES (1, now(), 33); -- Error too

INSERT INTO test_table VALUES (1, 22); -- NULL inserted
INSERT INTO test_table (a, b, c) VALUES (1, now(), 33); -- all values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where you can use this feature? First - security - you can use invisible column for hide some column from DB users with low permissions. Second - when you use generated column you can prevent insert data to such column doing it invisible.&lt;br&gt;
Look next example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE test_table (
  a INT,
  b INT
) ENGINE = InnoDB;

INSERT INTO test_table VALUES (1, 2), (2, 3), (3, 4);

ALTER TABLE test_table ADD COLUMN a_b_sum INT AS (a + b);

SELECT * FROM test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above example we added generated column to our &lt;code&gt;test_table&lt;/code&gt; but this change can break our insert query. What we can do in this case? Right - use invisible column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table ALTER COLUMN a_b_sum SET INVISIBLE;

INSERT INTO test_table VALUES (5, 7), (2, 9), (2, 3);

SELECT a, b, a_b_sum FROM test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can &lt;a href="https://sqlize.online/s/0d"&gt;run SQL queries online&lt;/a&gt; and test this feature.&lt;/p&gt;

&lt;p&gt;So, this is all about this feature. If you think about more use-cases please share it in comments. &lt;/p&gt;

</description>
      <category>mysql</category>
      <category>tutorial</category>
      <category>sql</category>
    </item>
    <item>
      <title>The power of MERGE in PostgreSQL 15</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Mon, 01 May 2023 14:22:32 +0000</pubDate>
      <link>https://dev.to/rozhnev/merge-in-postgresql-15-2o7f</link>
      <guid>https://dev.to/rozhnev/merge-in-postgresql-15-2o7f</guid>
      <description>&lt;p&gt;The MERGE statement is a powerful tool that can be used to perform conditional INSERT, UPDATE, or DELETE operations on rows in a table. According &lt;a href="https://en.wikipedia.org/wiki/Merge_(SQL)"&gt;Wikipedia&lt;/a&gt; MERGE operation was officially introduced in the SQL:2003 standard, and expanded in the SQL:2008 standard.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MERGE&lt;/code&gt; is similar to the &lt;code&gt;UPSERT&lt;/code&gt; statement in other SQL dialects, but it offers a number of advantages, including:&lt;/p&gt;

&lt;p&gt;It is more efficient, as it only performs the necessary operations for each row.&lt;br&gt;
It is more atomic, as it either succeeds or fails as a whole.&lt;br&gt;
It is more flexible, as it allows for more complex conditions to be specified.&lt;/p&gt;

&lt;p&gt;Before PostgreSQL 15 merge was possible using  &lt;code&gt;INSERT INTO ... ON CONFLICT [ conflict_target ] conflict_action&lt;/code&gt; but now we can use all advantages of &lt;code&gt;MERGE&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The syntax for the MERGE statement is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGE INTO target_table AS t
USING source_table AS s
ON &amp;lt;condition&amp;gt;
WHEN MATCHED THEN
  &amp;lt;update_statement&amp;gt;
WHEN NOT MATCHED THEN
  &amp;lt;insert_statement&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target_table is the table that will be updated or inserted into. The source_table is the table that contains the data that will be used to update or insert rows in the target_table. The condition is a Boolean expression that is used to determine whether a row in the source_table should be updated or inserted. The update_statement is a SQL statement that is used to update rows in the target_table. The insert_statement is a SQL statement that is used to insert rows into the target_table.&lt;/p&gt;

&lt;p&gt;Here is an example of a MERGE statement that update table customers using leads table. Both tables have email field, so we need to add only leads that email not exists in customers table&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGE INTO customers AS c
USING leads AS l
ON c.email = l.email
WHEN NOT MATCHED THEN
  insert (name, email, created_at)
  VALUES (l.name, l.email, DEFAULT);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This statement will first perform a join between the customers table and the leads table. The join will only return rows in the leads table where the email address is not exits in customers. For each row that is returned, the insert query will be performed.&lt;/p&gt;

&lt;p&gt;This query equivalent next &lt;code&gt;UPSERT&lt;/code&gt; query used before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;insert into customers (name, email)
select name, email from leads
on conflict (email) do nothing;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but if we look results we already see the MERGE advantage. It not increment id field in case when record already exists.&lt;/p&gt;

&lt;p&gt;Just &lt;a href="https://sqlize.online/sql/psql15/6197de8998da40ac8b2a3df19975103e/"&gt;run both SQL queries and compare results&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let try more complicated task. Now we add leads with new email and update name for exist&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGE INTO customers AS c
USING leads AS l
ON c.email = l.email
WHEN NOT MATCHED THEN
  INSERT (name, email, created_at)
  VALUES (l.name, l.email, DEFAULT)
WHEN MATCHED THEN UPDATE
    SET name = l.name
;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://sqlize.online/sql/psql15/3bc997f059ba4b105616857e2eafadf0/"&gt;Here you can run SQL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in last scenario we will add leads with absent emails, update name where customer creation date less then matched leads record and delete other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGE INTO customers AS c
USING leads AS l
ON c.email = l.email
WHEN NOT MATCHED THEN
  INSERT (name, email, created_at)
  VALUES (l.email, l.name, DEFAULT)
WHEN MATCHED AND c.created_at &amp;lt; l.created_at THEN UPDATE
    SET name = l.name
WHEN MATCHED THEN
    DELETE
;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In article conclusion we can say:&lt;br&gt;
The MERGE statement is a powerful tool that can be used to perform conditional INSERT, UPDATE, or DELETE operations on rows in a table. It is more efficient, more atomic, and more flexible than the UPSERT statement in other SQL dialects.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The old MySQL bug (detective story)</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Mon, 24 Apr 2023 11:47:12 +0000</pubDate>
      <link>https://dev.to/rozhnev/the-old-mysql-bug-detective-story-3m8l</link>
      <guid>https://dev.to/rozhnev/the-old-mysql-bug-detective-story-3m8l</guid>
      <description>&lt;p&gt;In this article I want to describe about one bug that I found in MySQL and aware you from mistakes what you can do.&lt;/p&gt;

&lt;p&gt;Some guy from my Telegram chat asked me: "How can I view all foreign key references to a specific table?" I gave him a quick answer: "Look it up in &lt;code&gt;information_schema&lt;/code&gt;", but after that I decided to check my answer.&lt;/p&gt;

&lt;p&gt;Just created simple table &lt;code&gt;test&lt;/code&gt; and table &lt;code&gt;ref1&lt;/code&gt; with field referenced to &lt;code&gt;id&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table test (
 id int primary key,
 first_name varchar(20), 
 last_name varchar(30)
);

create table ref1 (
 id int primary key,
 test_id int references test(id)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks simple because I like this short syntax. After that I looked into &lt;code&gt;information_schema&lt;/code&gt; table &lt;code&gt;REFERENTIAL_CONSTRAINTS&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM `information_schema`.`REFERENTIAL_CONSTRAINTS` WHERE REFERENCED_TABLE_NAME = 'test'; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and checked again. After that I sowed records in &lt;code&gt;REFERENTIAL_CONSTRAINTS&lt;/code&gt;. How is it possible? I was disappointed and decided to save my mind by next test case:&lt;/p&gt;

&lt;p&gt;I created one more referenced table &lt;code&gt;ref2&lt;/code&gt; with canonical syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table ref2 (
 id int primary key,
 test_id int,
 foreign key (test_id) references test(id)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and did check again. After that I sow record in &lt;code&gt;REFERENTIAL_CONSTRAINTS&lt;/code&gt;. How it possible? I was disappointed and decided to save my mind by next test case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- add values to test case
insert into test values (1), (2);

-- add ref1 row referenced to first row in test table
insert into ref1(id, test_id) values (1, 1);

-- add ref2 row referenced to second row in test table
insert into ref2(id, test_id) values (1, 2);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I think, I will try to delete first row from test and will get error (I still think it some feature, not bug) but MySQL drop this row without any warning&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delete from test where id = 1;

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

&lt;/div&gt;



&lt;p&gt;but when I try to delete second one that referenced by second table I did not succeed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delete from test where id = 2;

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

&lt;/div&gt;



&lt;p&gt;So it is not a feature, it is a real BUG! MySQL allows creating tables using short syntax without warnings but constraints are not created and do not guard your data consistently OMG!&lt;/p&gt;

&lt;p&gt;I looked in Google and found the bug has been open since 2004 &lt;a href="https://bugs.mysql.com/bug.php?id=4919"&gt;https://bugs.mysql.com/bug.php?id=4919&lt;/a&gt; and it is still not fixed. Hey guys, what are you doing? Hey guys what you during?&lt;/p&gt;

&lt;p&gt;I decided to run this test case over other databases and found all databases that I can test on &lt;a href="https://sqlize.online/sql/mariadb/0295e1d928b15c3e54f5cadcda10a750/"&gt;SQLize.online&lt;/a&gt; allows short syntax and all of them (MariaDB, PostgreSQL, SQL Server, Oracle) created foreign key constraints in both of cases except MySQL and SQLite.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>mysql</category>
      <category>bug</category>
    </item>
    <item>
      <title>Everything you wanted to know about Auto Increment but were afraid to ask</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Mon, 03 Apr 2023 19:02:46 +0000</pubDate>
      <link>https://dev.to/rozhnev/everything-you-wanted-to-know-about-auto-increment-but-were-afraid-to-ask-426g</link>
      <guid>https://dev.to/rozhnev/everything-you-wanted-to-know-about-auto-increment-but-were-afraid-to-ask-426g</guid>
      <description>&lt;p&gt;Auto-increment is a feature in databases that automatically generates a unique number for each new row added to a table. This number is usually used as a primary key to uniquely identify each row in the table. The database system automatically assigns the next available number to each new row, saving the user from having to manually manage the primary key values. This simplifies the process of adding new rows and ensures that each row has a unique identifier.&lt;/p&gt;

&lt;p&gt;Below we can see how to create table with auto-increment column in different RDBMS&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   name VARCHAR(50),
   age INT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the id column is defined as an integer data type with the AUTOINCREMENT keyword. It is also set as the primary key of the table.&lt;/p&gt;

&lt;p&gt;MySQL and MariaDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INT AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(50),
   age INT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the id column is defined as an auto increment column with the AUTO_INCREMENT keyword. It is also set as the primary key of the table.&lt;/p&gt;

&lt;p&gt;SQL Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INT IDENTITY(1,1) PRIMARY KEY,
   name VARCHAR(50),
   age INT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the id column is defined as an identity column with the IDENTITY keyword. It is also set as the primary key of the table.  The numbers 1,1 specify the starting value and increment value of an auto-incrementing column. &lt;br&gt;
For example, we can start numbering with 100 and add 10 (100, 110, 120, 130...) each next row by replacing IDENTITY(1,1) with IDENTITY(100, 10). Moreover, negative numbers can be used as a sequence start and step. Try it yourself &lt;a href="https://sqlize.online/s/1B"&gt;here&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE SEQUENCE example_table_seq;

CREATE TABLE example_table (
   id NUMBER DEFAULT example_table_seq.nextval PRIMARY KEY,
   name VARCHAR2(50),
   age NUMBER
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, a sequence is created first with the CREATE SEQUENCE statement. Then the id column is defined as a number data type with a default value of the next value of the sequence. It is also set as the primary key of the table. The sequence &lt;code&gt;example_table_seq&lt;/code&gt; started with 1 and step 1. When we need to customize it we can use &lt;code&gt;INCREMENT BY&lt;/code&gt; and &lt;code&gt;START WITH&lt;/code&gt; parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE SEQUENCE example_table_seq 
    INCREMENT BY 10
    START WITH 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL have several options for define auto-increment column. The most popular is &lt;code&gt;SERIAL&lt;/code&gt; keyword&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id SERIAL PRIMARY KEY,
   name VARCHAR(50),
   age INT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the id column is defined as a serial data type, which is equivalent to an auto increment column in other DBMS. It is also set as the primary key of the table.&lt;/p&gt;

&lt;p&gt;Also, you can use both &lt;code&gt;IDENTITY&lt;/code&gt; and &lt;code&gt;SEQUENCE&lt;/code&gt; syntax in PostgreSQL to create an auto-increment column. Here are the examples of creating an auto-increment column using IDENTITY and SEQUENCE syntax in PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   name VARCHAR(50),
   age INT
);
&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;CREATE SEQUENCE example_table_seq;

CREATE TABLE example_table (
   id INT DEFAULT nextval('example_table_seq') PRIMARY KEY,
   name VARCHAR(50),
   age INT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To insert a new row into table with auto-increment column, we can use the INSERT INTO statement, specifying for all columns except auto-increment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO example_table (name, age) VALUES ('John Smith', 30);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of SQL dialects allows to insert several rows in single query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO example_table (name, age)
VALUES
  ('Jane Doe', 25),
  ('Bob Johnson', 40),
  ('Alice Brown', 35);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite, MySQL &amp;amp; MariaDB allows to insert arbitrary value into auto-increment column and update the sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO example_table (id, name, age) VALUES (11, 'John Smith', 30);

INSERT INTO example_table (name, age)
VALUES
  ('Jane Doe', 25),
  ('Bob Johnson', 40),
  ('Alice Brown', 35);

SELECT * FROM example_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above example we inserting 'John Smith' with id = 11 and next records inserts uses next sequence values 12, 13, 14&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+====+=============+=====+
| id | name        | age |
+====+=============+=====+
| 11 | John Smith  | 30  |
| 12 | Jane Doe    | 25  |
| 13 | Bob Johnson | 40  |
| 14 | Alice Brown | 35  |
+----+-------------+-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MS SQL Server does not allow such voluntarism by default. So if we need to insert value in identity column we must to set &lt;code&gt;IDENTITY_INSERT&lt;/code&gt; on our table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET IDENTITY_INSERT example_table ON
INSERT INTO example_table (id, name, age) VALUES (11, 'John Smith', 30)
SET IDENTITY_INSERT example_table OFF;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above query inserts record with id = 11 and move up identity counter for next records.&lt;/p&gt;

&lt;p&gt;What about Oracle? It allows to insert values in id column, without sequence be affected, so we can insert row with arbitrary id, but this can cause to collision like in next example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE SEQUENCE example_table_seq;

CREATE TABLE example_table (
   id NUMBER DEFAULT example_table_seq.nextval PRIMARY KEY,
   name VARCHAR2(50),
   age NUMBER
);
-- insert row with id = 3
INSERT INTO example_table (id, name, age) VALUES (3, 'John Smith', 30);

-- next to queries will add records with id 1 and 2
INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25); -- 1
INSERT INTO example_table (name, age) VALUES ('Bob Johns', 40); -- 2

-- below query cause to error OCIStmtExecute: ORA-00001: unique constraint (0c7690dacb6b.SYS_C009760) violated 
INSERT INTO example_table (name, age) VALUES ('Alice Brown', 35); 

SELECT * FROM example_table;

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

&lt;/div&gt;



&lt;p&gt;PostgreSQL with &lt;code&gt;SERIAL&lt;/code&gt; or &lt;code&gt;SEQUENCE&lt;/code&gt; syntax for auto-increment  column show us same behavior like Oracle (row inserted, sequence not updated, error on duplicate). But when we use &lt;code&gt;IDENTITY&lt;/code&gt; syntax insert row with arbitrary id is not allowed. This restriction can be forced by &lt;code&gt;OVERRIDING SYSTEM VALUE&lt;/code&gt; like in next example, but again it is not update the sequence and collision may cause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   name VARCHAR(50),
   age INT
);

INSERT INTO example_table (id, name, age) OVERRIDING SYSTEM VALUE VALUES (3, 'John Smith', 30);

INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25);
INSERT INTO example_table (name, age) VALUES ('Bob Johnson', 40);
INSERT INTO example_table (name, age) VALUES ('Alice Brown', 35);

SELECT * FROM example_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets move on! How to change an auto-increment value?&lt;/p&gt;

&lt;p&gt;In SQLite you can reset the auto-increment value for a table by using the &lt;code&gt;sqlite_sequence&lt;/code&gt; table. &lt;br&gt;
The &lt;code&gt;sqlite_sequence&lt;/code&gt; table is an internal table used by SQLite to keep track of the next auto-increment value for each table in a database. Each row in the &lt;code&gt;sqlite_sequence&lt;/code&gt; table represents a table in the database, and the seq column stores the next auto-increment value for that table. So when we need to change it just try next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE sqlite_sequence SET seq = 100 WHERE name = 'example_table';

INSERT INTO example_table (name, age) VALUES ('John Gold', 30);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code will move auto-increment value to 100 and next inserted row will get &lt;code&gt;id&lt;/code&gt; = 101; &lt;/p&gt;

&lt;p&gt;We can delete auto-increment with next query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE FROM sqlite_sequence WHERE name = 'example_table';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or set it less than maximal value in auto-incremented column but after next row will be inserted the sqlite_sequence will be fixed with right value. Just look next code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example_table (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   name VARCHAR(50),
   age INT
);

INSERT INTO example_table (name, age)
VALUES
  ('Jane Doe', 25),
  ('Bob Johnson', 40),
  ('Alice Brown', 35);

SELECT * FROM sqlite_sequence WHERE name = 'example_table';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after table create and filled we see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+===============+=====+
| name          | seq |
+===============+=====+
| example_table | 3   |
+---------------+-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can drop it and make sure that value disappeared&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE FROM sqlite_sequence WHERE name = 'example_table';

SELECT * FROM sqlite_sequence WHERE name = 'example_table';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but after next record inserted value of sequence will be restored as well. &lt;a href="https://sqlize.online/sql/sqlite3/3f07c11acf32ffc67581b388fd42e9b7/"&gt;Try it here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MySQL &amp;amp; MariaDB also allows to change auto-increment using &lt;code&gt;ALTER TABLE&lt;/code&gt; query but it can not be less than maximal value in auto-incremented column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE example_table AUTO_INCREMENT = 100;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Oracle, it is possible to alter a sequence using the ALTER SEQUENCE statement. This statement allows you to change the characteristics of a sequence, such as its minimum and maximum values, its increment, and its starting value. If you need to make more significant changes to a sequence, such as changing its data type or dropping it entirely, then you may need to drop and re-create the sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DROP SEQUENCE example_table_seq;
CREATE SEQUENCE example_table_seq INCREMENT BY 1 START WITH 100;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in Oracle, there is no conflict checking between the new sequence value and existing records. Therefore, it is important to exercise caution and use this feature responsibly to avoid unintended consequences.&lt;/p&gt;

&lt;p&gt;In PostgreSQL, it doesn't matter how your auto-increment column is created (SERIAL, GENERATED ALWAYS AS IDENTITY, or using CREATE SEQUENCE) because you can use the ALTER SEQUENCE statement to modify the sequence. However, this DBMS does not guarantee the absence of conflicts after the sequence is modified, so it is important to exercise caution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER SEQUENCE example_table_id_seq RESTART WITH 100;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In conclusion:&lt;/p&gt;

&lt;p&gt;Auto-increment is a feature in databases that generates a unique number for each new row added to a table, saving the user from manually managing the primary key values. This feature is supported by most RDBMS and can be easily implemented using different syntax in each database. SQLite, MySQL, and MariaDB all support auto-increment columns, as well as PostgreSQL and Oracle, which also allow for more advanced options like sequences and serial data types. Although each database has its own syntax for implementing and mange auto-increment. Overall, the auto-increment feature greatly simplifies the process of adding new rows and ensures that each row has a unique identifier, which is essential for many database operations.&lt;/p&gt;

&lt;p&gt;If you want to practice creating tables with auto-increment columns and executing SQL statements, you can try using an online SQL editor like &lt;a href="https://sqlize.online"&gt;SQLize.online&lt;/a&gt;, which allows you to create tables, insert data, and run SQL queries on a live database.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>tutorial</category>
      <category>mysql</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Simple query to remove duplicates from table</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 28 Mar 2023 17:13:09 +0000</pubDate>
      <link>https://dev.to/rozhnev/simple-query-for-remove-duplicates-from-table-26e3</link>
      <guid>https://dev.to/rozhnev/simple-query-for-remove-duplicates-from-table-26e3</guid>
      <description>&lt;p&gt;In this small post I want to share the simple way to remove duplicates from table. The query works in MySQL, MariaDB and PostgreSQL databases. If you interested in such query for other RDBMS, please write me in comments.&lt;br&gt;
Let's start. Assume we have simple table with two columns: id - is primary key and v simple integer value:&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;t&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;t&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="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;4&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;7&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="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;9&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above create the table and insert couple of values. As you can see id have unique values, v have several duplicates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+====+===+
| id | v |
+====+===+
| 1  | 1 |
| 2  | 1 |
| 3  | 2 |
| 4  | 2 |
| 5  | 1 |
| 6  | 1 |
| 7  | 2 |
| 8  | 3 |
| 9  | 2 |
| 10 | 4 |
| 11 | 3 |
+----+---+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our mission is remove rows this duplicates in column v and rest unique values with minimal id value.&lt;br&gt;
How we can find the duplicates? We can use simple &lt;code&gt;LEFT JOIN&lt;/code&gt; on field &lt;code&gt;v&lt;/code&gt; with additional condition for prevent joins rows itself:&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;t&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query give us next result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+====+===+========+========+
| id | v | id     | v      |
+====+===+========+========+
| 1  | 1 | (null) | (null) |
| 2  | 1 | 1      | 1      |
| 3  | 2 | (null) | (null) |
| 4  | 2 | 3      | 2      |
| 5  | 1 | 1      | 1      |
| 5  | 1 | 2      | 1      |
| 6  | 1 | 1      | 1      |
| 6  | 1 | 2      | 1      |
| 6  | 1 | 5      | 1      |
| 7  | 2 | 3      | 2      |
| 7  | 2 | 4      | 2      |
| 8  | 3 | (null) | (null) |
| 9  | 2 | 3      | 2      |
| 9  | 2 | 4      | 2      |
| 9  | 2 | 7      | 2      |
| 10 | 4 | (null) | (null) |
| 11 | 3 | 8      | 3      |
+----+---+--------+--------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see the unique rows with minimal id have (null) values in last columns. So we need to remove rest. We can done this in next simple 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;delete&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&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;t&lt;/span&gt;
&lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just run in on &lt;a href="https://sqlize.online/sql/mysql80/d1e9b10003717a22797d9dcadff996c6/" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt; and see the result&lt;/p&gt;

&lt;p&gt;P.S. After this publication my colleague &lt;a href="https://stackoverflow.com/users/10138734/akina" rel="noopener noreferrer"&gt;@Akina&lt;/a&gt; suggested a shorter version:&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;delete&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&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;t&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>sql</category>
      <category>mysql</category>
      <category>postgres</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
