PostgreSQL Window Functions with practical examples

In this article we will try to elaborate on the usage of window functions in PostgreSQL.

A window function performs a calculation across a set of table rows that are somehow related to the current row. This is comparable to the type of calculation that can be done with an aggregate function. However, window functions do not cause rows to become grouped into a single output row like non-window aggregate calls would. Instead, the rows retain their separate identities. Behind the scenes, the window function is able to access more than just the current row of the query result.

To sum this up we can say that:

  • Define a set of rows per row within the result-set.
  • Perform an operation on those rows
  • Return the result of this operation as the value of each row.

Which rows will be in the window is defined by the window definition -which is also the main syntactic distinction is that window function- defined by the OVER clause. The operation which we want to apply will be defined alongside the OVER clause.

Overall when we see an OVER clause then the function is a window function else it will simply be an aggregation or scalar function.

Syntax – F.O.P.O.

In terms of syntax for window functions, when you are getting started with them F.O.P.O. is a nice way to remember the components you need.

  • F: The function component, essentially how we want to treat our data. (AVG(), RANK() etc).
  • O: The OVER() clause, the way to invoke the functions
  • P: The PARTITION BY() clause, the way we want to create subgroups – essentially our GROUP BY for the window functions.
  • O: ORDER BY()ascending or descending order of data based on the desired column(s).

func() over (PARTITION BY column ORDER BY column)

There are several window function provided in PostgreSQL. The complete list can be found here.

Dataset

We will be using the Northwind Database for our example for which the ERD you can found below.

Northiwnd Database ERD

Examples

Let’s start working with them to get familiarized. Assume that we want to get some data from the product (name, and unit price e.g) and also we want to calculate the average unit price for the category of the particular product. Most likely we would create a CTE where we would calculate the the average unit price per category and then join it with the rest of the results. Or perhaps we could do a complex group by as well.

Utilizing window function that would be something like:

 SELECT product_id,
       product_name,
       unit_price,
       category_id,
       AVG(unit_price) OVER(PARTITION BY category_id) AS average_unit_price_in_category
FROM products
WHERE not discontinued::bool;

And this will produce the below outcome:

product_idproduct_nameunit_pricecategory_idaverage_unit_price_in_category
39Chartreuse verte18146.02777777777778
76Lakkalikööri18146.02777777777778
38Côte de Blaye263.5146.02777777777778
35Steeleye Stout18146.02777777777778
34Sasquatch Ale14146.02777777777778
75Rhönbräu Klosterbier7.75146.02777777777778
70Outback Lager15146.02777777777778
67Laughing Lumberjack Lager14146.02777777777778
43Ipoh Coffee46146.02777777777778
61Sirop d'érable28.5222.990909229625355
4Chef Anton's Cajun Seasoning22222.990909229625355
6Grandma's Boysenberry Spread25222.990909229625355
8Northwoods Cranberry Sauce40222.990909229625355
15Genen Shouyu13222.990909229625355
44Gula Malacca19.45222.990909229625355
3Aniseed Syrup10222.990909229625355
63Vegie-spread43.9222.990909229625355
65Louisiana Fiery Hot Pepper Sauce21.05222.990909229625355
66Louisiana Hot Spiced Okra17222.990909229625355
77Original Frankfurter grüne Soße13222.990909229625355
27Schoggi Schokolade43.9325.1600000674908
19Teatime Chocolate Biscuits9.2325.1600000674908
16Pavlova17.45325.1600000674908
47Zaanse koeken9.5325.1600000674908
48Chocolade12.75325.1600000674908
49Maxilaku20325.1600000674908
50Valkoinen suklaa16.25325.1600000674908
62Tarte au sucre49.3325.1600000674908
68Scottish Longbreads12.5325.1600000674908
20Sir Rodney's Marmalade81325.1600000674908
21Sir Rodney's Scones10325.1600000674908
25NuNuCa Nuß-Nougat-Creme14325.1600000674908
26Gumbär Gummibärchen31.23325.1600000674908
11Queso Cabrales21428.729999923706053
31Gorgonzola Telino12.5428.729999923706053
32Mascarpone Fabioli32428.729999923706053
33Geitost2.5428.729999923706053
71Flotemysost21.5428.729999923706053
12Queso Manchego La Pastora38428.729999923706053
72Mozzarella di Giovanni34.8428.729999923706053
59Raclette Courdavault55428.729999923706053
60Camembert Pierrot34428.729999923706053
69Gudbrandsdalsost36428.729999923706053
64Wimmers gute Semmelknödel33.25521.291666666666668
52Filo Mix7521.291666666666668
23Tunnbröd9521.291666666666668
56Gnocchi di nonna Alice38521.291666666666668
57Ravioli Angelo19.5521.291666666666668
22Gustaf's Knäckebröd21521.291666666666668
54Tourtière7.45615.724999904632568
55Pâté chinois24615.724999904632568
14Tofu23.25729.0625
74Longlife Tofu10729.0625
7Uncle Bob's Organic Dried Pears30729.0625
51Manjimup Dried Apples53729.0625
41Jack's New England Clam Chowder9.65820.682499885559082
45Rogede sild9.5820.682499885559082
46Spegesild12820.682499885559082
58Escargots de Bourgogne13.25820.682499885559082
73Röd Kaviar15820.682499885559082
10Ikura31820.682499885559082
30Nord-Ost Matjeshering25.89820.682499885559082
18Carnarvon Tigers62.5820.682499885559082
36Inlagd Sill19820.682499885559082
37Gravad lax26820.682499885559082
40Boston Crab Meat18.4820.682499885559082
13Konbu6820.682499885559082

This SELECT statement may be syntactically confusing initially so let us try to break it down:

  • As in any SELECT statement we have the columns we want to display in the output.
  • Then we have the AVG() function called with the unit price.
  • This is what we want to calculate over a subset of rows (per category essentially). The PARTITION does this for us. So as the name implies it will partition the results over category_id.
  • The aggregate function (AVG(unit_price)) will now be calculated for each partition/group.

Let us perform one more similar example. Assume now that we want to produce the count of orders for each customer, along with the count of orders for the country of the customer. Again this can happen with a CTE easily and join with the outer query to get the desired outcomes. This can also happen with window functions.

SELECT
    DISTINCT
    c.customer_id,
    c.company_name,
    c.country,
    COUNT(order_id) OVER (PARTITION BY c.customer_id) as customer_orders,
    COUNT(order_id) OVER (PARTITION BY country) as country_orders
FROM orders
JOIN customers c on orders.customer_id = c.customer_id;

The logic is similar to the above but this time we are using the COUNT() function. In the first occurrence we partition over customers and in the second over countries. Note the DISTINCT in this case. Since we do not have an actual GROUP BY() clause this will produce one row per order per customer if we do not use DISTINCT.

The outcome will be:

customer_idcompany_namecountrycustomer_orderscountry_orders
BERGSBerglunds snabbköpSweden1837
LACORLa corne d'abondanceFrance477
REGGCReggiani CaseificiItaly1228
OLDWOOld World DelicatessenUSA10122
THECRThe Cracker BoxUSA3122
ANTONAntonio Moreno TaqueríaMexico728
FURIBFuria Bacalhau e Frutos do MarPortugal813
EASTCEastern ConnectionUK856
HILAAHILARION-AbastosVenezuela1846
LEHMSLehmanns MarktstandGermany15122
SANTGSanté GourmetNorway66
ANATRAna Trujillo Emparedados y heladosMexico428
NORTSNorth/SouthUK356
BLONPBlondesddsl père et filsFrance1177
ALFKIAlfreds FutterkisteGermany6122
PRINIPrincesa Isabel VinhosPortugal513
CACTUCactus Comidas para llevarArgentina616
FAMIAFamilia ArquibaldoBrazil783
DRACDDrachenblut DelikatessenGermany6122
FRANRFrance restaurationFrance377
FOLKOFolk och fä HBSweden1937
WILMKWilman KalaFinland722
HUNGCHungry Coyote Import StoreUSA5122
BOTTMBottom-Dollar MarketsCanada1430
MAGAAMagazzini Alimentari RiunitiItaly1028
LAMAILa maison d'AsieFrance1477
BOLIDBólido Comidas preparadasSpain323
WOLZAWolski ZajazdPoland77
CHOPSChop-suey ChineseSwitzerland818
SAVEASave-a-lot MarketsUSA31122
KOENEKöniglich EssenGermany14122
WARTHWartian HerkkuFinland1522
LINODLINO-DelicatesesVenezuela1246
TOMSPToms SpezialitätenGermany6122
TORTUTortuga RestauranteMexico1028
AROUTAround the HornUK1356
HUNGOHungry Owl All-Night GrocersIreland1919
LETSSLet's Stop N ShopUSA4122
GALEDGalería del gastrónomoSpain523
FOLIGFolies gourmandesFrance577
MORGKMorgenstern GesundkostGermany5122
RANCHRancho grandeArgentina516
GROSRGROSELLA-RestauranteVenezuela246
PICCOPiccolo und mehrAustria1040
FRANKFrankenversandGermany15122
OCEANOcéano Atlántico Ltda.Argentina516
ERNSHErnst HandelAustria3040
WANDKDie Wandernde KuhGermany10122
QUICKQUICK-StopGermany28122
LAZYKLazy K Kountry StoreUSA2122
RICSURichter SupermarktSwitzerland1018
THEBIThe Big CheeseUSA4122
ISLATIsland TradingUK1056
SPECDSpécialités du mondeFrance477
SEVESSeven Seas ImportsUK956
WELLIWellington ImportadoraBrazil983
GODOSGodos Cocina TípicaSpain1023
BSBEVB's BeveragesUK1056
MAISDMaison DeweyBelgium719
SPLIRSplit Rail Beer & AleUSA9122
PERICPericles Comidas clásicasMexico628
VINETVins et alcools ChevalierFrance577
SIMOBSimons bistroDenmark718
VICTEVictuailles en stockFrance1077
TRADHTradição HipermercadosBrazil683
ROMEYRomero y tomilloSpain523
DUMONDu monde entierFrance477
LILASLILA-SupermercadoVenezuela1446
TRAIHTrail's Head Gourmet ProvisionersUSA3122
VAFFEVaffeljernetDenmark1118
FRANSFranchi S.p.A.Italy628
HANARHanari CarnesBrazil1483
CONSHConsolidated HoldingsUK356
BLAUSBlauer See DelikatessenGermany7122
SUPRDSuprêmes délicesBelgium1219
MEREPMère PaillardeCanada1330
BONAPBon app'France1777
QUEDEQue DelíciaBrazil983
GOURLGourmet LanchonetesBrazil983
LAUGBLaughing Bacchus Wine CellarsCanada330
CENTCCentro comercial MoctezumaMexico128
LONEPLonesome Pine RestaurantUSA8122
RICARRicardo AdocicadosBrazil1183
RATTCRattlesnake Canyon GroceryUSA18122
WHITCWhite Clover MarketsUSA14122
OTTIKOttilies KäseladenGermany10122
QUEENQueen CozinhaBrazil1383
COMMIComércio MineiroBrazil583
GREALGreat Lakes Food MarketUSA11122

RANK()

