<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Timothy Malche</title>
    <description>The latest articles on Forem by Timothy Malche (@timothy_malche_56945869fc).</description>
    <link>https://forem.com/timothy_malche_56945869fc</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%2F3506975%2Faa6a2d47-e4c3-47d6-8a07-57720dad32fe.jpg</url>
      <title>Forem: Timothy Malche</title>
      <link>https://forem.com/timothy_malche_56945869fc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/timothy_malche_56945869fc"/>
    <language>en</language>
    <item>
      <title>Mastering Postgres Window Functions for Data Analysis in Supabase</title>
      <dc:creator>Timothy Malche</dc:creator>
      <pubDate>Wed, 24 Sep 2025 12:14:54 +0000</pubDate>
      <link>https://forem.com/timothy_malche_56945869fc/mastering-postgres-window-functions-for-data-analysis-in-supabase-3o5f</link>
      <guid>https://forem.com/timothy_malche_56945869fc/mastering-postgres-window-functions-for-data-analysis-in-supabase-3o5f</guid>
      <description>&lt;p&gt;Postgres &lt;a href="https://www.postgresql.org/docs/current/functions-window.html" rel="noopener noreferrer"&gt;window functions&lt;/a&gt; are a powerful SQL feature that lets you perform calculations across rows related to the current row without collapsing results. Unlike &lt;a href="https://www.postgresql.org/docs/current/tutorial-agg.html" rel="noopener noreferrer"&gt;aggregate functions&lt;/a&gt; that reduce multiple rows into a single value, window functions retain each row while allowing computations such as running totals, moving averages, and rankings. They work well for real-time analytics, user scoring, or time-series comparisons, which are commonly needed in modern data-driven apps.&lt;/p&gt;

&lt;p&gt;Window functions allow you to express powerful analytics logic in a single SQL statement that's easier to write and debug and optimized by Postgres under the hood. This makes your queries faster and easier to manage.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain what window functions are, how they differ from traditional aggregate functions, and how to use them effectively in Postgres. I'll walk you through real-world examples of their usage, such as ranking users, calculating moving averages, tracking historical changes, and computing running totals, all while using clean, performant SQL that runs directly in your database.&lt;/p&gt;

&lt;p&gt;All examples in this blog are tested on &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;, an open source backend-as-a-service (BaaS) platform that simplifies backend development for web and mobile applications. Supabase provides full support for &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;Postgres&lt;/a&gt; databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Postgres Window Functions
&lt;/h2&gt;

&lt;p&gt;The term &lt;em&gt;window function&lt;/em&gt; in SQL comes from the analogy of a sliding window that moves across data, allowing for calculations on a specific subset ("window" or "view") of rows at a time. A &lt;em&gt;window&lt;/em&gt; is a set of rows that are related to the current row and over which a window function is executed.&lt;/p&gt;

&lt;p&gt;Window functions retain each row while still allowing calculations across neighboring rows. This makes them incredibly useful for analytical tasks like running totals, rankings, percent changes, or comparing a value to a group average. Window functions are used to write clean, expressive, and high-performance SQL. Here's the basic structure of a window 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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="n"&gt;column_list&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="n"&gt;column_list&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="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="n"&gt;frame_clause&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;The previous snippet's components function as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PARTITION BY&lt;/code&gt; divides the data set into separate groups (like departments or users). Each group is treated as an independent mini-table.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; specifies the order for the rows within each partition, affecting how cumulative or ranking operations are performed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ROWS / RANGE&lt;/code&gt; defines the frame—how many rows to include relative to the current row.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Window Functions vs. Aggregate Functions
&lt;/h2&gt;

