{"id":19533,"date":"2022-09-28T04:47:40","date_gmt":"2022-09-28T11:47:40","guid":{"rendered":"https:\/\/coderpad.io\/?p=19533"},"modified":"2023-06-07T13:58:36","modified_gmt":"2023-06-07T20:58:36","slug":"window-functions-aggregate-data-postgres","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/window-functions-aggregate-data-postgres\/","title":{"rendered":"Why &#038; How to Use Window Functions to Aggregate Data in Postgres"},"content":{"rendered":"\n<p>A window function is a feature developed in PostgreSQL &#8212; available since <a href=\"https:\/\/www.postgresql.org\/docs\/8.4\/tutorial-window.html\" target=\"_blank\" rel=\"noreferrer noopener\">version 8.4<\/a>&#8212; to analyze data beyond the current row (hence the term &#8220;window&#8221;).<\/p>\n\n\n\n<p>These windows can aggregate information to each row of your output. Apart from <a href=\"https:\/\/www.postgresql.org\/docs\/current\/tutorial-agg.html\" target=\"_blank\" rel=\"noreferrer noopener\">aggregate functions<\/a> and groupings, window functions provide another way to perform calculations based on the values of several records.<\/p>\n\n\n\n<p><strong>Using window functions removes the hassle of using subqueries and <\/strong><code>JOINS<\/code><strong> to aggregate neighboring rows that can be related to the current row.<\/strong><\/p>\n\n\n\n<p>This tutorial will show you how aggregate functions are used as window functions. You will learn how to use <code>MAX()<\/code>, <code>MIN()<\/code>, <code>AVG()<\/code>, <code>SUM()<\/code>, <code>ROW_NUMBER()<\/code>, <code>RANK()<\/code>, and <code>DENSE_RANK()<\/code> as window functions. But first, you need to know why you should use them.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u2705 You can use the CoderPad sandbox <a href=\"#try-it-out\">at the bottom of this page<\/a> or <a href=\"https:\/\/app.coderpad.io\/sandbox?question_id=229021\" target=\"_blank\" rel=\"noreferrer noopener\">as a new browser window<\/a> to run the queries in this tutorial &#8212; the table is already loaded for you!<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why use window functions<\/strong><\/h2>\n\n\n\n<p>To get an idea of why you should use window functions, let\u2019s start with an example database:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc84e6aa.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc84e6aa.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>Using this data, we want to get a breakdown of salaries for each employee in every department. We want to analyze the max salary across each department to compare it with each employee record.<\/p>\n\n\n\n<p>To get that query result, you would probably use a <a href=\"https:\/\/www.postgresql.org\/docs\/current\/queries-with.html\" target=\"_blank\" rel=\"noreferrer noopener\">Common Table Expression (CTE)<\/a> with a <code>JOIN<\/code> statement like the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">WITH<\/span> dep_stats <span class=\"hljs-keyword\">AS<\/span> (\n\u00a0 \u00a0 <span class=\"hljs-keyword\">SELECT<\/span> dep_name, max(salary) max_salary\n\u00a0 \u00a0 <span class=\"hljs-keyword\">FROM<\/span> emp_salaries\n\u00a0 \u00a0 <span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name\n)\n\n<span class=\"hljs-keyword\">SELECT<\/span> e.*, d.max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries e\n<span class=\"hljs-keyword\">LEFT OUTER JOIN<\/span> dep_stats d <span class=\"hljs-keyword\">ON<\/span> d.dep_name = e.dep_name;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That query will give you this output:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc9b8cb0.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc9b8cb0.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>But a better way to get the same result is to use a window function like the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> e.*,\n     MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries e;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see, using a window function makes writing complex SQL queries easy. It allows you to output the current rows and add aggregate values accordingly.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u2139\ufe0f Notice that window functions allowed us to perform aggregation without using the <code>GROUP BY<\/code> clause, as in the case of aggregate functions.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Create a sample table<\/strong><\/h2>\n\n\n\n<p>In this tutorial, you can experiment with your own table or create a sample table that we will borrow from the <a href=\"https:\/\/www.postgresql.org\/docs\/current\/tutorial-window.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL documentation<\/a> with a simple tweak. If you&#8217;re doing this tutorial from your own IDE, you can create your sample table by running the following script:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">DROP<\/span> <span class=\"hljs-keyword\">TABLE<\/span> <span class=\"hljs-keyword\">IF<\/span> <span class=\"hljs-keyword\">EXISTS<\/span> emp_salaries;\n\n<span class=\"hljs-keyword\">CREATE<\/span> <span class=\"hljs-keyword\">TABLE<\/span> emp_salaries (\n  emp_id <span class=\"hljs-type\">SERIAL<\/span> <span class=\"hljs-keyword\">PRIMARY KEY<\/span>,\n  emp_name <span class=\"hljs-type\">VARCHAR<\/span>(<span class=\"hljs-number\">30<\/span>),\n  dep_name <span class=\"hljs-type\">VARCHAR<\/span>(<span class=\"hljs-number\">40<\/span>),\n  salary <span class=\"hljs-type\">DECIMAL<\/span>\n);\n\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">11<\/span>, <span class=\"hljs-string\">'Ahmed'<\/span>, <span class=\"hljs-string\">'Engineering'<\/span>, <span class=\"hljs-number\">5200<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">7<\/span>, <span class=\"hljs-string\">'Ali'<\/span>, <span class=\"hljs-string\">'Engineering'<\/span>, <span class=\"hljs-number\">4200<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">9<\/span>, <span class=\"hljs-string\">'Ibrahim'<\/span>, <span class=\"hljs-string\">'Engineering'<\/span>, <span class=\"hljs-number\">4500<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">8<\/span>, <span class=\"hljs-string\">'Mohamed'<\/span>, <span class=\"hljs-string\">'Engineering'<\/span>, <span class=\"hljs-number\">6000<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-string\">'Ayman'<\/span>, <span class=\"hljs-string\">'Engineering'<\/span>, <span class=\"hljs-number\">5200<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">5<\/span>, <span class=\"hljs-string\">'Moemen'<\/span>, <span class=\"hljs-string\">'HR'<\/span>, <span class=\"hljs-number\">3500<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-string\">'Moetaz'<\/span>, <span class=\"hljs-string\">'HR'<\/span>, <span class=\"hljs-number\">3900<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">3<\/span>, <span class=\"hljs-string\">'Abdullah'<\/span>, <span class=\"hljs-string\">'Sales'<\/span>, <span class=\"hljs-number\">4800<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-string\">'Assem'<\/span>, <span class=\"hljs-string\">'Sales'<\/span>, <span class=\"hljs-number\">5000<\/span>);\n<span class=\"hljs-keyword\">INSERT<\/span> <span class=\"hljs-keyword\">INTO<\/span> emp_salaries <span class=\"hljs-keyword\">VALUES<\/span> (<span class=\"hljs-number\">4<\/span>, <span class=\"hljs-string\">'Omar'<\/span>, <span class=\"hljs-string\">'Sales'<\/span>, <span class=\"hljs-number\">4800<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u26a0\ufe0f If you&#8217;re testing in your own environment, you&#8217;ll drop a table of the <code>emp_salaries<\/code> name if it does exist. <\/p>\n\n\n\n<p>Alternatively if you want to skip the table creation task, you can use the CoderPad sandbox <a href=\"#try-it-out\">at the bottom of this page<\/a> or <a href=\"https:\/\/app.coderpad.io\/sandbox?question_id=229021\" target=\"_blank\" rel=\"noreferrer noopener\">as a new browser window\/tab<\/a> to run the rest of the tutorial, as the sandbox already has the table loaded for you.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>PostgreSQL window function syntax<\/strong><\/h2>\n\n\n\n<p>The syntax of a window function is simple; it looks something like this:<\/p>\n\n\n\n<p><code>&lt;function_name&gt;(&lt;argument(s)&gt;) OVER(PARTITION BY &lt;column(s)&gt; ORDER BY &lt;column(s)&gt;) &lt;alias&gt;<\/code><\/p>\n\n\n\n<p>The <code>&lt;function_name&gt;<\/code> is an aggregate function like <code>SUM()<\/code>, <code>COUNT()<\/code>, <code>MAX()<\/code>, <code>MIN()<\/code>, <code>AVG()<\/code>, <code>RANK()<\/code>, etc. This function can take a column as an argument and can also take other arguments depending on the use case.<\/p>\n\n\n\n<p>The <code>OVER()<\/code> clause defines the window size. If it&#8217;s empty and <code>PARTITION BY<\/code> and <code>ORDER BY<\/code> are omitted, it means the window is across the whole table. However, <code>PARTITION BY<\/code> partitions the window by the column(s) that follow the clause. In addition, <code>ORDER BY<\/code> sorts the partitioned window by the column(s) indicated by <code>ORDER BY<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>PostgreSQL window functions examples<\/strong><\/h2>\n\n\n\n<p>To get an idea of how to best use window functions, we\u2019ll start with a simple SQL query using an aggregate function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> MAX(salary) max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This query outputs the maximum salary across all the records.<\/p>\n\n\n\n<p>What if you want this max value next to each record in the table?<\/p>\n\n\n\n<p>You might say this is easy; just add <code>*<\/code> next to the <code>max_salary<\/code> column. However, that query would create the following error:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cca59fbd.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cca59fbd.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>This means that the first column of the <code>emp_salaries<\/code> table &#8212; <code>emp_id<\/code> column must appear in the <code>GROUP BY<\/code> clause or be used in an aggregate function.<\/p>\n\n\n\n<p>To avoid this error, you have two options:&nbsp;<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A long route using a CTE along with a <code>JOIN<\/code> statement.&nbsp;<\/li>\n\n\n\n<li>A shorter route using a window function.&nbsp;<\/li>\n<\/ol>\n\n\n\n<p>The shorter (and preferred) option for us is to take the window function route and write the following query:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> *, MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>() max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With just <code>OVER()<\/code>, you can use <code>MAX()<\/code> as a window function to aggregate the maximum salary to each record in the table:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cca9ad72.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cca9ad72.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>GROUP BY<\/strong><strong> vs. <\/strong><strong>PARTITION BY<\/strong><strong> clauses<\/strong><\/h3>\n\n\n\n<p>What if you want a breakdown of max salaries across each department?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> MAX(salary) max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries e\n<span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This query gets only the maximum salary across each department:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccae51f5.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccae51f5.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>Here, there is one column that represents the max salary per department. Again you wouldn&#8217;t easily be able to combine the other columns with the max_salary column unless you use window functions.<\/p>\n\n\n\n<p>To compare each record with its corresponding maximum salary by department, use <code>PARTITION BY<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> *,\n     MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) max_salary\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This query results in the following records:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc9b8cb0.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331cc9b8cb0.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, the aggregate is done over the department names. So you get a breakdown of the max salary of each department next to each record in the <code>emp_salaries<\/code> table.<\/p>\n\n\n\n<p>If you\u2019re still confused about the difference between <code>GROUP BY<\/code> and <code>PARTITION BY<\/code>, remember this: <code>GROUP BY<\/code> <a href=\"https:\/\/stackoverflow.com\/a\/2404587\/4604121\" target=\"_blank\" rel=\"noreferrer noopener\">reduces the number of rows<\/a> returned by rolling them up to calculate the aggregate function for each row. <code>PARTITION BY<\/code> partitions the window based on a specific column; thus it doesn&#8217;t affect the number of rows in the result.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>MAX()<\/strong><strong>, <\/strong><strong>MIN()<\/strong><strong>, <\/strong><strong>AVG()<\/strong><strong>, <\/strong><strong>SUM()<\/strong><strong>, <\/strong><strong>COUNT()<\/strong><strong> functions<\/strong><\/h3>\n\n\n\n<p>It&#8217;s important to note that the previous examples that you used with <code>MAX()<\/code> can also be applied with either <code>MIN()<\/code>, <code>SUM()<\/code>, or <code>COUNT()<\/code>. These are other aggregate functions and can also be used as window functions as follows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> \n  emp_id, \n  salary, \n  MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) max_salary, \n  MIN(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) min_salary, \n  SUM(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) total_salaries, \n  COUNT(*) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) emp_count\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>which outputs the following result:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccb6aa0e.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccb6aa0e.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<blockquote class=\"wp-block-quote\">\n<p>While we don&#8217;t cover <code>AVG()<\/code> here, you can find more information on it in the <a href=\"https:\/\/www.postgresql.org\/docs\/8.2\/functions-aggregate.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL docs here<\/a>.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>ROW_NUMBER()<\/strong><strong> function<\/strong><\/h3>\n\n\n\n<p>The <code>ROW_NUMBER()<\/code> is another useful window function. It assigns serial numbers to records according to the window. See the following example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> *, \n  MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(\n               <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name\n               ) max_salary, \n  ROW_NUMBER() <span class=\"hljs-keyword\">OVER<\/span>() row_num\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccb89c21.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccb89c21.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, the row_num column contains an ordered list of numbers across the whole table. That&#8217;s because the window here has boundaries across the table, as the <code>OVER()<\/code> clause doesn&#8217;t have arguments.<\/p>\n\n\n\n<p>A practical use case for <code>ROW_NUMBER()<\/code> is to get the most senior employee in each department. Try it on your own and see what you get.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> \n  *, \n  MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) max_salary, \n  ROW_NUMBER() <span class=\"hljs-keyword\">OVER<\/span>(<span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name) seniority\n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Notice that in this case we partition by the department name. That partition would give you a result like this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccbbdfb4.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccbbdfb4.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, there is an ordered list according to each department. The engineering department has numbers <code>1<\/code> to <code>5<\/code>. HR: <code>1<\/code> and <code>2<\/code>. Sales: <code>1<\/code> to <code>3<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>PARTITION BY<\/strong><strong> and <\/strong><strong>ORDER BY<\/strong><strong> clauses<\/strong><\/h3>\n\n\n\n<p>A window function can have <code>ORDER BY<\/code> inside an <code>OVER()<\/code> clause. To see what that looks like, assume that <code>emp_id<\/code> represents seniority and that an <code>emp_id<\/code> of <code>8<\/code> precedes <code>9<\/code>, meaning that the former joined the company before the latter. We will then sort according to the <code>emp_id<\/code> to see the first employees who joined each department:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> *, \n  MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(\n               <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name\n               ) max_salary, \n  ROW_NUMBER() <span class=\"hljs-keyword\">OVER<\/span>(\n    <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name \n    <span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> emp_id\n    ) seniority \n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The seniority column represents ordered numbers according to the <code>emp_id<\/code>. If you filter by seniority with <code>1<\/code> values, you get the first employee for each department who joined the company.<\/p>\n\n\n\n<p>Give it a try here using a subquery:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> * \n<span class=\"hljs-keyword\">FROM<\/span> (\n    <span class=\"hljs-keyword\">SELECT<\/span> *, \n         MAX(salary) <span class=\"hljs-keyword\">OVER<\/span>(\n                      <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name\n                      ) max_salary, \n         ROW_NUMBER() <span class=\"hljs-keyword\">OVER<\/span>(\n                      <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name \n                      <span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> emp_id\n                      ) seniority \n    <span class=\"hljs-keyword\">FROM<\/span> emp_salaries\n  ) e \n<span class=\"hljs-keyword\">WHERE<\/span> e.seniority &lt; <span class=\"hljs-number\">2<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><strong>RANK()<\/strong><strong> function<\/strong><\/h3>\n\n\n\n<p>The <code>RANK()<\/code> window function returns the rank of the current row with gaps. But what do we mean by gaps?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"PostgreSQL SQL dialect and PL\/pgSQL\" data-shcb-language-slug=\"pgsql\"><span><code class=\"hljs language-pgsql shcb-wrap-lines\"><span class=\"hljs-keyword\">SELECT<\/span> *, \n  RANK() <span class=\"hljs-keyword\">OVER<\/span>(\n          <span class=\"hljs-keyword\">PARTITION<\/span> <span class=\"hljs-keyword\">BY<\/span> dep_name \n          <span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> salary <span class=\"hljs-keyword\">DESC<\/span>\n          ) rank \n<span class=\"hljs-keyword\">FROM<\/span> emp_salaries;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PostgreSQL SQL dialect and PL\/pgSQL<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">pgsql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This query returns the ranks of every employee, in descending order, according to each department. Gaps exist when there are duplicate values:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccbef93c.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccbef93c.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, <code>Ayman<\/code> and <code>Ahmed<\/code> share the same rank (<code>2<\/code>) on engineering department salaries. Rank <code>3<\/code> is a gap, and the <code>RANK()<\/code> function skips it. The next rank is <code>4<\/code> taken by <code>Ibrahim<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>DENSE_RANK()<\/strong><strong> function<\/strong><\/h3>\n\n\n\n<p><code>DENSE_RANK()<\/code> window function is similar to <code>RANK()<\/code> except that it returns the rank without gaps. If you replace <code>RANK()<\/code> with <code>DENSE_RANK()<\/code> in the previous query, you&#8217;ll find a result query like the following:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccc41699.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/09\/img_63331ccc41699.png\" alt=\"\"\/><\/a><\/figure>\n<\/div>\n\n\n<p>Now, rank <code>3<\/code> is not skipped, as in the case of <code>RANK()<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"try-it-out\"><strong>Try it out!<\/strong><\/h2>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 109.375%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=229021&#038;use_question_button\" width=\"640\" height=\"700\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>Window functions can make you productive. You will spend less time writing your queries with fewer lines of SQL.&nbsp;<\/p>\n\n\n\n<p>In this tutorial, you saw how to use them in PostgreSQL. You learned how to use <code>MAX<\/code>, <code>MIN<\/code>, <code>SUM<\/code>, <code>COUNT<\/code>, <code>AVG<\/code>, <code>ROW_NUMBER<\/code>, <code>RANK<\/code>, and <code>DENSE_RANK<\/code>. There are also other window functions that you can check out in <a href=\"https:\/\/www.postgresql.org\/docs\/12\/functions-window.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL documentation<\/a>.<\/p>\n\n\n\n<p><em>I\u2019m Ezz. I\u2019m an AWS Certified Machine Learning Specialist and a Data Platform Engineer. I help SaaS companies rank on Google. Check out my&nbsp;<a href=\"https:\/\/ezzeddinabdullah.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">website<\/a>&nbsp;for more.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Data aggregation queries can be messy to write and use. Postgres provides window functions to aggregate data neatly and reduces the need to write multiple subqueries. In this article, you&#8217;ll learn what window functions are and how to use them to aggregate your data.<\/p>\n","protected":false},"author":12,"featured_media":20130,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[67],"keyword-cluster":[],"class_list":["post-19533","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/19533","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=19533"}],"version-history":[{"count":130,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/19533\/revisions"}],"predecessor-version":[{"id":34576,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/19533\/revisions\/34576"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/20130"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=19533"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=19533"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=19533"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=19533"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=19533"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=19533"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}