The previously examples were mostly to demonstrate the syntax and utility of window functions. Let us now move on more practical examples and functionality.
Going back to our first example (average unit price per category), assume that you want to know how each product’s price ranks against the rest of the product prices within the same category. Also let’s say that we want to assign the highest ranks to the most expensive product. We can do this utilizing the RANK() function.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       AVG(unit_price) OVER(PARTITION BY category_id) AS average_unit_price_in_category,
       RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as product_price_ranked_in_category
FROM products
WHERE not discontinued::bool;

This is the same as earlier with the addition of the latest line in selector. What we define there, is that we want to give a rank (RANK()) to each record/product of the same category (PARTITION BY category_id) based on their unit price (ORDER BY unit_price) and that the most expensive product should be ranked as first (DESC).

This is the outcome:

product_idproduct_nameunit_pricecategory_idaverage_unit_price_in_categoryproduct_price_ranked_in_category
38Côte de Blaye263.5146.027777777777781
43Ipoh Coffee46146.027777777777782
35Steeleye Stout18146.027777777777783
76Lakkalikööri18146.027777777777783
39Chartreuse verte18146.027777777777783
70Outback Lager15146.027777777777786
67Laughing Lumberjack Lager14146.027777777777787
34Sasquatch Ale14146.027777777777787
75Rhönbräu Klosterbier7.75146.027777777777789
63Vegie-spread43.9222.9909092296253551
8Northwoods Cranberry Sauce40222.9909092296253552
61Sirop d'érable28.5222.9909092296253553
6Grandma's Boysenberry Spread25222.9909092296253554
4Chef Anton's Cajun Seasoning22222.9909092296253555
65Louisiana Fiery Hot Pepper Sauce21.05222.9909092296253556
44Gula Malacca19.45222.9909092296253557
66Louisiana Hot Spiced Okra17222.9909092296253558
77Original Frankfurter grüne Soße13222.9909092296253559
15Genen Shouyu13222.9909092296253559
3Aniseed Syrup10222.99090922962535511
20Sir Rodney's Marmalade81325.16000006749081
62Tarte au sucre49.3325.16000006749082
27Schoggi Schokolade43.9325.16000006749083
26Gumbär Gummibärchen31.23325.16000006749084
49Maxilaku20325.16000006749085
16Pavlova17.45325.16000006749086
50Valkoinen suklaa16.25325.16000006749087
25NuNuCa Nuß-Nougat-Creme14325.16000006749088
48Chocolade12.75325.16000006749089
68Scottish Longbreads12.5325.160000067490810
21Sir Rodney's Scones10325.160000067490811
47Zaanse koeken9.5325.160000067490812
19Teatime Chocolate Biscuits9.2325.160000067490813
59Raclette Courdavault55428.7299999237060531
12Queso Manchego La Pastora38428.7299999237060532
69Gudbrandsdalsost36428.7299999237060533
72Mozzarella di Giovanni34.8428.7299999237060534
60Camembert Pierrot34428.7299999237060535
32Mascarpone Fabioli32428.7299999237060536
71Flotemysost21.5428.7299999237060537
11Queso Cabrales21428.7299999237060538
31Gorgonzola Telino12.5428.7299999237060539
33Geitost2.5428.72999992370605310
56Gnocchi di nonna Alice38521.2916666666666681
64Wimmers gute Semmelknödel33.25521.2916666666666682
22Gustaf's Knäckebröd21521.2916666666666683
57Ravioli Angelo19.5521.2916666666666684
23Tunnbröd9521.2916666666666685
52Filo Mix7521.2916666666666686
55Pâté chinois24615.7249999046325681
54Tourtière7.45615.7249999046325682
51Manjimup Dried Apples53729.06251
7Uncle Bob's Organic Dried Pears30729.06252
14Tofu23.25729.06253
74Longlife Tofu10729.06254
18Carnarvon Tigers62.5820.6824998855590821
10Ikura31820.6824998855590822
37Gravad lax26820.6824998855590823
30Nord-Ost Matjeshering25.89820.6824998855590824
36Inlagd Sill19820.6824998855590825
40Boston Crab Meat18.4820.6824998855590826
73Röd Kaviar15820.6824998855590827
58Escargots de Bourgogne13.25820.6824998855590828
46Spegesild12820.6824998855590829
41Jack's New England Clam Chowder9.65820.68249988555908210
45Rogede sild9.5820.68249988555908211
13Konbu6820.68249988555908212

Notice how rows with the same value in unit price within the same category have the same rank and how the order is skipped to the actual rank in the next row (When you have for example two records with rank 2 the next one will have rank 4 and so on so forth). For each category the RANK() creates rankings and also allows us to keep track of duplicates. This is extremely powerful considering also how easy this construct is.

DENSE_RANK()

In the case when we need to keep the ranking complete and not skip any ranks when we have same ranks within the partition we can use DENSE_RANK(). This is the same as RANK() except the skipping ranks part. Let ‘s check them side by side.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as product_price_ranked_in_category,
       DENSE_RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as dense_product_price_ranked_in_category
FROM products
WHERE not discontinued::bool;

Output:

product_idproduct_nameunit_pricecategory_idproduct_price_ranked_in_categorydense_product_price_ranked_in_category
38Côte de Blaye263.5111
43Ipoh Coffee46122
35Steeleye Stout18133
76Lakkalikööri18133
39Chartreuse verte18133
70Outback Lager15164
67Laughing Lumberjack Lager14175
34Sasquatch Ale14175
75Rhönbräu Klosterbier7.75196
63Vegie-spread43.9211
8Northwoods Cranberry Sauce40222
61Sirop d'érable28.5233
6Grandma's Boysenberry Spread25244
4Chef Anton's Cajun Seasoning22255
65Louisiana Fiery Hot Pepper Sauce21.05266
44Gula Malacca19.45277
66Louisiana Hot Spiced Okra17288
77Original Frankfurter grüne Soße13299
15Genen Shouyu13299
3Aniseed Syrup1021110
20Sir Rodney's Marmalade81311
62Tarte au sucre49.3322
27Schoggi Schokolade43.9333
26Gumbär Gummibärchen31.23344
49Maxilaku20355
16Pavlova17.45366
50Valkoinen suklaa16.25377
25NuNuCa Nuß-Nougat-Creme14388
48Chocolade12.75399
68Scottish Longbreads12.531010
21Sir Rodney's Scones1031111
47Zaanse koeken9.531212
19Teatime Chocolate Biscuits9.231313
59Raclette Courdavault55411
12Queso Manchego La Pastora38422
69Gudbrandsdalsost36433
72Mozzarella di Giovanni34.8444
60Camembert Pierrot34455
32Mascarpone Fabioli32466
71Flotemysost21.5477
11Queso Cabrales21488
31Gorgonzola Telino12.5499
33Geitost2.541010
56Gnocchi di nonna Alice38511
64Wimmers gute Semmelknödel33.25522
22Gustaf's Knäckebröd21533
57Ravioli Angelo19.5544
23Tunnbröd9555
52Filo Mix7566
55Pâté chinois24611
54Tourtière7.45622
51Manjimup Dried Apples53711
7Uncle Bob's Organic Dried Pears30722
14Tofu23.25733
74Longlife Tofu10744
18Carnarvon Tigers62.5811
10Ikura31822
37Gravad lax26833
30Nord-Ost Matjeshering25.89844
36Inlagd Sill19855
40Boston Crab Meat18.4866
73Röd Kaviar15877
58Escargots de Bourgogne13.25888
46Spegesild12899
41Jack's New England Clam Chowder9.6581010
45Rogede sild9.581111
13Konbu681212

By checking the last two columns we can see that the difference lies on the ranking.

ROW_NUMBER()

When we simple need a row ordering within the partition we can use ROW_NUMBER(). This will simple follow the ordering and provide a “ranking”.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as product_price_ranked_in_category,
       DENSE_RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as dense_product_price_ranked_in_category,
       ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as row_number_in_category
FROM products
WHERE not discontinued::bool;

Check the last three columns to see how RANK(), DENSE_RANK(), and ROW_NUMBER() behave over the same partitioning.

product_idproduct_nameunit_pricecategory_idproduct_price_ranked_in_categorydense_product_price_ranked_in_categoryrow_number_in_category
38Côte de Blaye263.51111
43Ipoh Coffee461222
35Steeleye Stout181333
76Lakkalikööri181334
39Chartreuse verte181335
70Outback Lager151646
67Laughing Lumberjack Lager141757
34Sasquatch Ale141758
75Rhönbräu Klosterbier7.751969
63Vegie-spread43.92111
8Northwoods Cranberry Sauce402222
61Sirop d'érable28.52333
6Grandma's Boysenberry Spread252444
4Chef Anton's Cajun Seasoning222555
65Louisiana Fiery Hot Pepper Sauce21.052666
44Gula Malacca19.452777
66Louisiana Hot Spiced Okra172888
77Original Frankfurter grüne Soße132999
15Genen Shouyu1329910
3Aniseed Syrup102111011
20Sir Rodney's Marmalade813111
62Tarte au sucre49.33222
27Schoggi Schokolade43.93333
26Gumbär Gummibärchen31.233444
49Maxilaku203555
16Pavlova17.453666
50Valkoinen suklaa16.253777
25NuNuCa Nuß-Nougat-Creme143888
48Chocolade12.753999
68Scottish Longbreads12.53101010
21Sir Rodney's Scones103111111
47Zaanse koeken9.53121212
19Teatime Chocolate Biscuits9.23131313
59Raclette Courdavault554111
12Queso Manchego La Pastora384222
69Gudbrandsdalsost364333
72Mozzarella di Giovanni34.84444
60Camembert Pierrot344555
32Mascarpone Fabioli324666
71Flotemysost21.54777
11Queso Cabrales214888
31Gorgonzola Telino12.54999
33Geitost2.54101010
56Gnocchi di nonna Alice385111
64Wimmers gute Semmelknödel33.255222
22Gustaf's Knäckebröd215333
57Ravioli Angelo19.55444
23Tunnbröd95555
52Filo Mix75666
55Pâté chinois246111
54Tourtière7.456222
51Manjimup Dried Apples537111
7Uncle Bob's Organic Dried Pears307222
14Tofu23.257333
74Longlife Tofu107444
18Carnarvon Tigers62.58111
10Ikura318222
37Gravad lax268333
30Nord-Ost Matjeshering25.898444
36Inlagd Sill198555
40Boston Crab Meat18.48666
73Röd Kaviar158777
58Escargots de Bourgogne13.258888
46Spegesild128999
41Jack's New England Clam Chowder9.658101010
45Rogede sild9.58111111
13Konbu68121212

PERCENT_RANK()

The PERCENT_RANK() function returns a percentile ranking number which ranges from zero to one and calculates the percentile ranking of rows in a result set.

Syntax is similar, let’s still use the same example with unit price and categories.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as product_price_ranked_in_category,
       PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as percent_rank
FROM products
WHERE not discontinued::bool;

This will produce the following:

product_idproduct_nameunit_pricecategory_idproduct_price_ranked_in_categorypercent_rank
38Côte de Blaye263.5110
43Ipoh Coffee46120.125
35Steeleye Stout18130.25
76Lakkalikööri18130.25
39Chartreuse verte18130.25
70Outback Lager15160.625
67Laughing Lumberjack Lager14170.75
34Sasquatch Ale14170.75
75Rhönbräu Klosterbier7.75191
63Vegie-spread43.9210
8Northwoods Cranberry Sauce40220.1
61Sirop d'érable28.5230.2
6Grandma's Boysenberry Spread25240.3
4Chef Anton's Cajun Seasoning22250.4
65Louisiana Fiery Hot Pepper Sauce21.05260.5
44Gula Malacca19.45270.6
66Louisiana Hot Spiced Okra17280.7
77Original Frankfurter grüne Soße13290.8
15Genen Shouyu13290.8
3Aniseed Syrup102111
20Sir Rodney's Marmalade81310
62Tarte au sucre49.3320.08333333333333333
27Schoggi Schokolade43.9330.16666666666666666
26Gumbär Gummibärchen31.23340.25
49Maxilaku20350.3333333333333333
16Pavlova17.45360.4166666666666667
50Valkoinen suklaa16.25370.5
25NuNuCa Nuß-Nougat-Creme14380.5833333333333334
48Chocolade12.75390.6666666666666666
68Scottish Longbreads12.53100.75
21Sir Rodney's Scones103110.8333333333333334
47Zaanse koeken9.53120.9166666666666666
19Teatime Chocolate Biscuits9.23131
59Raclette Courdavault55410
12Queso Manchego La Pastora38420.1111111111111111
69Gudbrandsdalsost36430.2222222222222222
72Mozzarella di Giovanni34.8440.3333333333333333
60Camembert Pierrot34450.4444444444444444
32Mascarpone Fabioli32460.5555555555555556
71Flotemysost21.5470.6666666666666666
11Queso Cabrales21480.7777777777777778
31Gorgonzola Telino12.5490.8888888888888888
33Geitost2.54101
56Gnocchi di nonna Alice38510
64Wimmers gute Semmelknödel33.25520.2
22Gustaf's Knäckebröd21530.4
57Ravioli Angelo19.5540.6
23Tunnbröd9550.8
52Filo Mix7561
55Pâté chinois24610
54Tourtière7.45621
51Manjimup Dried Apples53710
7Uncle Bob's Organic Dried Pears30720.3333333333333333
14Tofu23.25730.6666666666666666
74Longlife Tofu10741
18Carnarvon Tigers62.5810
10Ikura31820.09090909090909091
37Gravad lax26830.18181818181818182
30Nord-Ost Matjeshering25.89840.2727272727272727
36Inlagd Sill19850.36363636363636365
40Boston Crab Meat18.4860.45454545454545453
73Röd Kaviar15870.5454545454545454
58Escargots de Bourgogne13.25880.6363636363636364
46Spegesild12890.7272727272727273
41Jack's New England Clam Chowder9.658100.8181818181818182
45Rogede sild9.58110.9090909090909091
13Konbu68121

Let’s examine category 1 for example. What this tells us is that the item with ID=38 is cheapest than 0% of the items in this category. The item with ID=75 is cheapest than 100% of the items in this category. The items with IDS 34 and 67 are cheapest that 75% of the items in this category and so on so forth.

CUME_DIST()

CUME_DIST() is a window function which calculates the cumulative distribution of a row value within the partition. The output of this function is a number which represents the number of rows with values less than or equal to the current row’s value divided by the total number of rows.

Assume the example below where in the CTE we simple calculate the order per country, where country here is the customer’s country.

WITH country_orders (country, num_of_orders) AS (
    SELECT country,
           count(order_id)
    FROM orders
    JOIN customers c on orders.customer_id = c.customer_id
    GROUP BY country
)
SELECT country,
       num_of_orders,
       CUME_DIST() OVER (
           ORDER BY num_of_orders
           )
FROM country_orders;

Note: We are skipping PARTITION_BY here since we already have created the subgroups in CTE. We could of course do the same with PARTITION_BY and a DISTINCT without the CTE.

The output is:

countrynum_of_orderscume_dist
Norway60.047619047619047616
Poland70.09523809523809523
Portugal130.14285714285714285
Argentina160.19047619047619047
Denmark180.2857142857142857
Switzerland180.2857142857142857
Belgium190.38095238095238093
Ireland190.38095238095238093
Finland220.42857142857142855
Spain230.47619047619047616
Mexico280.5714285714285714
Italy280.5714285714285714
Canada300.6190476190476191
Sweden370.6666666666666666
Austria400.7142857142857143
Venezuela460.7619047619047619
UK560.8095238095238095
France770.8571428571428571
Brazil830.9047619047619048
Germany1221
USA1221

Norway has 6 orders and is the only country with 6 orders (1 record). The total number of rows in result set is 21. The CUME_DIST()will perform: 1 / 21 = 0.04 …
Poland has 7 orders. The countries that have equal or less orders than 7 are 2 (Norway and Poland). Thus the calculation will be : 2 / 21 = 0.09….

Same logic applies to the rest of the rows in the result set.

NTILE()

This function allows as to do some bucketing/binning in the result-set. The number of buckets is provided as argument. It will attempt to break the results/or partition of the results into a specified number of buckets, which will have approximately the same number of rows.

Let us go back to the unit price example.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       NTILE(4) OVER (PARTITION BY category_id ORDER BY unit_price DESC ) as price_group
FROM products
WHERE not discontinued::bool;

For each category we want here to create subgroups (4 in our case) which will be specified from the ordering of unit price.

product_idproduct_nameunit_pricecategory_idprice_group
38Côte de Blaye263.511
43Ipoh Coffee4611
35Steeleye Stout1811
76Lakkalikööri1812
39Chartreuse verte1812
70Outback Lager1513
67Laughing Lumberjack Lager1413
34Sasquatch Ale1414
75Rhönbräu Klosterbier7.7514
63Vegie-spread43.921
8Northwoods Cranberry Sauce4021
61Sirop d'érable28.521
6Grandma's Boysenberry Spread2522
4Chef Anton's Cajun Seasoning2222
65Louisiana Fiery Hot Pepper Sauce21.0522
44Gula Malacca19.4523
66Louisiana Hot Spiced Okra1723
77Original Frankfurter grüne Soße1323
15Genen Shouyu1324
3Aniseed Syrup1024
20Sir Rodney's Marmalade8131
62Tarte au sucre49.331
27Schoggi Schokolade43.931
26Gumbär Gummibärchen31.2331
49Maxilaku2032
16Pavlova17.4532
50Valkoinen suklaa16.2532
25NuNuCa Nuß-Nougat-Creme1433
48Chocolade12.7533
68Scottish Longbreads12.533
21Sir Rodney's Scones1034
47Zaanse koeken9.534
19Teatime Chocolate Biscuits9.234
59Raclette Courdavault5541
12Queso Manchego La Pastora3841
69Gudbrandsdalsost3641
72Mozzarella di Giovanni34.842
60Camembert Pierrot3442
32Mascarpone Fabioli3242
71Flotemysost21.543
11Queso Cabrales2143
31Gorgonzola Telino12.544
33Geitost2.544
56Gnocchi di nonna Alice3851
64Wimmers gute Semmelknödel33.2551
22Gustaf's Knäckebröd2152
57Ravioli Angelo19.552
23Tunnbröd953
52Filo Mix754
55Pâté chinois2461
54Tourtière7.4562
51Manjimup Dried Apples5371
7Uncle Bob's Organic Dried Pears3072
14Tofu23.2573
74Longlife Tofu1074
18Carnarvon Tigers62.581
10Ikura3181
37Gravad lax2681
30Nord-Ost Matjeshering25.8982
36Inlagd Sill1982
40Boston Crab Meat18.482
73Röd Kaviar1583
58Escargots de Bourgogne13.2583
46Spegesild1283
41Jack's New England Clam Chowder9.6584
45Rogede sild9.584
13Konbu684

Checking the price_group column we can see SQL attempts to make 4 groups of similar size for each category.

LAG()

Very often used in interview questions, LAG()allows us access the previous row of the current row within a working set. Simply putted we can access data from the previous row. This is extremely powerful when we want to find differences between current and previous rows.

It can accept an offset where we can define the number of previous rows from which to access data, defaults to 1 if skipped, and it can also accept a default value which is useful for the records that previous rows does not exist (first row in the working set), if skipped it will return NULL. We should be careful with the default since this will affect the calculation which we want to perform later.

Let us attempt to calculate how much time is passing on average for each customer to make an order.

Note: We are making the assumption, that the company opened at 01-01-1995 and all the customers were registered on this day. That means that for every client’s first order we should calculate from 01-01-1995 the passed days.

SELECT
    customer_id,
    order_date,
    LAG(order_date, 1 ,'1995-01-01') OVER (PARTITION BY customer_id ORDER BY order_date) AS previous_order,
    order_date - LAG(order_date, 1 ,'1995-01-01') OVER (PARTITION BY customer_id ORDER BY order_date) as difference_in_days
FROM orders
ORDER BY customer_id;

The second and third column are not needed for our example. We display them for demo purposes and to have LAG() a bit more readable.

We are getting the order_date and we deduct the previous (LAG() with 1 offset) order_date of the same customer (PARTITION_BY) based on the order of order_date. In the case we do not have a previous record we will assign 01-01-1995 as the default value. The output of the above is:

customer_idorder_dateprevious_orderdifference_in_days
ALFKI1997-08-251995-01-01967
ALFKI1997-10-031997-08-2539
ALFKI1997-10-131997-10-0310
ALFKI1998-01-151997-10-1394
ALFKI1998-03-161998-01-1560
ALFKI1998-04-091998-03-1624
ANATR1996-09-181995-01-01626
ANATR1997-08-081996-09-18324
ANATR1997-11-281997-08-08112
ANATR1998-03-041997-11-2896
ANTON1996-11-271995-01-01696
ANTON1997-04-151996-11-27139
ANTON1997-05-131997-04-1528
ANTON1997-06-191997-05-1337
ANTON1997-09-221997-06-1995
ANTON1997-09-251997-09-223
ANTON1998-01-281997-09-25125
AROUT1996-11-151995-01-01684
AROUT1996-12-161996-11-1531
AROUT1997-02-211996-12-1667
AROUT1997-06-041997-02-21103
AROUT1997-10-161997-06-04134
AROUT1997-11-141997-10-1629
AROUT1997-11-171997-11-143
AROUT1997-12-081997-11-1721
AROUT1997-12-241997-12-0816
AROUT1998-02-021997-12-2440
AROUT1998-03-031998-02-0229
AROUT1998-03-161998-03-0313
AROUT1998-04-101998-03-1625
BERGS1996-08-121995-01-01589
BERGS1996-08-141996-08-122
BERGS1996-12-161996-08-14124
BERGS1997-02-121996-12-1658
BERGS1997-02-131997-02-121
BERGS1997-05-011997-02-1377
BERGS1997-06-181997-05-0148
BERGS1997-08-111997-06-1854
BERGS1997-09-021997-08-1122
BERGS1997-09-171997-09-0215
BERGS1997-10-011997-09-1714
BERGS1997-11-071997-10-0137
BERGS1997-12-161997-11-0739
BERGS1998-01-161997-12-1631
BERGS1998-01-281998-01-1612
BERGS1998-02-031998-01-286
BERGS1998-02-061998-02-033
BERGS1998-03-041998-02-0626
BLAUS1997-04-091995-01-01829
BLAUS1997-04-171997-04-098
BLAUS1997-06-271997-04-1771
BLAUS1997-07-291997-06-2732
BLAUS1998-01-271997-07-29182
BLAUS1998-03-171998-01-2749
BLAUS1998-04-291998-03-1743
BLONP1996-07-251995-01-01571
BLONP1996-09-041996-07-2541
BLONP1996-11-221996-09-0479
BLONP1997-02-051996-11-2275
BLONP1997-02-181997-02-0513
BLONP1997-06-051997-02-18107
BLONP1997-06-121997-06-057
BLONP1997-06-301997-06-1218
BLONP1997-08-121997-06-3043
BLONP1997-09-231997-08-1242
BLONP1998-01-121997-09-23111
BOLID1996-10-101995-01-01648
BOLID1997-12-291996-10-10445
BOLID1998-03-241997-12-2985
BONAP1996-10-161995-01-01654
BONAP1996-10-291996-10-1613
BONAP1996-11-251996-10-2927
BONAP1997-03-111996-11-25106
BONAP1997-04-181997-03-1138
BONAP1997-05-021997-04-1814
BONAP1997-09-101997-05-02131
BONAP1997-10-231997-09-1043
BONAP1997-11-051997-10-2313
BONAP1997-11-061997-11-051
BONAP1997-11-261997-11-0620
BONAP1998-01-121997-11-2647
BONAP1998-02-051998-01-1224
BONAP1998-02-091998-02-054
BONAP1998-03-061998-02-0925
BONAP1998-03-111998-03-065
BONAP1998-05-061998-03-1156
BOTTM1996-12-201995-01-01719
BOTTM1997-01-101996-12-2021
BOTTM1997-01-101997-01-100
BOTTM1997-01-301997-01-1020
BOTTM1997-04-011997-01-3061
BOTTM1997-11-141997-04-01227
BOTTM1998-03-021997-11-14108
BOTTM1998-03-121998-03-0210
BOTTM1998-03-131998-03-121
BOTTM1998-03-251998-03-1312
BOTTM1998-03-271998-03-252
BOTTM1998-04-161998-03-2720
BOTTM1998-04-231998-04-167
BOTTM1998-04-241998-04-231
BSBEV1996-08-261995-01-01603
BSBEV1997-03-111996-08-26197
BSBEV1997-03-241997-03-1113
BSBEV1997-05-151997-03-2452
BSBEV1997-05-161997-05-151
BSBEV1997-06-241997-05-1639
BSBEV1997-07-151997-06-2421
BSBEV1998-03-111997-07-15239
BSBEV1998-03-131998-03-112
BSBEV1998-04-141998-03-1332
CACTU1997-04-291995-01-01849
CACTU1997-12-171997-04-29232
CACTU1998-01-071997-12-1721
CACTU1998-02-111998-01-0735
CACTU1998-03-101998-02-1127
CACTU1998-04-281998-03-1049
CENTC1996-07-181995-01-01564
CHOPS1996-07-111995-01-01557
CHOPS1996-12-031996-07-11145
CHOPS1997-04-281996-12-03146
CHOPS1997-11-061997-04-28192
CHOPS1997-11-191997-11-0613
CHOPS1998-03-201997-11-19121
CHOPS1998-04-161998-03-2027
CHOPS1998-04-221998-04-166
COMMI1996-08-271995-01-01604
COMMI1997-03-061996-08-27191
COMMI1997-04-021997-03-0627
COMMI1998-03-231997-04-02355
COMMI1998-04-221998-03-2330
CONSH1997-02-041995-01-01765
CONSH1997-03-031997-02-0427
CONSH1998-01-231997-03-03326
DRACD1996-11-261995-01-01695
DRACD1996-12-231996-11-2627
DRACD1997-12-251996-12-23367
DRACD1998-01-091997-12-2515
DRACD1998-04-201998-01-09101
DRACD1998-05-041998-04-2014
DUMON1996-09-201995-01-01628
DUMON1997-07-241996-09-20307
DUMON1997-09-261997-07-2464
DUMON1998-02-161997-09-26143
EASTC1996-11-261995-01-01695
EASTC1997-01-011996-11-2636
EASTC1997-05-091997-01-01128
EASTC1997-11-031997-05-09178
EASTC1998-03-311997-11-03148
EASTC1998-04-151998-03-3115
EASTC1998-04-241998-04-159
EASTC1998-04-281998-04-244
ERNSH1996-07-171995-01-01563
ERNSH1996-07-231996-07-176
ERNSH1996-11-111996-07-23111
ERNSH1996-11-291996-11-1118
ERNSH1996-12-131996-11-2914
ERNSH1996-12-231996-12-1310
ERNSH1997-01-021996-12-2310
ERNSH1997-01-031997-01-021
ERNSH1997-01-301997-01-0327
ERNSH1997-02-111997-01-3012
ERNSH1997-04-221997-02-1170
ERNSH1997-06-171997-04-2256
ERNSH1997-07-101997-06-1723
ERNSH1997-08-151997-07-1036
ERNSH1997-09-121997-08-1528
ERNSH1997-10-091997-09-1227
ERNSH1997-12-031997-10-0955
ERNSH1997-12-101997-12-037
ERNSH1997-12-111997-12-101
ERNSH1997-12-151997-12-114
ERNSH1997-12-241997-12-159
ERNSH1998-01-161997-12-2423
ERNSH1998-01-271998-01-1611
ERNSH1998-02-181998-01-2722
ERNSH1998-03-231998-02-1833
ERNSH1998-03-261998-03-233
ERNSH1998-04-011998-03-266
ERNSH1998-04-081998-04-017
ERNSH1998-04-131998-04-085
ERNSH1998-05-051998-04-1322
FAMIA1996-11-061995-01-01675
FAMIA1996-12-181996-11-0642
FAMIA1997-01-141996-12-1827
FAMIA1997-04-211997-01-1497
FAMIA1997-06-261997-04-2166
FAMIA1997-08-291997-06-2664
FAMIA1997-10-311997-08-2963
FOLIG1997-01-081995-01-01738
FOLIG1997-03-201997-01-0871
FOLIG1997-08-151997-03-20148
FOLIG1997-12-031997-08-15110
FOLIG1997-12-221997-12-0319
FOLKO1996-07-241995-01-01570
FOLKO1996-10-111996-07-2479
FOLKO1996-12-101996-10-1160
FOLKO1997-02-031996-12-1055
FOLKO1997-02-281997-02-0325
FOLKO1997-05-121997-02-2873
FOLKO1997-06-061997-05-1225
FOLKO1997-10-141997-06-06130
FOLKO1997-12-021997-10-1449
FOLKO1997-12-111997-12-029
FOLKO1998-01-091997-12-1129
FOLKO1998-02-101998-01-0932
FOLKO1998-02-231998-02-1013
FOLKO1998-03-171998-02-2322
FOLKO1998-03-261998-03-179
FOLKO1998-03-271998-03-261
FOLKO1998-04-011998-03-275
FOLKO1998-04-061998-04-015
FOLKO1998-04-271998-04-0621
FRANK1996-07-291995-01-01575
FRANK1996-10-241996-07-2987
FRANK1996-10-301996-10-246
FRANK1996-12-271996-10-3058
FRANK1997-03-271996-12-2790
FRANK1997-06-061997-03-2771
FRANK1997-08-071997-06-0662
FRANK1997-09-021997-08-0726
FRANK1997-09-161997-09-0214
FRANK1997-09-191997-09-163
FRANK1997-10-241997-09-1935
FRANK1997-12-231997-10-2460
FRANK1998-01-291997-12-2337
FRANK1998-03-051998-01-2935
FRANK1998-04-091998-03-0535
FRANR1997-09-171995-01-01990
FRANR1998-01-291997-09-17134
FRANR1998-03-241998-01-2954
FRANS1997-01-221995-01-01752
FRANS1997-10-201997-01-22271
FRANS1997-11-251997-10-2036
FRANS1997-12-311997-11-2536
FRANS1998-04-151997-12-31105
FRANS1998-04-301998-04-1515
FURIB1996-10-141995-01-01652
FURIB1996-11-121996-10-1429
FURIB1997-03-041996-11-12112
FURIB1997-03-311997-03-0427
FURIB1997-05-281997-03-3158
FURIB1997-07-181997-05-2851
FURIB1997-09-101997-07-1854
FURIB1998-03-191997-09-10190
GALED1996-11-281995-01-01697
GALED1997-01-271996-11-2860
GALED1997-06-131997-01-27137
GALED1998-02-131997-06-13245
GALED1998-03-051998-02-1320
GODOS1996-09-111995-01-01619
GODOS1997-05-281996-09-11259
GODOS1997-08-121997-05-2876
GODOS1998-02-051997-08-12177
GODOS1998-02-061998-02-051
GODOS1998-02-161998-02-0610
GODOS1998-02-261998-02-1610
GODOS1998-03-131998-02-2615
GODOS1998-04-081998-03-1326
GODOS1998-04-211998-04-0813
GOURL1997-01-231995-01-01753
GOURL1997-09-011997-01-23221
GOURL1997-09-291997-09-0128
GOURL1997-10-171997-09-2918
GOURL1997-11-071997-10-1721
GOURL1997-12-151997-11-0738
GOURL1997-12-221997-12-157
GOURL1998-03-181997-12-2286
GOURL1998-04-241998-03-1837
GREAL1997-05-061995-01-01856
GREAL1997-07-041997-05-0659
GREAL1997-07-311997-07-0427
GREAL1997-07-311997-07-310
GREAL1997-09-041997-07-3135
GREAL1997-09-251997-09-0421
GREAL1998-01-061997-09-25103
GREAL1998-03-091998-01-0662
GREAL1998-04-071998-03-0929
GREAL1998-04-221998-04-0715
GREAL1998-04-301998-04-228
GROSR1996-07-301995-01-01576
GROSR1997-12-181996-07-30506
HANAR1996-07-081995-01-01554
HANAR1996-07-101996-07-082
HANAR1997-05-191996-07-10313
HANAR1997-08-261997-05-1999
HANAR1997-10-021997-08-2637
HANAR1997-12-091997-10-0268
HANAR1997-12-181997-12-099
HANAR1998-02-131997-12-1857
HANAR1998-02-241998-02-1311
HANAR1998-03-031998-02-247
HANAR1998-03-041998-03-031
HANAR1998-03-271998-03-0423
HANAR1998-04-141998-03-2718
HANAR1998-04-271998-04-1413
HILAA1996-07-161995-01-01562
HILAA1996-12-261996-07-16163
HILAA1997-03-171996-12-2681
HILAA1997-03-261997-03-179
HILAA1997-03-311997-03-265
HILAA1997-04-071997-03-317
HILAA1997-05-291997-04-0752
HILAA1997-07-161997-05-2948
HILAA1997-07-291997-07-1613
HILAA1997-08-221997-07-2924
HILAA1997-10-151997-08-2254
HILAA1997-12-251997-10-1571
HILAA1998-02-021997-12-2539
HILAA1998-02-231998-02-0221
HILAA1998-03-181998-02-2323
HILAA1998-03-191998-03-181
HILAA1998-03-251998-03-196
HILAA1998-04-281998-03-2534
HUNGC1996-12-061995-01-01705
HUNGC1996-12-251996-12-0619
HUNGC1997-01-151996-12-2521
HUNGC1997-07-161997-01-15182
HUNGC1997-09-081997-07-1654
HUNGO1996-09-051995-01-01613
HUNGO1996-09-191996-09-0514
HUNGO1996-10-221996-09-1933
HUNGO1996-12-051996-10-2244
HUNGO1996-12-121996-12-057
HUNGO1997-01-291996-12-1248
HUNGO1997-04-111997-01-2972
HUNGO1997-04-241997-04-1113
HUNGO1997-06-121997-04-2449
HUNGO1997-08-271997-06-1276
HUNGO1997-09-091997-08-2713
HUNGO1997-09-301997-09-0921
HUNGO1997-10-131997-09-3013
HUNGO1997-10-211997-10-138
HUNGO1997-11-111997-10-2121
HUNGO1998-02-191997-11-11100
HUNGO1998-02-261998-02-197
HUNGO1998-03-301998-02-2632
HUNGO1998-04-301998-03-3031
ISLAT1996-09-261995-01-01634
ISLAT1996-10-011996-09-265
ISLAT1996-10-031996-10-012
ISLAT1997-03-131996-10-03161
ISLAT1997-08-051997-03-13145
ISLAT1997-09-181997-08-0544
ISLAT1997-11-201997-09-1863
ISLAT1997-12-261997-11-2036
ISLAT1998-01-131997-12-2618
ISLAT1998-03-061998-01-1352
KOENE1996-10-071995-01-01645
KOENE1996-10-091996-10-072
KOENE1997-02-251996-10-09139
KOENE1997-02-251997-02-250
KOENE1997-03-071997-02-2510
KOENE1997-04-151997-03-0739
KOENE1997-05-201997-04-1535
KOENE1997-08-131997-05-2085
KOENE1997-10-271997-08-1375
KOENE1997-12-261997-10-2760
KOENE1998-01-061997-12-2611
KOENE1998-01-231998-01-0617
KOENE1998-02-181998-01-2326
KOENE1998-04-161998-02-1857
LACOR1998-01-291995-01-011124
LACOR1998-03-051998-01-2935
LACOR1998-03-241998-03-0519
LACOR1998-03-241998-03-240
LAMAI1996-11-111995-01-01680
LAMAI1996-11-201996-11-119
LAMAI1996-12-031996-11-2013
LAMAI1997-01-141996-12-0342
LAMAI1997-01-241997-01-1410
LAMAI1997-02-211997-01-2428
LAMAI1997-04-021997-02-2140
LAMAI1997-04-091997-04-027
LAMAI1997-07-251997-04-09107
LAMAI1997-08-141997-07-2520
LAMAI1997-12-191997-08-14127
LAMAI1998-01-141997-12-1926
LAMAI1998-03-031998-01-1448
LAMAI1998-04-271998-03-0355
LAUGB1997-04-031995-01-01823
LAUGB1997-08-051997-04-03124
LAUGB1998-01-011997-08-05149
LAZYK1997-03-211995-01-01810
LAZYK1997-05-221997-03-2162
LEHMS1996-08-131995-01-01590
LEHMS1996-08-191996-08-136
LEHMS1996-10-311996-08-1973
LEHMS1997-04-041996-10-31155
LEHMS1997-04-301997-04-0426
LEHMS1997-05-121997-04-3012
LEHMS1997-05-141997-05-122
LEHMS1997-06-031997-05-1420
LEHMS1997-07-081997-06-0335
LEHMS1997-07-091997-07-081
LEHMS1997-12-101997-07-09154
LEHMS1998-01-301997-12-1051
LEHMS1998-02-171998-01-3018
LEHMS1998-03-091998-02-1720
LEHMS1998-05-051998-03-0957
LETSS1997-06-251995-01-01906
LETSS1997-10-271997-06-25124
LETSS1997-11-101997-10-2714
LETSS1998-02-121997-11-1094
LILAS1996-08-161995-01-01593
LILAS1996-09-031996-08-1618
LILAS1996-10-161996-09-0343
LILAS1996-11-191996-10-1634
LILAS1996-12-121996-11-1923
LILAS1997-02-281996-12-1278
LILAS1997-04-081997-02-2839
LILAS1997-05-211997-04-0843
LILAS1997-12-161997-05-21209
LILAS1998-01-091997-12-1624
LILAS1998-02-201998-01-0942
LILAS1998-04-031998-02-2042
LILAS1998-05-011998-04-0328
LILAS1998-05-051998-05-014
LINOD1997-01-061995-01-01736
LINOD1997-03-251997-01-0678
LINOD1997-08-201997-03-25148
LINOD1997-10-081997-08-2049
LINOD1997-11-041997-10-0827
LINOD1998-01-021997-11-0459
LINOD1998-01-191998-01-0217
LINOD1998-01-191998-01-190
LINOD1998-03-021998-01-1942
LINOD1998-03-171998-03-0215
LINOD1998-04-101998-03-1724
LINOD1998-04-211998-04-1011
LONEP1996-09-171995-01-01625
LONEP1996-09-301996-09-1713
LONEP1997-05-211996-09-30233
LONEP1997-09-091997-05-21111
LONEP1997-09-111997-09-092
LONEP1998-02-031997-09-11145
LONEP1998-02-121998-02-039
LONEP1998-04-131998-02-1260
MAGAA1996-08-071995-01-01584
MAGAA1996-09-091996-08-0733
MAGAA1997-01-031996-09-09116
MAGAA1997-03-061997-01-0362
MAGAA1997-08-181997-03-06165
MAGAA1997-11-251997-08-1899
MAGAA1997-12-181997-11-2523
MAGAA1998-01-071997-12-1820
MAGAA1998-03-101998-01-0762
MAGAA1998-03-161998-03-106
MAISD1997-05-071995-01-01857
MAISD1997-08-281997-05-07113
MAISD1997-12-011997-08-2895
MAISD1998-02-171997-12-0178
MAISD1998-02-191998-02-172
MAISD1998-03-261998-02-1935
MAISD1998-04-071998-03-2612
MEREP1996-10-171995-01-01655
MEREP1996-10-281996-10-1711
MEREP1996-12-091996-10-2842
MEREP1997-01-231996-12-0945
MEREP1997-02-071997-01-2315
MEREP1997-04-141997-02-0766
MEREP1997-06-111997-04-1458
MEREP1997-06-171997-06-116
MEREP1997-07-071997-06-1720
MEREP1997-07-211997-07-0714
MEREP1997-08-011997-07-2111
MEREP1997-08-041997-08-013
MEREP1997-10-301997-08-0487
MORGK1996-08-091995-01-01586
MORGK1997-06-201996-08-09315
MORGK1997-10-091997-06-20111
MORGK1997-12-161997-10-0968
MORGK1998-03-121997-12-1686
NORTS1997-04-241995-01-01844
NORTS1997-11-241997-04-24214
NORTS1998-04-291997-11-24156
OCEAN1997-01-091995-01-01739
OCEAN1997-05-081997-01-09119
OCEAN1998-02-201997-05-08288
OCEAN1998-03-181998-02-2026
OCEAN1998-03-301998-03-1812
OLDWO1996-09-131995-01-01621
OLDWO1996-10-251996-09-1342
OLDWO1997-02-101996-10-25108
OLDWO1997-07-091997-02-10149
OLDWO1997-09-241997-07-0977
OLDWO1997-10-161997-09-2422
OLDWO1998-01-011997-10-1677
OLDWO1998-01-271998-01-0126
OLDWO1998-03-201998-01-2752
OLDWO1998-04-201998-03-2031
OTTIK1996-07-191995-01-01565
OTTIK1997-01-071996-07-19172
OTTIK1997-04-161997-01-0799
OTTIK1997-05-301997-04-1644
OTTIK1997-06-261997-05-3027
OTTIK1997-09-261997-06-2692
OTTIK1997-12-051997-09-2670
OTTIK1998-01-151997-12-0541
OTTIK1998-04-031998-01-1578
OTTIK1998-04-141998-04-0311
PERIC1996-10-041995-01-01642
PERIC1996-11-141996-10-0441
PERIC1997-03-131996-11-14119
PERIC1997-04-101997-03-1328
PERIC1998-04-021997-04-10357
PERIC1998-05-051998-04-0233
PICCO1996-11-131995-01-01682
PICCO1996-12-241996-11-1341
PICCO1997-01-271996-12-2434
PICCO1997-03-281997-01-2760
PICCO1997-05-081997-03-2841
PICCO1997-07-111997-05-0864
PICCO1997-09-301997-07-1181
PICCO1997-11-191997-09-3050
PICCO1998-01-211997-11-1963
PICCO1998-04-271998-01-2196
PRINI1996-10-231995-01-01661
PRINI1996-12-271996-10-2365
PRINI1997-02-031996-12-2738
PRINI1997-03-171997-02-0342
PRINI1998-04-081997-03-17387
QUEDE1996-07-191995-01-01565
QUEDE1996-08-271996-07-1939
QUEDE1996-12-111996-08-27106
QUEDE1997-01-211996-12-1141
QUEDE1997-07-021997-01-21162
QUEDE1997-08-271997-07-0256
QUEDE1997-10-281997-08-2762
QUEDE1997-12-241997-10-2857
QUEDE1998-03-311997-12-2497
QUEEN1996-12-041995-01-01703
QUEEN1997-01-071996-12-0434
QUEEN1997-03-261997-01-0778
QUEEN1997-08-191997-03-26146
QUEEN1997-09-051997-08-1917
QUEEN1997-10-141997-09-0539
QUEEN1997-11-041997-10-1421
QUEEN1997-12-191997-11-0445
QUEEN1998-02-041997-12-1947
QUEEN1998-02-261998-02-0422
QUEEN1998-02-271998-02-261
QUEEN1998-03-191998-02-2720
QUEEN1998-05-041998-03-1946
QUICK1996-08-051995-01-01582
QUICK1996-08-201996-08-0515
QUICK1996-08-211996-08-201
QUICK1996-09-241996-08-2134
QUICK1996-11-041996-09-2441
QUICK1996-11-221996-11-0418
QUICK1997-01-171996-11-2256
QUICK1997-02-191997-01-1733
QUICK1997-04-231997-02-1963
QUICK1997-05-051997-04-2312
QUICK1997-05-191997-05-0514
QUICK1997-05-271997-05-198
QUICK1997-07-031997-05-2737
QUICK1997-09-051997-07-0364
QUICK1997-10-031997-09-0528
QUICK1997-10-061997-10-033
QUICK1997-10-291997-10-0623
QUICK1997-11-181997-10-2920
QUICK1997-12-041997-11-1816
QUICK1997-12-221997-12-0418
QUICK1998-01-211997-12-2230
QUICK1998-02-021998-01-2112
QUICK1998-02-101998-02-028
QUICK1998-03-101998-02-1028
QUICK1998-03-191998-03-109
QUICK1998-04-011998-03-1913
QUICK1998-04-021998-04-011
QUICK1998-04-141998-04-0212
RANCH1997-02-171995-01-01778
RANCH1997-10-241997-02-17249
RANCH1998-01-131997-10-2481
RANCH1998-02-271998-01-1345
RANCH1998-04-131998-02-2745
RATTC1996-07-221995-01-01568
RATTC1996-08-021996-07-2211
RATTC1996-08-301996-08-0228
RATTC1996-09-251996-08-3026
RATTC1996-09-271996-09-252
RATTC1996-11-051996-09-2739
RATTC1997-01-011996-11-0557
RATTC1997-03-191997-01-0177
RATTC1997-06-101997-03-1983
RATTC1997-06-161997-06-106
RATTC1997-07-141997-06-1628
RATTC1997-12-021997-07-14141
RATTC1998-01-071997-12-0236
RATTC1998-01-261998-01-0719
RATTC1998-02-161998-01-2621
RATTC1998-03-311998-02-1643
RATTC1998-04-061998-03-316
RATTC1998-05-061998-04-0630
REGGC1996-08-231995-01-01600
REGGC1997-01-281996-08-23158
REGGC1997-02-121997-01-2815
REGGC1997-06-091997-02-12117
REGGC1997-07-021997-06-0923
REGGC1997-09-031997-07-0263
REGGC1997-11-031997-09-0361
REGGC1998-01-021997-11-0360
REGGC1998-02-261998-01-0255
REGGC1998-03-111998-02-2613
REGGC1998-04-091998-03-1129
REGGC1998-04-301998-04-0921
RICAR1996-08-221995-01-01599
RICAR1996-09-061996-08-2215
RICAR1997-02-141996-09-06161
RICAR1997-03-201997-02-1434
RICAR1997-06-101997-03-2082
RICAR1997-08-061997-06-1057
RICAR1997-08-281997-08-0622
RICAR1998-01-051997-08-28130
RICAR1998-01-261998-01-0521
RICAR1998-02-091998-01-2614
RICAR1998-04-291998-02-0979
RICSU1996-07-121995-01-01558
RICSU1997-01-201996-07-12192
RICSU1997-05-141997-01-20114
RICSU1997-09-121997-05-14121
RICSU1997-11-241997-09-1273
RICSU1997-11-281997-11-244
RICSU1998-03-061997-11-2898
RICSU1998-03-161998-03-0610
RICSU1998-04-171998-03-1632
RICSU1998-05-061998-04-1719
ROMEY1996-08-141995-01-01591
ROMEY1996-08-151996-08-141
ROMEY1996-09-161996-08-1532
ROMEY1998-03-021996-09-16532
ROMEY1998-04-091998-03-0238
SANTG1996-12-181995-01-01717
SANTG1997-04-291996-12-18132
SANTG1997-08-201997-04-29113
SANTG1998-01-141997-08-20147
SANTG1998-02-261998-01-1443
SANTG1998-04-101998-02-2643
SAVEA1996-10-081995-01-01646
SAVEA1996-12-251996-10-0878
SAVEA1996-12-301996-12-255
SAVEA1997-02-101996-12-3042
SAVEA1997-02-201997-02-1010
SAVEA1997-04-181997-02-2057
SAVEA1997-06-021997-04-1845
SAVEA1997-07-181997-06-0246
SAVEA1997-07-221997-07-184
SAVEA1997-07-281997-07-226
SAVEA1997-08-111997-07-2814
SAVEA1997-09-041997-08-1124
SAVEA1997-09-231997-09-0419
SAVEA1997-10-101997-09-2317
SAVEA1997-10-211997-10-1011
SAVEA1997-10-221997-10-211
SAVEA1997-10-221997-10-220
SAVEA1997-10-291997-10-227
SAVEA1997-11-201997-10-2922
SAVEA1997-11-271997-11-207
SAVEA1998-01-051997-11-2739
SAVEA1998-01-221998-01-0517
SAVEA1998-02-111998-01-2220
SAVEA1998-02-181998-02-117
SAVEA1998-03-111998-02-1821
SAVEA1998-03-271998-03-1116
SAVEA1998-03-301998-03-273
SAVEA1998-04-061998-03-307
SAVEA1998-04-171998-04-0611
SAVEA1998-04-171998-04-170
SAVEA1998-05-011998-04-1714
SEVES1996-11-211995-01-01690
SEVES1996-12-091996-11-2118
SEVES1996-12-191996-12-0910
SEVES1997-03-121996-12-1983
SEVES1997-05-011997-03-1250
SEVES1997-05-231997-05-0122
SEVES1997-12-261997-05-23217
SEVES1997-12-301997-12-264
SEVES1998-02-041997-12-3036
SIMOB1996-10-291995-01-01667
SIMOB1997-01-161996-10-2979
SIMOB1997-06-031997-01-16138
SIMOB1997-08-221997-06-0380
SIMOB1997-09-151997-08-2224
SIMOB1997-12-291997-09-15105
SIMOB1998-05-061997-12-29128
SPECD1997-11-121995-01-011046
SPECD1998-02-251997-11-12105
SPECD1998-03-201998-02-2523
SPECD1998-04-221998-03-2033
SPLIR1996-08-011995-01-01578
SPLIR1996-10-151996-08-0175
SPLIR1996-11-081996-10-1524
SPLIR1996-12-021996-11-0824
SPLIR1996-12-171996-12-0215
SPLIR1997-01-311996-12-1745
SPLIR1997-11-271997-01-31300
SPLIR1998-01-081997-11-2742
SPLIR1998-03-251998-01-0876
SUPRD1996-07-091995-01-01555
SUPRD1996-09-101996-07-0963
SUPRD1997-02-261996-09-10169
SUPRD1997-03-041997-02-266
SUPRD1997-03-141997-03-0410
SUPRD1997-12-051997-03-14266
SUPRD1998-01-201997-12-0546
SUPRD1998-01-221998-01-202
SUPRD1998-02-121998-01-2221
SUPRD1998-03-061998-02-1222
SUPRD1998-04-201998-03-0645
SUPRD1998-04-211998-04-201
THEBI1996-09-201995-01-01628
THEBI1997-10-171996-09-20392
THEBI1997-12-301997-10-1774
THEBI1998-04-011997-12-3092
THECR1997-08-071995-01-01949
THECR1997-12-121997-08-07127
THECR1998-04-061997-12-12115
TOMSP1996-07-051995-01-01551
TOMSP1997-02-061996-07-05216
TOMSP1997-02-141997-02-068
TOMSP1997-05-261997-02-14101
TOMSP1997-07-231997-05-2658
TOMSP1998-03-231997-07-23243
TORTU1996-08-081995-01-01585
TORTU1996-08-291996-08-0821
TORTU1996-09-121996-08-2914
TORTU1996-10-021996-09-1220
TORTU1997-04-251996-10-02205
TORTU1997-06-231997-04-2559
TORTU1997-09-221997-06-2391
TORTU1998-01-201997-09-22120
TORTU1998-02-271998-01-2038
TORTU1998-05-041998-02-2766
TRADH1996-08-281995-01-01605
TRADH1997-04-041996-08-28219
TRADH1997-07-221997-04-04109
TRADH1998-01-131997-07-22175
TRADH1998-01-151998-01-132
TRADH1998-01-191998-01-154
TRAIH1997-06-191995-01-01900
TRAIH1997-06-231997-06-194
TRAIH1998-01-081997-06-23199
VAFFE1996-11-281995-01-01697
VAFFE1996-12-311996-11-2833
VAFFE1997-03-051996-12-3164
VAFFE1997-07-071997-03-05124
VAFFE1997-07-171997-07-0710
VAFFE1997-10-011997-07-1776
VAFFE1997-11-171997-10-0147
VAFFE1997-12-081997-11-1721
VAFFE1998-03-031997-12-0885
VAFFE1998-03-121998-03-039
VAFFE1998-04-021998-03-1221
VICTE1996-07-081995-01-01554
VICTE1996-10-211996-07-08105
VICTE1997-02-191996-10-21121
VICTE1997-02-271997-02-198
VICTE1997-03-181997-02-2719
VICTE1997-05-231997-03-1866
VICTE1997-12-311997-05-23222
VICTE1998-01-051997-12-315
VICTE1998-01-211998-01-0516
VICTE1998-01-231998-01-212
VINET1996-07-041995-01-01550
VINET1996-08-061996-07-0433
VINET1996-09-021996-08-0627
VINET1997-11-111996-09-02435
VINET1997-11-121997-11-111
WANDK1996-09-091995-01-01617
WANDK1996-09-231996-09-0914
WANDK1996-11-071996-09-2345
WANDK1996-11-181996-11-0711
WANDK1997-04-221996-11-18155
WANDK1997-08-141997-04-22114
WANDK1997-08-211997-08-147
WANDK1997-09-011997-08-2111
WANDK1997-09-151997-09-0114
WANDK1998-04-231997-09-15220
WARTH1996-07-261995-01-01572
WARTH1996-08-011996-07-266
WARTH1996-10-031996-08-0163
WARTH1996-10-181996-10-0315
WARTH1997-01-131996-10-1887
WARTH1997-01-161997-01-133
WARTH1997-02-051997-01-1620
WARTH1997-02-241997-02-0519
WARTH1997-05-051997-02-2470
WARTH1997-05-301997-05-0525
WARTH1997-06-301997-05-3031
WARTH1997-08-191997-06-3050
WARTH1997-11-211997-08-1994
WARTH1997-12-171997-11-2126
WARTH1998-04-151997-12-17119
WELLI1996-07-151995-01-01561
WELLI1997-01-211996-07-15190
WELLI1997-07-011997-01-21161
WELLI1997-08-251997-07-0155
WELLI1997-12-301997-08-25127
WELLI1998-01-011997-12-302
WELLI1998-02-201998-01-0150
WELLI1998-02-241998-02-204
WELLI1998-03-091998-02-2413
WHITC1996-07-311995-01-01577
WHITC1996-11-011996-07-3193
WHITC1997-03-101996-11-01129
WHITC1997-03-241997-03-1014
WHITC1997-04-111997-03-2418
WHITC1997-07-111997-04-1191
WHITC1997-10-061997-07-1187
WHITC1997-10-081997-10-062
WHITC1997-10-301997-10-0822
WHITC1997-11-131997-10-3014
WHITC1998-01-301997-11-1378
WHITC1998-02-241998-01-3025
WHITC1998-04-171998-02-2452
WHITC1998-05-011998-04-1714
WILMK1997-07-301995-01-01941
WILMK1997-09-181997-07-3050
WILMK1997-10-071997-09-1819
WILMK1998-02-061997-10-07122
WILMK1998-02-101998-02-064
WILMK1998-02-261998-02-1016
WILMK1998-04-071998-02-2640
WOLZA1996-12-051995-01-01704
WOLZA1997-07-251996-12-05232
WOLZA1997-12-231997-07-25151
WOLZA1998-02-041997-12-2343
WOLZA1998-02-251998-02-0421
WOLZA1998-04-031998-02-2537
WOLZA1998-04-231998-04-0320