&lt;p&gt;The easiest way to understand the difference between the window functions and aggregate functions is with an example. Imagine you want to analyze sales data for the following table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;customer_name&lt;/th&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;product&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;quantity&lt;/th&gt;
&lt;th&gt;unit_price&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;sale_date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Laptop A&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;800.00&lt;/td&gt;
&lt;td&gt;800.00&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Phone X&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;1000.00&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;Accessories&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;td&gt;60.00&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Keyboard&lt;/td&gt;
&lt;td&gt;Accessories&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Eva&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Laptop B&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;900.00&lt;/td&gt;
&lt;td&gt;900.00&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Frank&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Grace&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Headphones&lt;/td&gt;
&lt;td&gt;Accessories&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Heidi&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Phone X&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;2024-01-05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're analyzing sales by region, an aggregate query using &lt;code&gt;SUM(amount)&lt;/code&gt; with &lt;code&gt;GROUP BY region&lt;/code&gt; will give you the total per region but discard individual transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the above statement will give you the following result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;sum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;1860.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;400.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;950.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A window function instead adds a column with the total sales per region alongside each original sale record. This means you get both the group-level insight and the detailed row-level data in a single result—ideal for use cases like percentiles or ranking users without losing context. Take the following window function as 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;SELECT&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;region&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;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales_per_region&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;total_sales_per_region&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;400.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;400.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;60.00&lt;/td&gt;
&lt;td&gt;1860.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;1000.00&lt;/td&gt;
&lt;td&gt;1860.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;800.00&lt;/td&gt;
&lt;td&gt;1860.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;900.00&lt;/td&gt;
&lt;td&gt;950.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;td&gt;950.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Window Frames
&lt;/h2&gt;

&lt;p&gt;While the "window" is a set of rows that a window function can operate on that is defined using the &lt;code&gt;PARTITION BY&lt;/code&gt; (and optionally, &lt;code&gt;ORDER BY&lt;/code&gt;) clause, a "window frame" is a subset of the "window", used for row-by-row calculation. It's defined using &lt;code&gt;ROWS&lt;/code&gt; or &lt;code&gt;RANGE&lt;/code&gt; clauses (like &lt;code&gt;ROWS BETWEEN 1 PRECEDING AND CURRENT ROW&lt;/code&gt;). The "window frame" controls how many rows around the current row are used in the calculation. The window frame answers the question &lt;em&gt;"Which nearby rows should be considered for this calculation?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Window frames can be defined in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ROWS&lt;/code&gt;: Selects a fixed number of rows before and after the current row, no matter what the values are.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING&lt;/code&gt; includes the row before, the current row, and the row after.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;RANGE&lt;/code&gt;: Selects rows that have similar values based on the &lt;code&gt;ORDER BY&lt;/code&gt; column.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&lt;/code&gt; includes all rows with values less than or equal to the current one and treats tied values as a group.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The following are some common window-frame clause keywords:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clause&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The very first row in the partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The very last row in the partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CURRENT ROW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The row currently being evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;N&amp;gt; PRECEDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N rows before the current row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;N&amp;gt; FOLLOWING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N rows after the current row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Complete frame clauses combine these keywords to define a range. For example, this creates a three-row window—the previous row, the current row, and the next 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;ROWS&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="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This includes all rows from the partition's start up to the current 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;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;Together, these give you precise control over which rows are used in each window function's calculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Types of Window Functions
&lt;/h2&gt;

&lt;p&gt;The common types of window functions are divided into three major categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ranking functions:&lt;/strong&gt; These functions assign a rank or position to each row within a partition based on a specified ordering—for example, &lt;code&gt;ROW_NUMBER()&lt;/code&gt;, &lt;code&gt;RANK()&lt;/code&gt;, &lt;code&gt;DENSE_RANK()&lt;/code&gt;, and &lt;code&gt;NTILE(n)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate functions:&lt;/strong&gt; These functions perform a calculation on a set of rows and return a single value. When used as window functions, they operate within a defined window (partition) instead of collapsing rows like in a &lt;code&gt;GROUP BY&lt;/code&gt; clause. The aggregate functions are &lt;code&gt;SUM()&lt;/code&gt;, &lt;code&gt;AVG()&lt;/code&gt;, &lt;code&gt;COUNT()&lt;/code&gt;, &lt;code&gt;MAX()&lt;/code&gt;, and &lt;code&gt;MIN()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value functions:&lt;/strong&gt; These functions retrieve values from another row in the window frame relative to the current row. The functions are &lt;code&gt;LAG(n)&lt;/code&gt;, &lt;code&gt;LEAD(n)&lt;/code&gt;, &lt;code&gt;FIRST_VALUE()&lt;/code&gt;, &lt;code&gt;LAST_VALUE()&lt;/code&gt;, and &lt;code&gt;NTH_VALUE(n)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following table summarizes each of these function types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function Type&lt;/th&gt;
&lt;th&gt;What does it do?&lt;/th&gt;
&lt;th&gt;Why is it useful?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate&lt;/td&gt;
&lt;td&gt;Calculates totals, averages, etc.&lt;/td&gt;
&lt;td&gt;Compare rows with group totals; calculate trends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ranking&lt;/td&gt;
&lt;td&gt;Assigns rank or order to rows&lt;/td&gt;
&lt;td&gt;Leaderboards, top-N queries, grouped rankings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;Retrieves relative row values&lt;/td&gt;
&lt;td&gt;Change detection, trend analysis, time comparisons&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's now explore each of these function types with real-world examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranking Functions in Action
&lt;/h2&gt;