From this point it is pretty straight forward to get the average day span for each customer to perform an order. We can use either some CTE either sub-queries. We will also add the days of the latest and earliest orders, and the LAG() calculation without default value to demonstrate how this may affect the output.

Sub-query:

SELECT customer_id,
       max(order_date) as latest_order,
       min(order_date) as earliest_order,
       avg(difference_in_days_with_default) as avg_difference_in_days_with_default,
       avg(difference_in_days) as avg_difference_in_days
FROM (
        SELECT
            customer_id,
            order_date,
            order_date - LAG(order_date, 1 ,'1995-01-01') OVER (PARTITION BY customer_id ORDER BY order_date) as difference_in_days_with_default,
            order_date - LAG(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) as difference_in_days
        FROM orders
        ORDER BY customer_id
     ) t
GROUP BY customer_id;

CTE:

WITH customer_order_stats (customer_id, order_date, difference_in_days_with_default, difference_in_days) AS (
    SELECT
        customer_id,
        order_date,
        order_date - LAG(order_date, 1 ,'1995-01-01') OVER (PARTITION BY customer_id ORDER BY order_date) as difference_in_days_with_default,
        order_date - LAG(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) as difference_in_days
    FROM orders
    ORDER BY customer_id
)
SELECT customer_id,
       max(order_date) as latest_order,
       min(order_date) as earliest_order,
       avg(difference_in_days_with_default) as avg_difference_in_days_with_default,
       avg(difference_in_days) as avg_difference_in_days
FROM customer_order_stats
GROUP BY customer_id;

Both of which produce:

customer_idlatest_orderearliest_orderavg_difference_in_days_with_defaultavg_difference_in_days
ALFKI1998-04-091997-08-2519945.4
ANATR1998-03-041996-09-18289.5177.3333333333333333
ANTON1998-01-281996-11-27160.428571428571428671.1666666666666667
AROUT1998-04-101996-11-1591.923076923076923142.5833333333333333
BERGS1998-03-041996-08-1264.333333333333333333.4705882352941176
BLAUS1998-04-291997-04-09173.428571428571428664.1666666666666667
BLONP1998-01-121996-07-25100.636363636363636453.6
BOLID1998-03-241996-10-10392.6666666666666667265
BONAP1998-05-061996-10-1671.823529411764705935.4375
BOTTM1998-04-241996-12-2086.357142857142857137.6923076923076923
BSBEV1998-04-141996-08-26119.966.2222222222222222
CACTU1998-04-281997-04-29202.166666666666666772.8
CENTC1996-07-181996-07-18564
CHOPS1998-04-221996-07-11150.87592.8571428571428571
COMMI1998-04-221996-08-27241.4150.75
CONSH1998-01-231997-02-04372.6666666666666667176.5
DRACD1998-05-041996-11-26203.1666666666666667104.8
DUMON1998-02-161996-09-20285.5171.3333333333333333
EASTC1998-04-281996-11-26151.62574
ERNSH1998-05-051996-07-1740.666666666666666722.6551724137931034
FAMIA1997-10-311996-11-06147.714285714285714359.8333333333333333
FOLIG1997-12-221997-01-08217.287
FOLKO1998-04-271996-07-2463.789473684210526335.6666666666666667
FRANK1998-04-091996-07-2979.644.2142857142857143
FRANR1998-03-241997-09-17392.666666666666666794
FRANS1998-04-301997-01-22202.592.6
FURIB1998-03-191996-10-14146.62574.4285714285714286
GALED1998-03-051996-11-28231.8115.5
GODOS1998-04-211996-09-11120.665.2222222222222222
GOURL1998-04-241997-01-23134.333333333333333357
GREAL1998-04-301997-05-06110.454545454545454535.9
GROSR1997-12-181996-07-30541506
HANAR1998-04-271996-07-0886.571428571428571450.6153846153846154
HILAA1998-04-281996-07-1667.388888888888888938.2941176470588235
HUNGC1997-09-081996-12-06196.269
HUNGO1998-04-301996-09-0563.947368421052631633.4444444444444444
ISLAT1998-03-061996-09-2611658.4444444444444444
KOENE1998-04-161996-10-0785.785714285714285742.7692307692307692
LACOR1998-03-241998-01-29294.518
LAMAI1998-04-271996-11-1186.571428571428571440.9230769230769231
LAUGB1998-01-011997-04-03365.3333333333333333136.5
LAZYK1997-05-221997-03-2143662
LEHMS1998-05-051996-08-1381.333333333333333345
LETSS1998-02-121997-06-25284.577.3333333333333333
LILAS1998-05-051996-08-1687.142857142857142948.2307692307692308
LINOD1998-04-211997-01-06100.542.7272727272727273
LONEP1998-04-131996-09-17149.7581.8571428571428571
MAGAA1998-03-161996-08-0711765.1111111111111111
MAISD1998-04-071997-05-07170.285714285714285755.8333333333333333
MEREP1997-10-301996-10-1779.461538461538461531.5
MORGK1998-03-121996-08-09233.2145
NORTS1998-04-291997-04-24404.6666666666666667185
OCEAN1998-03-301997-01-09236.8111.25
OLDWO1998-04-201996-09-13120.564.8888888888888889
OTTIK1998-04-141996-07-19119.970.4444444444444444
PERIC1998-05-051996-10-04203.3333333333333333115.6
PICCO1998-04-271996-11-13121.258.8888888888888889
PRINI1998-04-081996-10-23238.6133
QUEDE1998-03-311996-07-19131.666666666666666777.5
QUEEN1998-05-041996-12-0493.769230769230769243
QUICK1998-04-141996-08-0542.821428571428571422.8518518518518519
RANCH1998-04-131997-02-17239.6105
RATTC1998-05-061996-07-2267.833333333333333338.4117647058823529
REGGC1998-04-301996-08-23101.2555.9090909090909091
RICAR1998-04-291996-08-22110.363636363636363661.5
RICSU1998-05-061996-07-12122.173.6666666666666667
ROMEY1998-04-091996-08-14238.8150.75
SANTG1998-04-101996-12-18199.166666666666666795.6
SAVEA1998-05-011996-10-0839.225806451612903219
SEVES1998-02-041996-11-21125.555555555555555655
SIMOB1998-05-061996-10-29174.428571428571428692.3333333333333333
SPECD1998-04-221997-11-12301.7553.6666666666666667
SPLIR1998-03-251996-08-0113175.125
SUPRD1998-04-211996-07-09100.559.1818181818181818
THEBI1998-04-011996-09-20296.5186
THECR1998-04-061997-08-07397121
TOMSP1998-03-231996-07-05196.1666666666666667125.2
TORTU1998-05-041996-08-08121.970.4444444444444444
TRADH1998-01-191996-08-28185.6666666666666667101.8
TRAIH1998-01-081997-06-19367.6666666666666667101.5
VAFFE1998-04-021996-11-28107.909090909090909149
VICTE1998-01-231996-07-08111.862.6666666666666667
VINET1997-11-121996-07-04209.2124
WANDK1998-04-231996-09-09120.865.6666666666666667
WARTH1998-04-151996-07-268044.8571428571428571
WELLI1998-03-091996-07-15129.222222222222222275.25
WHITC1998-05-011996-07-3186.857142857142857149.1538461538461538
WILMK1998-04-071997-07-30170.285714285714285741.8333333333333333
WOLZA1998-04-231996-12-05172.571428571428571484