&lt;p&gt;Postgres provides a powerful set of ranking functions that allow you to assign positions to rows based on a given order. As an example, this article uses a leaderboard-building scenario, where you can use the functions to perform actions such as identifying top performers, dividing users into cohorts, or analyzing performance relative to peers.&lt;/p&gt;

&lt;p&gt;Ranking functions are always used with the &lt;code&gt;OVER(ORDER BY ...)&lt;/code&gt; clause, and they do not collapse rows like &lt;code&gt;GROUP BY&lt;/code&gt;; they preserve row-level detail while adding new insights. The example below uses the following &lt;code&gt;student_results&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;student_id&lt;/th&gt;
&lt;th&gt;student_name&lt;/th&gt;
&lt;th&gt;subject&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  ROW_NUMBER()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt; assigns a unique sequential number to each row within a partition based on the order specified. Tied values are given different row numbers arbitrarily. The following example ranks students within each subject based on their score:&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;student_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; 
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;row_num&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;student_results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output assigns a unique row number starting from 1 for the top scorer within each subject:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;subject&lt;/th&gt;
&lt;th&gt;student_name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;row_num&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  RANK()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RANK()&lt;/code&gt; assigns a rank to each row. Tied values receive the same rank, but the next rank is skipped to preserve the total count. Here's an example that assigns a rank to each student within their subject based on descending scores, giving equal ranks for tied scores and skipping the next rank(s) accordingly:&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;student_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; 
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;student_results&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 output, Abigail and Sarah share rank 1 in the subject Math. Adam is ranked at 3 because  it's the next available in Maths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;subject&lt;/th&gt;
&lt;th&gt;student_name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  DENSE_RANK()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt; works like &lt;code&gt;RANK()&lt;/code&gt;, but there are no gaps (&lt;em&gt;ie&lt;/em&gt; no numbers are skipped) in the ranking sequence. Ties get the same rank, and the next rank increases by one. The following query assigns a dense rank to each student within their subject based on descending scores:&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;student_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; 
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dense_rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;student_results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it gives equal ranks for tied scores without skipping the next rank (ranks remain consecutive), Adam gets rank 2 in Math this time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;subject&lt;/th&gt;
&lt;th&gt;student_name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  NTILE(n)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NTILE(n)&lt;/code&gt; function distributes rows into n equal groups (or as evenly as possible). Each group is assigned a bucket number starting from 1. This query divides students within each subject into two equally sized groups (tiles) based on descending scores:&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;student_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;NTILE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;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;subject&lt;/span&gt; 
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;performance_bucket&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;student_results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also labels them as performance buckets (&lt;em&gt;eg&lt;/em&gt; 1 = higher performers; 2 = lower performers):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;subject&lt;/th&gt;
&lt;th&gt;student_name&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;performance_bucket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Abigail&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Sarah&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;Bruce&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Aggregate Window Functions in Action
&lt;/h2&gt;

&lt;p&gt;Aggregate window functions allow you to perform aggregate calculations like &lt;code&gt;SUM()&lt;/code&gt;, &lt;code&gt;AVG()&lt;/code&gt;, and &lt;code&gt;COUNT()&lt;/code&gt; without collapsing the result set. These functions are useful in calculating things like running totals, moving averages, or group-level statistics while still seeing individual records. Let's walk through these key aggregate window functions and how they're used in real-world scenarios using the following &lt;code&gt;sales_commissions&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;sale_id&lt;/th&gt;
&lt;th&gt;employee_name&lt;/th&gt;
&lt;th&gt;product_name&lt;/th&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;units_sold&lt;/th&gt;
&lt;th&gt;unit_price&lt;/th&gt;
&lt;th&gt;commission_rate&lt;/th&gt;
&lt;th&gt;sale_date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;500.00&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1000.00&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1000.00&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1000.00&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;300.00&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Eva&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;300.00&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Frank&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;300.00&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;250.00&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SUM()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SUM()&lt;/code&gt; calculates a running total (cumulative sum) of values as you move through rows or the total sum for an entire group without collapsing the original data. When calculating the running total, it adds values row by row in order (&lt;em&gt;eg&lt;/em&gt; daily sales accumulating over time). In calculating group total, it sums all values in a category (&lt;em&gt;eg&lt;/em&gt; total sales per product) while keeping individual rows intact. This query calculates each employee's commission and shows the total and running (cumulative) commission per product over time:&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;employee_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_commission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Sum of total commission per product&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;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&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;product_name&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_product_commission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Running total commission per product (ordered by date)&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;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&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;product_name&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;sale_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_commission&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_commissions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee_name&lt;/th&gt;
&lt;th&gt;product_name&lt;/th&gt;
&lt;th&gt;sale_date&lt;/th&gt;
&lt;th&gt;total_commission&lt;/th&gt;
&lt;th&gt;total_product_commission&lt;/th&gt;
&lt;th&gt;running_commission&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;300.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;300.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;400.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;700.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;500.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;125.0000&lt;/td&gt;
&lt;td&gt;524.0000&lt;/td&gt;
&lt;td&gt;125.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;126.0000&lt;/td&gt;
&lt;td&gt;524.0000&lt;/td&gt;
&lt;td&gt;251.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eva&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;126.0000&lt;/td&gt;
&lt;td&gt;524.0000&lt;/td&gt;
&lt;td&gt;377.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frank&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;td&gt;147.0000&lt;/td&gt;
&lt;td&gt;524.0000&lt;/td&gt;
&lt;td&gt;524.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;320.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;320.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;400.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;720.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;480.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;td&gt;1200.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  AVG()
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AVG()&lt;/code&gt; window function calculates the average (mean) of a set of values over a specific group of rows called a "window." Instead of computing the average for the whole table, it gives you an average that changes row by row based on nearby rows in the window. This is useful for things like moving averages or spotting trends over time. This query computes each employee's commission and shows the overall average and moving (cumulative) average commission per product over time based on the sale date:&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;employee_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_commission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Overall average commission for the product&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;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&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;product_name&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;average_commission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Moving average commission per product, ordered by sale date&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;units_sold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;commission_rate&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;product_name&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;sale_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;moving_average_commission&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_commissions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee_name&lt;/th&gt;
&lt;th&gt;product_name&lt;/th&gt;
&lt;th&gt;sale_date&lt;/th&gt;
&lt;th&gt;total_commission&lt;/th&gt;
&lt;th&gt;average_commission&lt;/th&gt;
&lt;th&gt;moving_average_commission&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;300.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;300.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;400.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;350.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;500.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;125.0000&lt;/td&gt;
&lt;td&gt;131.0000000000000000&lt;/td&gt;
&lt;td&gt;125.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;126.0000&lt;/td&gt;
&lt;td&gt;131.0000000000000000&lt;/td&gt;
&lt;td&gt;125.5000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eva&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;126.0000&lt;/td&gt;
&lt;td&gt;131.0000000000000000&lt;/td&gt;
&lt;td&gt;125.6666666666666667&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frank&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;td&gt;147.0000&lt;/td&gt;
&lt;td&gt;131.0000000000000000&lt;/td&gt;
&lt;td&gt;131.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;320.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;320.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;400.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;360.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;480.0000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;td&gt;400.0000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  COUNT()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;COUNT()&lt;/code&gt; counts how many rows fall within a defined window frame. It can be used for cumulative and group-wise counting. This query shows each employee's sale and calculates the total number of sales for each product and a running (cumulative) count of sales per product over time based on &lt;code&gt;sale_date&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;employee_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Group-wise count: total sales records for this product&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;product_name&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales_per_product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Cumulative count: running count of sales records per product by sale_date&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;product_name&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;sale_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;cumulative_sales_count&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_commissions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The output from this query is as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee_name&lt;/th&gt;
&lt;th&gt;product_name&lt;/th&gt;
&lt;th&gt;sale_date&lt;/th&gt;
&lt;th&gt;total_sales_per_product&lt;/th&gt;
&lt;th&gt;cumulative_sales_count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;David&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eva&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frank&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;2024-01-02&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Value Window Functions in Action
&lt;/h2&gt;