FIRST_VALUE(), LAST_VALUE(), AND nth_VALUE

As the name implies those function will return the first, last or the n value (based on order) within the working set.

Those functions can also accept a frame clause. This defines the subset of the partition where we want to do the calculations. Typically we would want to use the whole partition and this will look like this:

RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

This means that we want to work with the whole working set. By default if we do not add a this range each function will return the result related to the current record. The following demonstrates both cases to make it easier for understanding .

WITH total_sales_per_product (product_id, unit_total_sales) AS (
    SELECT
        p.product_id,
        SUM(quantity) as sales
    FROM order_details
    JOIN products p on order_details.product_id = p.product_id
    GROUP BY p.product_id
)
SELECT
    p.product_name,
    category_name,
    unit_total_sales,
    FIRST_VALUE(product_name) OVER (
        PARTITION BY category_name ORDER BY unit_total_sales
        RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
        ) as lowest_sales_per_category,
    NTH_VALUE(product_name, 2)      OVER (
        PARTITION BY category_name ORDER BY unit_total_sales
        RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as second_lowest_sales_per_category,
    LAST_VALUE(product_name) OVER (
        PARTITION BY category_name ORDER BY unit_total_sales
        RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as highest_sales_per_category,
    LAST_VALUE(product_name) OVER (
        PARTITION BY category_name ORDER BY unit_total_sales) as highest_sales_per_category_unbound_no_frame
FROM products p
JOIN total_sales_per_product ON total_sales_per_product.product_id = p.product_id
JOIN categories c on p.category_id = c.category_id;

Here we are calculating the total units sold for each product. Then we are partitioning per category and we want to see the products with the lowest sales, the second lowest sales, and the highest sales for the particular category. Notice how the two last columns with LAST_VALUE() behave with and without the frame definition.

product_namecategory_nameunit_total_saleslowest_sales_per_categorysecond_lowest_sales_per_categoryhighest_sales_per_categoryhighest_sales_per_category_unbound_no_frame
Laughing Lumberjack LagerBeverages184Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierLaughing Lumberjack Lager
Sasquatch AleBeverages506Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierSasquatch Ale
Ipoh CoffeeBeverages580Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierIpoh Coffee
Côte de BlayeBeverages623Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierCôte de Blaye
Chartreuse verteBeverages793Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierChartreuse verte
Outback LagerBeverages817Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierOutback Lager
ChaiBeverages828Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierChai
Steeleye StoutBeverages883Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierSteeleye Stout
LakkalikööriBeverages981Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierLakkalikööri
ChangBeverages1057Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierChang
Guaraná FantásticaBeverages1125Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierGuaraná Fantástica
Rhönbräu KlosterbierBeverages1155Laughing Lumberjack LagerSasquatch AleRhönbräu KlosterbierRhönbräu Klosterbier
Genen ShouyuCondiments122Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeGenen Shouyu
Louisiana Hot Spiced OkraCondiments239Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeLouisiana Hot Spiced Okra
Chef Anton's Gumbo MixCondiments298Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeChef Anton's Gumbo Mix
Grandma's Boysenberry SpreadCondiments301Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeGrandma's Boysenberry Spread
Aniseed SyrupCondiments328Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeAniseed Syrup
Northwoods Cranberry SauceCondiments372Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeNorthwoods Cranberry Sauce
Vegie-spreadCondiments445Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeVegie-spread
Chef Anton's Cajun SeasoningCondiments453Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeChef Anton's Cajun Seasoning
Gula MalaccaCondiments601Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeGula Malacca
Sirop d'érableCondiments603Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeSirop d'érable
Louisiana Fiery Hot Pepper SauceCondiments745Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeLouisiana Fiery Hot Pepper Sauce
Original Frankfurter grüne SoßeCondiments791Genen ShouyuLouisiana Hot Spiced OkraOriginal Frankfurter grüne SoßeOriginal Frankfurter grüne Soße
ChocoladeConfections138ChocoladeValkoinen suklaaPavlovaChocolade
Valkoinen suklaaConfections235ChocoladeValkoinen suklaaPavlovaValkoinen suklaa
Sir Rodney's MarmaladeConfections313ChocoladeValkoinen suklaaPavlovaSir Rodney's Marmalade
NuNuCa Nuß-Nougat-CremeConfections318ChocoladeValkoinen suklaaPavlovaNuNuCa Nuß-Nougat-Creme
Schoggi SchokoladeConfections365ChocoladeValkoinen suklaaPavlovaSchoggi Schokolade
Zaanse koekenConfections485ChocoladeValkoinen suklaaPavlovaZaanse koeken
MaxilakuConfections520ChocoladeValkoinen suklaaPavlovaMaxilaku
Teatime Chocolate BiscuitsConfections723ChocoladeValkoinen suklaaPavlovaTeatime Chocolate Biscuits
Gumbär GummibärchenConfections753ChocoladeValkoinen suklaaPavlovaGumbär Gummibärchen
Scottish LongbreadsConfections799ChocoladeValkoinen suklaaPavlovaScottish Longbreads
Sir Rodney's SconesConfections1016ChocoladeValkoinen suklaaPavlovaSir Rodney's Scones
Tarte au sucreConfections1083ChocoladeValkoinen suklaaPavlovaTarte au sucre
PavlovaConfections1158ChocoladeValkoinen suklaaPavlovaPavlova
Mascarpone FabioliDairy Products297Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotMascarpone Fabioli
Queso Manchego La PastoraDairy Products344Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotQueso Manchego La Pastora
Queso CabralesDairy Products706Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotQueso Cabrales
GudbrandsdalsostDairy Products714Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotGudbrandsdalsost
GeitostDairy Products755Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotGeitost
Mozzarella di GiovanniDairy Products806Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotMozzarella di Giovanni
FlotemysostDairy Products1057Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotFlotemysost
Gorgonzola TelinoDairy Products1397Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotGorgonzola Telino
Raclette CourdavaultDairy Products1496Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotRaclette Courdavault
Camembert PierrotDairy Products1577Mascarpone FabioliQueso Manchego La PastoraCamembert PierrotCamembert Pierrot
Gustaf's KnäckebrödGrains/Cereals348Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceGustaf's Knäckebröd
Ravioli AngeloGrains/Cereals434Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceRavioli Angelo
Filo MixGrains/Cereals500Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceFilo Mix
TunnbrödGrains/Cereals580Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceTunnbröd
Singaporean Hokkien Fried MeeGrains/Cereals697Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceSingaporean Hokkien Fried Mee
Wimmers gute SemmelknödelGrains/Cereals740Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceWimmers gute Semmelknödel
Gnocchi di nonna AliceGrains/Cereals1263Gustaf's KnäckebrödRavioli AngeloGnocchi di nonna AliceGnocchi di nonna Alice
Mishi Kobe NikuMeat/Poultry95Mishi Kobe NikuPerth PastiesAlice MuttonMishi Kobe Niku
Perth PastiesMeat/Poultry722Mishi Kobe NikuPerth PastiesAlice MuttonPerth Pasties
Thüringer RostbratwurstMeat/Poultry746Mishi Kobe NikuPerth PastiesAlice MuttonThüringer Rostbratwurst
TourtièreMeat/Poultry755Mishi Kobe NikuPerth PastiesAlice MuttonTourtière
Pâté chinoisMeat/Poultry903Mishi Kobe NikuPerth PastiesAlice MuttonPâté chinois
Alice MuttonMeat/Poultry978Mishi Kobe NikuPerth PastiesAlice MuttonAlice Mutton
Longlife TofuProduce297Longlife TofuTofuManjimup Dried ApplesLonglife Tofu
TofuProduce404Longlife TofuTofuManjimup Dried ApplesTofu
Rössle SauerkrautProduce640Longlife TofuTofuManjimup Dried ApplesRössle Sauerkraut
Uncle Bob's Organic Dried PearsProduce763Longlife TofuTofuManjimup Dried ApplesUncle Bob's Organic Dried Pears
Manjimup Dried ApplesProduce886Longlife TofuTofuManjimup Dried ApplesManjimup Dried Apples
Gravad laxSeafood125Gravad laxRöd KaviarBoston Crab MeatGravad lax
Röd KaviarSeafood293Gravad laxRöd KaviarBoston Crab MeatRöd Kaviar
Rogede sildSeafood508Gravad laxRöd KaviarBoston Crab MeatRogede sild
Escargots de BourgogneSeafood534Gravad laxRöd KaviarBoston Crab MeatEscargots de Bourgogne
Carnarvon TigersSeafood539Gravad laxRöd KaviarBoston Crab MeatCarnarvon Tigers
SpegesildSeafood548Gravad laxRöd KaviarBoston Crab MeatSpegesild
Nord-Ost MatjesheringSeafood612Gravad laxRöd KaviarBoston Crab MeatNord-Ost Matjeshering
IkuraSeafood742Gravad laxRöd KaviarBoston Crab MeatIkura
Inlagd SillSeafood805Gravad laxRöd KaviarBoston Crab MeatInlagd Sill
KonbuSeafood891Gravad laxRöd KaviarBoston Crab MeatKonbu
Jack's New England Clam ChowderSeafood981Gravad laxRöd KaviarBoston Crab MeatJack's New England Clam Chowder
Boston Crab MeatSeafood1103Gravad laxRöd KaviarBoston Crab MeatBoston Crab Meat

Rolling Averages

Now that we discussed a bit about frames, it is a good opportunity to see how we can implement rolling averages with window function syntax in PostgreSQL.

We can use the frames for this. A frame is the subset of the current partition. To define a frame, we can use:

  • RANGE (example above): the offset of the current row and frame rows are row values.
  • ROWS (number of rows to include): the offsets of the current row and frame rows are row numbers.

For every frame we need to define a START and an END.

  • START:
    • N PRECEDING-> the frame starts at nth rows before the current row.
    • UNBOUND PRECEDING -> the frame starts at the first row of the partition.
    • CURRENT ROW -> the row that is being evaluated
  • END:
    • CURRENT ROW -> the row that is being evaluated
    • UNBOUND FOLLOWING-> the frame ends at the final row in the partition.
    • N FOLLOWING-> the frame ends at the nth row after the current row.

This may be confusing in the beginning but with some practice it will become easier. SQL being an expressive language helps as well. Check the example with LAST_VALUE for example, where we essentially specify a BETWEEN.

Let us see a simple example with a rolling average for the current row and 3 rows preceding the current row.

SELECT product_id,
       product_name,
       unit_price,
       category_id,
       AVG(unit_price) OVER(PARTITION BY category_id) AS average_unit_price_in_category,
       AVG(unit_price) OVER(PARTITION BY category_id ROWS 3 PRECEDING) AS avg_3_rows_preceding
FROM products
WHERE not discontinued::bool;

Check the values in the output:

product_nameunit_pricecategory_idaverage_unit_price_in_categoryavg_3_rows_preceding
Rhönbräu Klosterbier7.75146.027777777777787.75
Sasquatch Ale14146.0277777777777810.875
Laughing Lumberjack Lager14146.0277777777777811.916666666666666
Outback Lager15146.0277777777777812.6875
Steeleye Stout18146.0277777777777815.25
Lakkalikööri18146.0277777777777816.25
Chartreuse verte18146.0277777777777817.25
Ipoh Coffee46146.0277777777777825
Côte de Blaye263.5146.0277777777777886.375
Aniseed Syrup10222.99090922962535510
Original Frankfurter grüne Soße13222.99090922962535511.5
Genen Shouyu13222.99090922962535512
Louisiana Hot Spiced Okra17222.99090922962535513.25
Gula Malacca19.45222.99090922962535515.612500190734863
Louisiana Fiery Hot Pepper Sauce21.05222.99090922962535517.625
Chef Anton's Cajun Seasoning22222.99090922962535519.875
Grandma's Boysenberry Spread25222.99090922962535521.875
Sirop d'érable28.5222.99090922962535524.137499809265137
Northwoods Cranberry Sauce40222.99090922962535528.875
Vegie-spread43.9222.99090922962535534.35000038146973
Teatime Chocolate Biscuits9.2325.16000006749089.199999809265137
Zaanse koeken9.5325.16000006749089.349999904632568
Sir Rodney's Scones10325.16000006749089.566666603088379
Scottish Longbreads12.5325.160000067490810.299999952316284
Chocolade12.75325.160000067490811.1875
NuNuCa Nuß-Nougat-Creme14325.160000067490812.3125
Valkoinen suklaa16.25325.160000067490813.875
Pavlova17.45325.160000067490815.112500190734863
Maxilaku20325.160000067490816.925000190734863
Gumbär Gummibärchen31.23325.160000067490821.232500076293945
Schoggi Schokolade43.9325.160000067490828.145000457763672
Tarte au sucre49.3325.160000067490836.107500076293945
Sir Rodney's Marmalade81325.160000067490851.357500076293945
Geitost2.5428.7299999237060532.5
Gorgonzola Telino12.5428.7299999237060537.5
Queso Cabrales21428.72999992370605312
Flotemysost21.5428.72999992370605314.375
Mascarpone Fabioli32428.72999992370605321.75
Camembert Pierrot34428.72999992370605327.125
Mozzarella di Giovanni34.8428.72999992370605330.574999809265137
Gudbrandsdalsost36428.72999992370605334.19999980926514
Queso Manchego La Pastora38428.72999992370605335.69999980926514
Raclette Courdavault55428.72999992370605340.94999980926514
Filo Mix7521.2916666666666687
Tunnbröd9521.2916666666666688
Ravioli Angelo19.5521.29166666666666811.833333333333334
Gustaf's Knäckebröd21521.29166666666666814.125
Wimmers gute Semmelknödel33.25521.29166666666666820.6875
Gnocchi di nonna Alice38521.29166666666666827.9375
Tourtière7.45615.7249999046325687.449999809265137
Pâté chinois24615.72499990463256815.724999904632568
Longlife Tofu10729.062510
Tofu23.25729.062516.625
Uncle Bob's Organic Dried Pears30729.062521.083333333333332
Manjimup Dried Apples53729.062529.0625
Konbu6820.6824998855590826
Rogede sild9.5820.6824998855590827.75
Jack's New England Clam Chowder9.65820.6824998855590828.383333206176758
Spegesild12820.6824998855590829.287499904632568
Escargots de Bourgogne13.25820.68249988555908211.099999904632568
Röd Kaviar15820.68249988555908212.474999904632568
Boston Crab Meat18.4820.68249988555908214.662499904632568
Inlagd Sill19820.68249988555908216.41249990463257
Nord-Ost Matjeshering25.89820.68249988555908219.572499752044678
Gravad lax26820.68249988555908222.322499752044678
Ikura31820.68249988555908225.47249984741211
Carnarvon Tigers62.5820.68249988555908236.34749984741211

For each row we calculate the general average and the rolling average of the current row based on current row’s price and the prices of the three previous rows.


That would be all. We covered all the available window functions in PostgreSQL and we discussed introductory about create sub groups within the same partition with frames. As we saw they are very powerful constructs that occasionally can save us a ton of extra work to reach the desired outcome.

Window functions is possibly one of the most advanced topics when studying SQL and getting to understand them will add an extremely useful tool under your belt.