&lt;p&gt;Value window functions are focused on fetching data from neighboring rows within a window frame and can compare the current row to past or future data. These functions are particularly useful in time-series analysis, trend detection, and change tracking. Let's explore different value window functions through the following &lt;code&gt;package_tracking&lt;/code&gt; table example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tracking_id&lt;/th&gt;
&lt;th&gt;package_id&lt;/th&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;status_time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Newark&lt;/td&gt;
&lt;td&gt;2024-06-01 12:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Harrisburg&lt;/td&gt;
&lt;td&gt;2024-06-01 18:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 08:15:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 10:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;San Francisco&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Oakland&lt;/td&gt;
&lt;td&gt;2024-06-03 10:45:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Palo Alto&lt;/td&gt;
&lt;td&gt;2024-06-03 15:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 11:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LAG()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LAG()&lt;/code&gt; returns the value of a column from a previous row based on the &lt;code&gt;ORDER BY&lt;/code&gt; clause. You can specify how many rows back to look, and the default is 1. This SQL query analyzes how much time passed between each checkpoint update for every package in the &lt;code&gt;package_tracking&lt;/code&gt; table—that is, it calculates the delay or duration between checkpoints:&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;package_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_time&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;package_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;status_time&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;previous_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_time&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;package_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;status_time&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hours_since_last&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;package_tracking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first row returns "null" because there's no previous row to look back:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;package_id&lt;/th&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;status_time&lt;/th&gt;
&lt;th&gt;previous_time&lt;/th&gt;
&lt;th&gt;hours_since_last&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Newark&lt;/td&gt;
&lt;td&gt;2024-06-01 12:30:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;td&gt;3.5000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Harrisburg&lt;/td&gt;
&lt;td&gt;2024-06-01 18:00:00&lt;/td&gt;
&lt;td&gt;2024-06-01 12:30:00&lt;/td&gt;
&lt;td&gt;5.5000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 08:15:00&lt;/td&gt;
&lt;td&gt;2024-06-01 18:00:00&lt;/td&gt;
&lt;td&gt;14.2500000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 10:00:00&lt;/td&gt;
&lt;td&gt;2024-06-02 08:15:00&lt;/td&gt;
&lt;td&gt;1.7500000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;San Francisco&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Oakland&lt;/td&gt;
&lt;td&gt;2024-06-03 10:45:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;td&gt;2.7500000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Palo Alto&lt;/td&gt;
&lt;td&gt;2024-06-03 15:30:00&lt;/td&gt;
&lt;td&gt;2024-06-03 10:45:00&lt;/td&gt;
&lt;td&gt;4.7500000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 09:00:00&lt;/td&gt;
&lt;td&gt;2024-06-03 15:30:00&lt;/td&gt;
&lt;td&gt;17.5000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 11:30:00&lt;/td&gt;
&lt;td&gt;2024-06-04 09:00:00&lt;/td&gt;
&lt;td&gt;2.5000000000000000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LEAD()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LEAD()&lt;/code&gt; returns the value of a column from the next row. Like &lt;code&gt;LAG()&lt;/code&gt;, you can define how far ahead to look. You can expose this info in your app or dashboard and show the next expected checkpoint, which is useful for live tracking systems or ETAs.&lt;/p&gt;

&lt;p&gt;This query shows the most recent checkpoint and what's expected next:&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;package_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;checkpoint&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_checkpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;checkpoint&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;package_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;status_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;next_checkpoint&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;package_tracking&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;package_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&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;status_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&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;Here's the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;package_id&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;current_checkpoint&lt;/th&gt;
&lt;th&gt;status_time&lt;/th&gt;
&lt;th&gt;next_checkpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;2024-06-02 10:00:00&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FIRST_VALUE()
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;FIRST_VALUE()&lt;/code&gt; function returns the value of the first row in the window frame. This query retrieves the dispatch time for each package and the earliest recorded &lt;code&gt;status_time&lt;/code&gt; per &lt;code&gt;package_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;package_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Show origin dispatch time for the package&lt;/span&gt;
  &lt;span class="n"&gt;FIRST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_time&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;package_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;status_time&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;dispatched_on&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;package_tracking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the output, which allows you to compare all other checkpoints against when the package was first sent:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;package_id&lt;/th&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;status_time&lt;/th&gt;
&lt;th&gt;dispatched_on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Newark&lt;/td&gt;
&lt;td&gt;2024-06-01 12:30:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Harrisburg&lt;/td&gt;
&lt;td&gt;2024-06-01 18:00:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 08:15:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 10:00:00&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;San Francisco&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Oakland&lt;/td&gt;
&lt;td&gt;2024-06-03 10:45:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Palo Alto&lt;/td&gt;
&lt;td&gt;2024-06-03 15:30:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 09:00:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 11:30:00&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LAST_VALUE()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LAST_VALUE()&lt;/code&gt; gives you the value from the last row in your window frame. By default, the window goes up only to the current row (not beyond it). So unless you extend the frame (using something like &lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING&lt;/code&gt;), it might return the current row's value, not the true last row of the whole group. This query checks whether a package has reached its final destination city (not the delivery status) by comparing the current checkpoint's city with the last city in the delivery path:&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;package_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Get the final destination city (same for all rows in that package)&lt;/span&gt;
  &lt;span class="n"&gt;LAST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&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;package_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;status_time&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;final_destination_city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- Compare current city to final destination&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt; 
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LAST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&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;package_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;status_time&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="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Yes'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'No'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;is_final_destination_reached&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;package_tracking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;package_id&lt;/th&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;status_time&lt;/th&gt;
&lt;th&gt;final_destination_city&lt;/th&gt;
&lt;th&gt;is_final_destination_reached&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;2024-06-01 09:00:00&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Newark&lt;/td&gt;
&lt;td&gt;2024-06-01 12:30:00&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Harrisburg&lt;/td&gt;
&lt;td&gt;2024-06-01 18:00:00&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 08:15:00&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;2024-06-02 10:00:00&lt;/td&gt;
&lt;td&gt;Baltimore&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Picked Up&lt;/td&gt;
&lt;td&gt;San Francisco&lt;/td&gt;
&lt;td&gt;2024-06-03 08:00:00&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Distribution Hub&lt;/td&gt;
&lt;td&gt;Oakland&lt;/td&gt;
&lt;td&gt;2024-06-03 10:45:00&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;In Transit&lt;/td&gt;
&lt;td&gt;Palo Alto&lt;/td&gt;
&lt;td&gt;2024-06-03 15:30:00&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Out for Delivery&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 09:00:00&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;2024-06-04 11:30:00&lt;/td&gt;
&lt;td&gt;San Jose&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Remember, the clause &lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING&lt;/code&gt; is essential when using &lt;code&gt;LAST_VALUE()&lt;/code&gt; in window functions because without it, &lt;code&gt;LAST_VALUE()&lt;/code&gt; returns the current row's value and not the true last value in the partition due to the default frame ending at the current row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls and Troubleshooting with Window Functions
&lt;/h2&gt;

&lt;p&gt;Window functions are powerful, but small mistakes can lead to confusing results or slow performance. Here are ways to avoid common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unstable ordering:&lt;/strong&gt; If &lt;code&gt;ORDER BY&lt;/code&gt; values are tied, the row order becomes unpredictable. Fix this by adding a secondary column: &lt;code&gt;ORDER BY score DESC, student_id&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Unexpected ranks:&lt;/strong&gt; If two rows have the same value, &lt;code&gt;RANK()&lt;/code&gt; gives them the same rank but skips the next number (creating a gap). &lt;code&gt;DENSE_RANK()&lt;/code&gt; also gives the same rank but doesn't skip any numbers. So tied values affect how ranks are counted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance issues:&lt;/strong&gt; Using complex window ranges or grouping by columns with too many unique values (like IDs) can make queries slow. Instead, use simpler window frames and group by useful categories like departments or regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null behavior:&lt;/strong&gt; By default, Postgres treats null values as the smallest when sorting. You can use &lt;code&gt;NULLS LAST&lt;/code&gt; if you want them at the end. &lt;code&gt;LAG()&lt;/code&gt; and &lt;code&gt;LEAD()&lt;/code&gt; also return "null" when there's no previous or next row. Use a default value like &lt;code&gt;LAG(sales, 1, 0)&lt;/code&gt; to avoid this.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; Debug by starting with a minimal query and adding complexity step by step. It helps reveal how window frames and partitions are applied.&lt;/p&gt;

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

&lt;p&gt;Postgres window functions let you perform advanced analytics—like running totals, rankings, and comparisons—without losing row-level detail. They are more flexible and readable than complex subqueries or joins. We saw real-world examples like sales analysis, student rankings, and time-based trends using different window functions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>sql</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
