1. import datetime
    
  2. from decimal import Decimal
    
  3. from unittest import mock
    
  4. 
    
  5. from django.core.exceptions import FieldError
    
  6. from django.db import NotSupportedError, connection
    
  7. from django.db.models import (
    
  8.     Avg,
    
  9.     BooleanField,
    
  10.     Case,
    
  11.     F,
    
  12.     Func,
    
  13.     IntegerField,
    
  14.     Max,
    
  15.     Min,
    
  16.     OuterRef,
    
  17.     Q,
    
  18.     RowRange,
    
  19.     Subquery,
    
  20.     Sum,
    
  21.     Value,
    
  22.     ValueRange,
    
  23.     When,
    
  24.     Window,
    
  25.     WindowFrame,
    
  26. )
    
  27. from django.db.models.fields.json import KeyTextTransform, KeyTransform
    
  28. from django.db.models.functions import (
    
  29.     Cast,
    
  30.     CumeDist,
    
  31.     DenseRank,
    
  32.     ExtractYear,
    
  33.     FirstValue,
    
  34.     Lag,
    
  35.     LastValue,
    
  36.     Lead,
    
  37.     NthValue,
    
  38.     Ntile,
    
  39.     PercentRank,
    
  40.     Rank,
    
  41.     RowNumber,
    
  42.     Upper,
    
  43. )
    
  44. from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
    
  45. 
    
  46. from .models import Detail, Employee
    
  47. 
    
  48. 
    
  49. @skipUnlessDBFeature("supports_over_clause")
    
  50. class WindowFunctionTests(TestCase):
    
  51.     @classmethod
    
  52.     def setUpTestData(cls):
    
  53.         Employee.objects.bulk_create(
    
  54.             [
    
  55.                 Employee(
    
  56.                     name=e[0],
    
  57.                     salary=e[1],
    
  58.                     department=e[2],
    
  59.                     hire_date=e[3],
    
  60.                     age=e[4],
    
  61.                     bonus=Decimal(e[1]) / 400,
    
  62.                 )
    
  63.                 for e in [
    
  64.                     ("Jones", 45000, "Accounting", datetime.datetime(2005, 11, 1), 20),
    
  65.                     (
    
  66.                         "Williams",
    
  67.                         37000,
    
  68.                         "Accounting",
    
  69.                         datetime.datetime(2009, 6, 1),
    
  70.                         20,
    
  71.                     ),
    
  72.                     ("Jenson", 45000, "Accounting", datetime.datetime(2008, 4, 1), 20),
    
  73.                     ("Adams", 50000, "Accounting", datetime.datetime(2013, 7, 1), 50),
    
  74.                     ("Smith", 55000, "Sales", datetime.datetime(2007, 6, 1), 30),
    
  75.                     ("Brown", 53000, "Sales", datetime.datetime(2009, 9, 1), 30),
    
  76.                     ("Johnson", 40000, "Marketing", datetime.datetime(2012, 3, 1), 30),
    
  77.                     ("Smith", 38000, "Marketing", datetime.datetime(2009, 10, 1), 20),
    
  78.                     ("Wilkinson", 60000, "IT", datetime.datetime(2011, 3, 1), 40),
    
  79.                     ("Moore", 34000, "IT", datetime.datetime(2013, 8, 1), 40),
    
  80.                     ("Miller", 100000, "Management", datetime.datetime(2005, 6, 1), 40),
    
  81.                     ("Johnson", 80000, "Management", datetime.datetime(2005, 7, 1), 50),
    
  82.                 ]
    
  83.             ]
    
  84.         )
    
  85. 
    
  86.     def test_dense_rank(self):
    
  87.         tests = [
    
  88.             ExtractYear(F("hire_date")).asc(),
    
  89.             F("hire_date__year").asc(),
    
  90.             "hire_date__year",
    
  91.         ]
    
  92.         for order_by in tests:
    
  93.             with self.subTest(order_by=order_by):
    
  94.                 qs = Employee.objects.annotate(
    
  95.                     rank=Window(expression=DenseRank(), order_by=order_by),
    
  96.                 )
    
  97.                 self.assertQuerysetEqual(
    
  98.                     qs,
    
  99.                     [
    
  100.                         ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 1),
    
  101.                         ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 1),
    
  102.                         ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 1),
    
  103.                         ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 2),
    
  104.                         ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 3),
    
  105.                         ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 4),
    
  106.                         ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 4),
    
  107.                         ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 4),
    
  108.                         ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 5),
    
  109.                         ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 6),
    
  110.                         ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 7),
    
  111.                         ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 7),
    
  112.                     ],
    
  113.                     lambda entry: (
    
  114.                         entry.name,
    
  115.                         entry.salary,
    
  116.                         entry.department,
    
  117.                         entry.hire_date,
    
  118.                         entry.rank,
    
  119.                     ),
    
  120.                     ordered=False,
    
  121.                 )
    
  122. 
    
  123.     def test_department_salary(self):
    
  124.         qs = Employee.objects.annotate(
    
  125.             department_sum=Window(
    
  126.                 expression=Sum("salary"),
    
  127.                 partition_by=F("department"),
    
  128.                 order_by=[F("hire_date").asc()],
    
  129.             )
    
  130.         ).order_by("department", "department_sum")
    
  131.         self.assertQuerysetEqual(
    
  132.             qs,
    
  133.             [
    
  134.                 ("Jones", "Accounting", 45000, 45000),
    
  135.                 ("Jenson", "Accounting", 45000, 90000),
    
  136.                 ("Williams", "Accounting", 37000, 127000),
    
  137.                 ("Adams", "Accounting", 50000, 177000),
    
  138.                 ("Wilkinson", "IT", 60000, 60000),
    
  139.                 ("Moore", "IT", 34000, 94000),
    
  140.                 ("Miller", "Management", 100000, 100000),
    
  141.                 ("Johnson", "Management", 80000, 180000),
    
  142.                 ("Smith", "Marketing", 38000, 38000),
    
  143.                 ("Johnson", "Marketing", 40000, 78000),
    
  144.                 ("Smith", "Sales", 55000, 55000),
    
  145.                 ("Brown", "Sales", 53000, 108000),
    
  146.             ],
    
  147.             lambda entry: (
    
  148.                 entry.name,
    
  149.                 entry.department,
    
  150.                 entry.salary,
    
  151.                 entry.department_sum,
    
  152.             ),
    
  153.         )
    
  154. 
    
  155.     def test_rank(self):
    
  156.         """
    
  157.         Rank the employees based on the year they're were hired. Since there
    
  158.         are multiple employees hired in different years, this will contain
    
  159.         gaps.
    
  160.         """
    
  161.         qs = Employee.objects.annotate(
    
  162.             rank=Window(
    
  163.                 expression=Rank(),
    
  164.                 order_by=F("hire_date__year").asc(),
    
  165.             )
    
  166.         )
    
  167.         self.assertQuerysetEqual(
    
  168.             qs,
    
  169.             [
    
  170.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 1),
    
  171.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 1),
    
  172.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 1),
    
  173.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 4),
    
  174.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 5),
    
  175.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 6),
    
  176.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 6),
    
  177.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 6),
    
  178.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 9),
    
  179.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 10),
    
  180.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 11),
    
  181.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 11),
    
  182.             ],
    
  183.             lambda entry: (
    
  184.                 entry.name,
    
  185.                 entry.salary,
    
  186.                 entry.department,
    
  187.                 entry.hire_date,
    
  188.                 entry.rank,
    
  189.             ),
    
  190.             ordered=False,
    
  191.         )
    
  192. 
    
  193.     def test_row_number(self):
    
  194.         """
    
  195.         The row number window function computes the number based on the order
    
  196.         in which the tuples were inserted. Depending on the backend,
    
  197. 
    
  198.         Oracle requires an ordering-clause in the Window expression.
    
  199.         """
    
  200.         qs = Employee.objects.annotate(
    
  201.             row_number=Window(
    
  202.                 expression=RowNumber(),
    
  203.                 order_by=F("pk").asc(),
    
  204.             )
    
  205.         ).order_by("pk")
    
  206.         self.assertQuerysetEqual(
    
  207.             qs,
    
  208.             [
    
  209.                 ("Jones", "Accounting", 1),
    
  210.                 ("Williams", "Accounting", 2),
    
  211.                 ("Jenson", "Accounting", 3),
    
  212.                 ("Adams", "Accounting", 4),
    
  213.                 ("Smith", "Sales", 5),
    
  214.                 ("Brown", "Sales", 6),
    
  215.                 ("Johnson", "Marketing", 7),
    
  216.                 ("Smith", "Marketing", 8),
    
  217.                 ("Wilkinson", "IT", 9),
    
  218.                 ("Moore", "IT", 10),
    
  219.                 ("Miller", "Management", 11),
    
  220.                 ("Johnson", "Management", 12),
    
  221.             ],
    
  222.             lambda entry: (entry.name, entry.department, entry.row_number),
    
  223.         )
    
  224. 
    
  225.     def test_row_number_no_ordering(self):
    
  226.         """
    
  227.         The row number window function computes the number based on the order
    
  228.         in which the tuples were inserted.
    
  229.         """
    
  230.         # Add a default ordering for consistent results across databases.
    
  231.         qs = Employee.objects.annotate(
    
  232.             row_number=Window(
    
  233.                 expression=RowNumber(),
    
  234.             )
    
  235.         ).order_by("pk")
    
  236.         self.assertQuerysetEqual(
    
  237.             qs,
    
  238.             [
    
  239.                 ("Jones", "Accounting", 1),
    
  240.                 ("Williams", "Accounting", 2),
    
  241.                 ("Jenson", "Accounting", 3),
    
  242.                 ("Adams", "Accounting", 4),
    
  243.                 ("Smith", "Sales", 5),
    
  244.                 ("Brown", "Sales", 6),
    
  245.                 ("Johnson", "Marketing", 7),
    
  246.                 ("Smith", "Marketing", 8),
    
  247.                 ("Wilkinson", "IT", 9),
    
  248.                 ("Moore", "IT", 10),
    
  249.                 ("Miller", "Management", 11),
    
  250.                 ("Johnson", "Management", 12),
    
  251.             ],
    
  252.             lambda entry: (entry.name, entry.department, entry.row_number),
    
  253.         )
    
  254. 
    
  255.     def test_avg_salary_department(self):
    
  256.         qs = Employee.objects.annotate(
    
  257.             avg_salary=Window(
    
  258.                 expression=Avg("salary"),
    
  259.                 order_by=F("department").asc(),
    
  260.                 partition_by="department",
    
  261.             )
    
  262.         ).order_by("department", "-salary", "name")
    
  263.         self.assertQuerysetEqual(
    
  264.             qs,
    
  265.             [
    
  266.                 ("Adams", 50000, "Accounting", 44250.00),
    
  267.                 ("Jenson", 45000, "Accounting", 44250.00),
    
  268.                 ("Jones", 45000, "Accounting", 44250.00),
    
  269.                 ("Williams", 37000, "Accounting", 44250.00),
    
  270.                 ("Wilkinson", 60000, "IT", 47000.00),
    
  271.                 ("Moore", 34000, "IT", 47000.00),
    
  272.                 ("Miller", 100000, "Management", 90000.00),
    
  273.                 ("Johnson", 80000, "Management", 90000.00),
    
  274.                 ("Johnson", 40000, "Marketing", 39000.00),
    
  275.                 ("Smith", 38000, "Marketing", 39000.00),
    
  276.                 ("Smith", 55000, "Sales", 54000.00),
    
  277.                 ("Brown", 53000, "Sales", 54000.00),
    
  278.             ],
    
  279.             transform=lambda row: (
    
  280.                 row.name,
    
  281.                 row.salary,
    
  282.                 row.department,
    
  283.                 row.avg_salary,
    
  284.             ),
    
  285.         )
    
  286. 
    
  287.     def test_lag(self):
    
  288.         """
    
  289.         Compute the difference between an employee's salary and the next
    
  290.         highest salary in the employee's department. Return None if the
    
  291.         employee has the lowest salary.
    
  292.         """
    
  293.         qs = Employee.objects.annotate(
    
  294.             lag=Window(
    
  295.                 expression=Lag(expression="salary", offset=1),
    
  296.                 partition_by=F("department"),
    
  297.                 order_by=[F("salary").asc(), F("name").asc()],
    
  298.             )
    
  299.         ).order_by("department", F("salary").asc(), F("name").asc())
    
  300.         self.assertQuerysetEqual(
    
  301.             qs,
    
  302.             [
    
  303.                 ("Williams", 37000, "Accounting", None),
    
  304.                 ("Jenson", 45000, "Accounting", 37000),
    
  305.                 ("Jones", 45000, "Accounting", 45000),
    
  306.                 ("Adams", 50000, "Accounting", 45000),
    
  307.                 ("Moore", 34000, "IT", None),
    
  308.                 ("Wilkinson", 60000, "IT", 34000),
    
  309.                 ("Johnson", 80000, "Management", None),
    
  310.                 ("Miller", 100000, "Management", 80000),
    
  311.                 ("Smith", 38000, "Marketing", None),
    
  312.                 ("Johnson", 40000, "Marketing", 38000),
    
  313.                 ("Brown", 53000, "Sales", None),
    
  314.                 ("Smith", 55000, "Sales", 53000),
    
  315.             ],
    
  316.             transform=lambda row: (row.name, row.salary, row.department, row.lag),
    
  317.         )
    
  318. 
    
  319.     def test_lag_decimalfield(self):
    
  320.         qs = Employee.objects.annotate(
    
  321.             lag=Window(
    
  322.                 expression=Lag(expression="bonus", offset=1),
    
  323.                 partition_by=F("department"),
    
  324.                 order_by=[F("bonus").asc(), F("name").asc()],
    
  325.             )
    
  326.         ).order_by("department", F("bonus").asc(), F("name").asc())
    
  327.         self.assertQuerysetEqual(
    
  328.             qs,
    
  329.             [
    
  330.                 ("Williams", 92.5, "Accounting", None),
    
  331.                 ("Jenson", 112.5, "Accounting", 92.5),
    
  332.                 ("Jones", 112.5, "Accounting", 112.5),
    
  333.                 ("Adams", 125, "Accounting", 112.5),
    
  334.                 ("Moore", 85, "IT", None),
    
  335.                 ("Wilkinson", 150, "IT", 85),
    
  336.                 ("Johnson", 200, "Management", None),
    
  337.                 ("Miller", 250, "Management", 200),
    
  338.                 ("Smith", 95, "Marketing", None),
    
  339.                 ("Johnson", 100, "Marketing", 95),
    
  340.                 ("Brown", 132.5, "Sales", None),
    
  341.                 ("Smith", 137.5, "Sales", 132.5),
    
  342.             ],
    
  343.             transform=lambda row: (row.name, row.bonus, row.department, row.lag),
    
  344.         )
    
  345. 
    
  346.     def test_first_value(self):
    
  347.         qs = Employee.objects.annotate(
    
  348.             first_value=Window(
    
  349.                 expression=FirstValue("salary"),
    
  350.                 partition_by=F("department"),
    
  351.                 order_by=F("hire_date").asc(),
    
  352.             )
    
  353.         ).order_by("department", "hire_date")
    
  354.         self.assertQuerysetEqual(
    
  355.             qs,
    
  356.             [
    
  357.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 45000),
    
  358.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 45000),
    
  359.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 45000),
    
  360.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 45000),
    
  361.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 60000),
    
  362.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 60000),
    
  363.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 100000),
    
  364.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 100000),
    
  365.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 38000),
    
  366.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 38000),
    
  367.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 55000),
    
  368.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 55000),
    
  369.             ],
    
  370.             lambda row: (
    
  371.                 row.name,
    
  372.                 row.salary,
    
  373.                 row.department,
    
  374.                 row.hire_date,
    
  375.                 row.first_value,
    
  376.             ),
    
  377.         )
    
  378. 
    
  379.     def test_last_value(self):
    
  380.         qs = Employee.objects.annotate(
    
  381.             last_value=Window(
    
  382.                 expression=LastValue("hire_date"),
    
  383.                 partition_by=F("department"),
    
  384.                 order_by=F("hire_date").asc(),
    
  385.             )
    
  386.         )
    
  387.         self.assertQuerysetEqual(
    
  388.             qs,
    
  389.             [
    
  390.                 (
    
  391.                     "Adams",
    
  392.                     "Accounting",
    
  393.                     datetime.date(2013, 7, 1),
    
  394.                     50000,
    
  395.                     datetime.date(2013, 7, 1),
    
  396.                 ),
    
  397.                 (
    
  398.                     "Jenson",
    
  399.                     "Accounting",
    
  400.                     datetime.date(2008, 4, 1),
    
  401.                     45000,
    
  402.                     datetime.date(2008, 4, 1),
    
  403.                 ),
    
  404.                 (
    
  405.                     "Jones",
    
  406.                     "Accounting",
    
  407.                     datetime.date(2005, 11, 1),
    
  408.                     45000,
    
  409.                     datetime.date(2005, 11, 1),
    
  410.                 ),
    
  411.                 (
    
  412.                     "Williams",
    
  413.                     "Accounting",
    
  414.                     datetime.date(2009, 6, 1),
    
  415.                     37000,
    
  416.                     datetime.date(2009, 6, 1),
    
  417.                 ),
    
  418.                 (
    
  419.                     "Moore",
    
  420.                     "IT",
    
  421.                     datetime.date(2013, 8, 1),
    
  422.                     34000,
    
  423.                     datetime.date(2013, 8, 1),
    
  424.                 ),
    
  425.                 (
    
  426.                     "Wilkinson",
    
  427.                     "IT",
    
  428.                     datetime.date(2011, 3, 1),
    
  429.                     60000,
    
  430.                     datetime.date(2011, 3, 1),
    
  431.                 ),
    
  432.                 (
    
  433.                     "Miller",
    
  434.                     "Management",
    
  435.                     datetime.date(2005, 6, 1),
    
  436.                     100000,
    
  437.                     datetime.date(2005, 6, 1),
    
  438.                 ),
    
  439.                 (
    
  440.                     "Johnson",
    
  441.                     "Management",
    
  442.                     datetime.date(2005, 7, 1),
    
  443.                     80000,
    
  444.                     datetime.date(2005, 7, 1),
    
  445.                 ),
    
  446.                 (
    
  447.                     "Johnson",
    
  448.                     "Marketing",
    
  449.                     datetime.date(2012, 3, 1),
    
  450.                     40000,
    
  451.                     datetime.date(2012, 3, 1),
    
  452.                 ),
    
  453.                 (
    
  454.                     "Smith",
    
  455.                     "Marketing",
    
  456.                     datetime.date(2009, 10, 1),
    
  457.                     38000,
    
  458.                     datetime.date(2009, 10, 1),
    
  459.                 ),
    
  460.                 (
    
  461.                     "Brown",
    
  462.                     "Sales",
    
  463.                     datetime.date(2009, 9, 1),
    
  464.                     53000,
    
  465.                     datetime.date(2009, 9, 1),
    
  466.                 ),
    
  467.                 (
    
  468.                     "Smith",
    
  469.                     "Sales",
    
  470.                     datetime.date(2007, 6, 1),
    
  471.                     55000,
    
  472.                     datetime.date(2007, 6, 1),
    
  473.                 ),
    
  474.             ],
    
  475.             transform=lambda row: (
    
  476.                 row.name,
    
  477.                 row.department,
    
  478.                 row.hire_date,
    
  479.                 row.salary,
    
  480.                 row.last_value,
    
  481.             ),
    
  482.             ordered=False,
    
  483.         )
    
  484. 
    
  485.     def test_function_list_of_values(self):
    
  486.         qs = (
    
  487.             Employee.objects.annotate(
    
  488.                 lead=Window(
    
  489.                     expression=Lead(expression="salary"),
    
  490.                     order_by=[F("hire_date").asc(), F("name").desc()],
    
  491.                     partition_by="department",
    
  492.                 )
    
  493.             )
    
  494.             .values_list("name", "salary", "department", "hire_date", "lead")
    
  495.             .order_by("department", F("hire_date").asc(), F("name").desc())
    
  496.         )
    
  497.         self.assertNotIn("GROUP BY", str(qs.query))
    
  498.         self.assertSequenceEqual(
    
  499.             qs,
    
  500.             [
    
  501.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 45000),
    
  502.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 37000),
    
  503.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 50000),
    
  504.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), None),
    
  505.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 34000),
    
  506.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), None),
    
  507.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 80000),
    
  508.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), None),
    
  509.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 40000),
    
  510.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), None),
    
  511.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 53000),
    
  512.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), None),
    
  513.             ],
    
  514.         )
    
  515. 
    
  516.     def test_min_department(self):
    
  517.         """An alternative way to specify a query for FirstValue."""
    
  518.         qs = Employee.objects.annotate(
    
  519.             min_salary=Window(
    
  520.                 expression=Min("salary"),
    
  521.                 partition_by=F("department"),
    
  522.                 order_by=[F("salary").asc(), F("name").asc()],
    
  523.             )
    
  524.         ).order_by("department", "salary", "name")
    
  525.         self.assertQuerysetEqual(
    
  526.             qs,
    
  527.             [
    
  528.                 ("Williams", "Accounting", 37000, 37000),
    
  529.                 ("Jenson", "Accounting", 45000, 37000),
    
  530.                 ("Jones", "Accounting", 45000, 37000),
    
  531.                 ("Adams", "Accounting", 50000, 37000),
    
  532.                 ("Moore", "IT", 34000, 34000),
    
  533.                 ("Wilkinson", "IT", 60000, 34000),
    
  534.                 ("Johnson", "Management", 80000, 80000),
    
  535.                 ("Miller", "Management", 100000, 80000),
    
  536.                 ("Smith", "Marketing", 38000, 38000),
    
  537.                 ("Johnson", "Marketing", 40000, 38000),
    
  538.                 ("Brown", "Sales", 53000, 53000),
    
  539.                 ("Smith", "Sales", 55000, 53000),
    
  540.             ],
    
  541.             lambda row: (row.name, row.department, row.salary, row.min_salary),
    
  542.         )
    
  543. 
    
  544.     def test_max_per_year(self):
    
  545.         """
    
  546.         Find the maximum salary awarded in the same year as the
    
  547.         employee was hired, regardless of the department.
    
  548.         """
    
  549.         qs = Employee.objects.annotate(
    
  550.             max_salary_year=Window(
    
  551.                 expression=Max("salary"),
    
  552.                 order_by=ExtractYear("hire_date").asc(),
    
  553.                 partition_by=ExtractYear("hire_date"),
    
  554.             )
    
  555.         ).order_by(ExtractYear("hire_date"), "salary")
    
  556.         self.assertQuerysetEqual(
    
  557.             qs,
    
  558.             [
    
  559.                 ("Jones", "Accounting", 45000, 2005, 100000),
    
  560.                 ("Johnson", "Management", 80000, 2005, 100000),
    
  561.                 ("Miller", "Management", 100000, 2005, 100000),
    
  562.                 ("Smith", "Sales", 55000, 2007, 55000),
    
  563.                 ("Jenson", "Accounting", 45000, 2008, 45000),
    
  564.                 ("Williams", "Accounting", 37000, 2009, 53000),
    
  565.                 ("Smith", "Marketing", 38000, 2009, 53000),
    
  566.                 ("Brown", "Sales", 53000, 2009, 53000),
    
  567.                 ("Wilkinson", "IT", 60000, 2011, 60000),
    
  568.                 ("Johnson", "Marketing", 40000, 2012, 40000),
    
  569.                 ("Moore", "IT", 34000, 2013, 50000),
    
  570.                 ("Adams", "Accounting", 50000, 2013, 50000),
    
  571.             ],
    
  572.             lambda row: (
    
  573.                 row.name,
    
  574.                 row.department,
    
  575.                 row.salary,
    
  576.                 row.hire_date.year,
    
  577.                 row.max_salary_year,
    
  578.             ),
    
  579.         )
    
  580. 
    
  581.     def test_cume_dist(self):
    
  582.         """
    
  583.         Compute the cumulative distribution for the employees based on the
    
  584.         salary in increasing order. Equal to rank/total number of rows (12).
    
  585.         """
    
  586.         qs = Employee.objects.annotate(
    
  587.             cume_dist=Window(
    
  588.                 expression=CumeDist(),
    
  589.                 order_by=F("salary").asc(),
    
  590.             )
    
  591.         ).order_by("salary", "name")
    
  592.         # Round result of cume_dist because Oracle uses greater precision.
    
  593.         self.assertQuerysetEqual(
    
  594.             qs,
    
  595.             [
    
  596.                 ("Moore", "IT", 34000, 0.0833333333),
    
  597.                 ("Williams", "Accounting", 37000, 0.1666666667),
    
  598.                 ("Smith", "Marketing", 38000, 0.25),
    
  599.                 ("Johnson", "Marketing", 40000, 0.3333333333),
    
  600.                 ("Jenson", "Accounting", 45000, 0.5),
    
  601.                 ("Jones", "Accounting", 45000, 0.5),
    
  602.                 ("Adams", "Accounting", 50000, 0.5833333333),
    
  603.                 ("Brown", "Sales", 53000, 0.6666666667),
    
  604.                 ("Smith", "Sales", 55000, 0.75),
    
  605.                 ("Wilkinson", "IT", 60000, 0.8333333333),
    
  606.                 ("Johnson", "Management", 80000, 0.9166666667),
    
  607.                 ("Miller", "Management", 100000, 1),
    
  608.             ],
    
  609.             lambda row: (
    
  610.                 row.name,
    
  611.                 row.department,
    
  612.                 row.salary,
    
  613.                 round(row.cume_dist, 10),
    
  614.             ),
    
  615.         )
    
  616. 
    
  617.     def test_nthvalue(self):
    
  618.         qs = Employee.objects.annotate(
    
  619.             nth_value=Window(
    
  620.                 expression=NthValue(expression="salary", nth=2),
    
  621.                 order_by=[F("hire_date").asc(), F("name").desc()],
    
  622.                 partition_by=F("department"),
    
  623.             )
    
  624.         ).order_by("department", "hire_date", "name")
    
  625.         self.assertQuerysetEqual(
    
  626.             qs,
    
  627.             [
    
  628.                 ("Jones", "Accounting", datetime.date(2005, 11, 1), 45000, None),
    
  629.                 ("Jenson", "Accounting", datetime.date(2008, 4, 1), 45000, 45000),
    
  630.                 ("Williams", "Accounting", datetime.date(2009, 6, 1), 37000, 45000),
    
  631.                 ("Adams", "Accounting", datetime.date(2013, 7, 1), 50000, 45000),
    
  632.                 ("Wilkinson", "IT", datetime.date(2011, 3, 1), 60000, None),
    
  633.                 ("Moore", "IT", datetime.date(2013, 8, 1), 34000, 34000),
    
  634.                 ("Miller", "Management", datetime.date(2005, 6, 1), 100000, None),
    
  635.                 ("Johnson", "Management", datetime.date(2005, 7, 1), 80000, 80000),
    
  636.                 ("Smith", "Marketing", datetime.date(2009, 10, 1), 38000, None),
    
  637.                 ("Johnson", "Marketing", datetime.date(2012, 3, 1), 40000, 40000),
    
  638.                 ("Smith", "Sales", datetime.date(2007, 6, 1), 55000, None),
    
  639.                 ("Brown", "Sales", datetime.date(2009, 9, 1), 53000, 53000),
    
  640.             ],
    
  641.             lambda row: (
    
  642.                 row.name,
    
  643.                 row.department,
    
  644.                 row.hire_date,
    
  645.                 row.salary,
    
  646.                 row.nth_value,
    
  647.             ),
    
  648.         )
    
  649. 
    
  650.     def test_lead(self):
    
  651.         """
    
  652.         Determine what the next person hired in the same department makes.
    
  653.         Because the dataset is ambiguous, the name is also part of the
    
  654.         ordering clause. No default is provided, so None/NULL should be
    
  655.         returned.
    
  656.         """
    
  657.         qs = Employee.objects.annotate(
    
  658.             lead=Window(
    
  659.                 expression=Lead(expression="salary"),
    
  660.                 order_by=[F("hire_date").asc(), F("name").desc()],
    
  661.                 partition_by="department",
    
  662.             )
    
  663.         ).order_by("department", F("hire_date").asc(), F("name").desc())
    
  664.         self.assertQuerysetEqual(
    
  665.             qs,
    
  666.             [
    
  667.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 45000),
    
  668.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 37000),
    
  669.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 50000),
    
  670.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), None),
    
  671.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 34000),
    
  672.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), None),
    
  673.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 80000),
    
  674.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), None),
    
  675.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 40000),
    
  676.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), None),
    
  677.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 53000),
    
  678.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), None),
    
  679.             ],
    
  680.             transform=lambda row: (
    
  681.                 row.name,
    
  682.                 row.salary,
    
  683.                 row.department,
    
  684.                 row.hire_date,
    
  685.                 row.lead,
    
  686.             ),
    
  687.         )
    
  688. 
    
  689.     def test_lead_offset(self):
    
  690.         """
    
  691.         Determine what the person hired after someone makes. Due to
    
  692.         ambiguity, the name is also included in the ordering.
    
  693.         """
    
  694.         qs = Employee.objects.annotate(
    
  695.             lead=Window(
    
  696.                 expression=Lead("salary", offset=2),
    
  697.                 partition_by="department",
    
  698.                 order_by=F("hire_date").asc(),
    
  699.             )
    
  700.         )
    
  701.         self.assertQuerysetEqual(
    
  702.             qs,
    
  703.             [
    
  704.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 37000),
    
  705.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 50000),
    
  706.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), None),
    
  707.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), None),
    
  708.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), None),
    
  709.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), None),
    
  710.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), None),
    
  711.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), None),
    
  712.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), None),
    
  713.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), None),
    
  714.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), None),
    
  715.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), None),
    
  716.             ],
    
  717.             transform=lambda row: (
    
  718.                 row.name,
    
  719.                 row.salary,
    
  720.                 row.department,
    
  721.                 row.hire_date,
    
  722.                 row.lead,
    
  723.             ),
    
  724.             ordered=False,
    
  725.         )
    
  726. 
    
  727.     @skipUnlessDBFeature("supports_default_in_lead_lag")
    
  728.     def test_lead_default(self):
    
  729.         qs = Employee.objects.annotate(
    
  730.             lead_default=Window(
    
  731.                 expression=Lead(expression="salary", offset=5, default=60000),
    
  732.                 partition_by=F("department"),
    
  733.                 order_by=F("department").asc(),
    
  734.             )
    
  735.         )
    
  736.         self.assertEqual(
    
  737.             list(qs.values_list("lead_default", flat=True).distinct()), [60000]
    
  738.         )
    
  739. 
    
  740.     def test_ntile(self):
    
  741.         """
    
  742.         Compute the group for each of the employees across the entire company,
    
  743.         based on how high the salary is for them. There are twelve employees
    
  744.         so it divides evenly into four groups.
    
  745.         """
    
  746.         qs = Employee.objects.annotate(
    
  747.             ntile=Window(
    
  748.                 expression=Ntile(num_buckets=4),
    
  749.                 order_by="-salary",
    
  750.             )
    
  751.         ).order_by("ntile", "-salary", "name")
    
  752.         self.assertQuerysetEqual(
    
  753.             qs,
    
  754.             [
    
  755.                 ("Miller", "Management", 100000, 1),
    
  756.                 ("Johnson", "Management", 80000, 1),
    
  757.                 ("Wilkinson", "IT", 60000, 1),
    
  758.                 ("Smith", "Sales", 55000, 2),
    
  759.                 ("Brown", "Sales", 53000, 2),
    
  760.                 ("Adams", "Accounting", 50000, 2),
    
  761.                 ("Jenson", "Accounting", 45000, 3),
    
  762.                 ("Jones", "Accounting", 45000, 3),
    
  763.                 ("Johnson", "Marketing", 40000, 3),
    
  764.                 ("Smith", "Marketing", 38000, 4),
    
  765.                 ("Williams", "Accounting", 37000, 4),
    
  766.                 ("Moore", "IT", 34000, 4),
    
  767.             ],
    
  768.             lambda x: (x.name, x.department, x.salary, x.ntile),
    
  769.         )
    
  770. 
    
  771.     def test_percent_rank(self):
    
  772.         """
    
  773.         Calculate the percentage rank of the employees across the entire
    
  774.         company based on salary and name (in case of ambiguity).
    
  775.         """
    
  776.         qs = Employee.objects.annotate(
    
  777.             percent_rank=Window(
    
  778.                 expression=PercentRank(),
    
  779.                 order_by=[F("salary").asc(), F("name").asc()],
    
  780.             )
    
  781.         ).order_by("percent_rank")
    
  782.         # Round to account for precision differences among databases.
    
  783.         self.assertQuerysetEqual(
    
  784.             qs,
    
  785.             [
    
  786.                 ("Moore", "IT", 34000, 0.0),
    
  787.                 ("Williams", "Accounting", 37000, 0.0909090909),
    
  788.                 ("Smith", "Marketing", 38000, 0.1818181818),
    
  789.                 ("Johnson", "Marketing", 40000, 0.2727272727),
    
  790.                 ("Jenson", "Accounting", 45000, 0.3636363636),
    
  791.                 ("Jones", "Accounting", 45000, 0.4545454545),
    
  792.                 ("Adams", "Accounting", 50000, 0.5454545455),
    
  793.                 ("Brown", "Sales", 53000, 0.6363636364),
    
  794.                 ("Smith", "Sales", 55000, 0.7272727273),
    
  795.                 ("Wilkinson", "IT", 60000, 0.8181818182),
    
  796.                 ("Johnson", "Management", 80000, 0.9090909091),
    
  797.                 ("Miller", "Management", 100000, 1.0),
    
  798.             ],
    
  799.             transform=lambda row: (
    
  800.                 row.name,
    
  801.                 row.department,
    
  802.                 row.salary,
    
  803.                 round(row.percent_rank, 10),
    
  804.             ),
    
  805.         )
    
  806. 
    
  807.     def test_nth_returns_null(self):
    
  808.         """
    
  809.         Find the nth row of the data set. None is returned since there are
    
  810.         fewer than 20 rows in the test data.
    
  811.         """
    
  812.         qs = Employee.objects.annotate(
    
  813.             nth_value=Window(
    
  814.                 expression=NthValue("salary", nth=20), order_by=F("salary").asc()
    
  815.             )
    
  816.         )
    
  817.         self.assertEqual(
    
  818.             list(qs.values_list("nth_value", flat=True).distinct()), [None]
    
  819.         )
    
  820. 
    
  821.     def test_multiple_partitioning(self):
    
  822.         """
    
  823.         Find the maximum salary for each department for people hired in the
    
  824.         same year.
    
  825.         """
    
  826.         qs = Employee.objects.annotate(
    
  827.             max=Window(
    
  828.                 expression=Max("salary"),
    
  829.                 partition_by=[F("department"), F("hire_date__year")],
    
  830.             )
    
  831.         ).order_by("department", "hire_date", "name")
    
  832.         self.assertQuerysetEqual(
    
  833.             qs,
    
  834.             [
    
  835.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 45000),
    
  836.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 45000),
    
  837.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 37000),
    
  838.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 50000),
    
  839.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 60000),
    
  840.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 34000),
    
  841.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 100000),
    
  842.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 100000),
    
  843.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 38000),
    
  844.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 40000),
    
  845.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 55000),
    
  846.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 53000),
    
  847.             ],
    
  848.             transform=lambda row: (
    
  849.                 row.name,
    
  850.                 row.salary,
    
  851.                 row.department,
    
  852.                 row.hire_date,
    
  853.                 row.max,
    
  854.             ),
    
  855.         )
    
  856. 
    
  857.     def test_multiple_ordering(self):
    
  858.         """
    
  859.         Accumulate the salaries over the departments based on hire_date.
    
  860.         If two people were hired on the same date in the same department, the
    
  861.         ordering clause will render a different result for those people.
    
  862.         """
    
  863.         qs = Employee.objects.annotate(
    
  864.             sum=Window(
    
  865.                 expression=Sum("salary"),
    
  866.                 partition_by="department",
    
  867.                 order_by=[F("hire_date").asc(), F("name").asc()],
    
  868.             )
    
  869.         ).order_by("department", "sum")
    
  870.         self.assertQuerysetEqual(
    
  871.             qs,
    
  872.             [
    
  873.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 45000),
    
  874.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 90000),
    
  875.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 127000),
    
  876.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 177000),
    
  877.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 60000),
    
  878.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 94000),
    
  879.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 100000),
    
  880.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 180000),
    
  881.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 38000),
    
  882.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 78000),
    
  883.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 55000),
    
  884.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 108000),
    
  885.             ],
    
  886.             transform=lambda row: (
    
  887.                 row.name,
    
  888.                 row.salary,
    
  889.                 row.department,
    
  890.                 row.hire_date,
    
  891.                 row.sum,
    
  892.             ),
    
  893.         )
    
  894. 
    
  895.     def test_related_ordering_with_count(self):
    
  896.         qs = Employee.objects.annotate(
    
  897.             department_sum=Window(
    
  898.                 expression=Sum("salary"),
    
  899.                 partition_by=F("department"),
    
  900.                 order_by=["classification__code"],
    
  901.             )
    
  902.         )
    
  903.         self.assertEqual(qs.count(), 12)
    
  904. 
    
  905.     @skipUnlessDBFeature("supports_frame_range_fixed_distance")
    
  906.     def test_range_n_preceding_and_following(self):
    
  907.         qs = Employee.objects.annotate(
    
  908.             sum=Window(
    
  909.                 expression=Sum("salary"),
    
  910.                 order_by=F("salary").asc(),
    
  911.                 partition_by="department",
    
  912.                 frame=ValueRange(start=-2, end=2),
    
  913.             )
    
  914.         )
    
  915.         self.assertIn("RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING", str(qs.query))
    
  916.         self.assertQuerysetEqual(
    
  917.             qs,
    
  918.             [
    
  919.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 37000),
    
  920.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 90000),
    
  921.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 90000),
    
  922.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 50000),
    
  923.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 53000),
    
  924.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 55000),
    
  925.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 40000),
    
  926.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 38000),
    
  927.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 60000),
    
  928.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 34000),
    
  929.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 100000),
    
  930.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 80000),
    
  931.             ],
    
  932.             transform=lambda row: (
    
  933.                 row.name,
    
  934.                 row.salary,
    
  935.                 row.department,
    
  936.                 row.hire_date,
    
  937.                 row.sum,
    
  938.             ),
    
  939.             ordered=False,
    
  940.         )
    
  941. 
    
  942.     def test_range_unbound(self):
    
  943.         """A query with RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING."""
    
  944.         qs = Employee.objects.annotate(
    
  945.             sum=Window(
    
  946.                 expression=Sum("salary"),
    
  947.                 partition_by="age",
    
  948.                 order_by=[F("age").asc()],
    
  949.                 frame=ValueRange(start=None, end=None),
    
  950.             )
    
  951.         ).order_by("department", "hire_date", "name")
    
  952.         self.assertIn(
    
  953.             "RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING", str(qs.query)
    
  954.         )
    
  955.         self.assertQuerysetEqual(
    
  956.             qs,
    
  957.             [
    
  958.                 ("Jones", "Accounting", 45000, datetime.date(2005, 11, 1), 165000),
    
  959.                 ("Jenson", "Accounting", 45000, datetime.date(2008, 4, 1), 165000),
    
  960.                 ("Williams", "Accounting", 37000, datetime.date(2009, 6, 1), 165000),
    
  961.                 ("Adams", "Accounting", 50000, datetime.date(2013, 7, 1), 130000),
    
  962.                 ("Wilkinson", "IT", 60000, datetime.date(2011, 3, 1), 194000),
    
  963.                 ("Moore", "IT", 34000, datetime.date(2013, 8, 1), 194000),
    
  964.                 ("Miller", "Management", 100000, datetime.date(2005, 6, 1), 194000),
    
  965.                 ("Johnson", "Management", 80000, datetime.date(2005, 7, 1), 130000),
    
  966.                 ("Smith", "Marketing", 38000, datetime.date(2009, 10, 1), 165000),
    
  967.                 ("Johnson", "Marketing", 40000, datetime.date(2012, 3, 1), 148000),
    
  968.                 ("Smith", "Sales", 55000, datetime.date(2007, 6, 1), 148000),
    
  969.                 ("Brown", "Sales", 53000, datetime.date(2009, 9, 1), 148000),
    
  970.             ],
    
  971.             transform=lambda row: (
    
  972.                 row.name,
    
  973.                 row.department,
    
  974.                 row.salary,
    
  975.                 row.hire_date,
    
  976.                 row.sum,
    
  977.             ),
    
  978.         )
    
  979. 
    
  980.     def test_subquery_row_range_rank(self):
    
  981.         qs = Employee.objects.annotate(
    
  982.             highest_avg_salary_date=Subquery(
    
  983.                 Employee.objects.filter(
    
  984.                     department=OuterRef("department"),
    
  985.                 )
    
  986.                 .annotate(
    
  987.                     avg_salary=Window(
    
  988.                         expression=Avg("salary"),
    
  989.                         order_by=[F("hire_date").asc()],
    
  990.                         frame=RowRange(start=-1, end=1),
    
  991.                     ),
    
  992.                 )
    
  993.                 .order_by("-avg_salary", "hire_date")
    
  994.                 .values("hire_date")[:1],
    
  995.             ),
    
  996.         ).order_by("department", "name")
    
  997.         self.assertQuerysetEqual(
    
  998.             qs,
    
  999.             [
    
  1000.                 ("Adams", "Accounting", datetime.date(2005, 11, 1)),
    
  1001.                 ("Jenson", "Accounting", datetime.date(2005, 11, 1)),
    
  1002.                 ("Jones", "Accounting", datetime.date(2005, 11, 1)),
    
  1003.                 ("Williams", "Accounting", datetime.date(2005, 11, 1)),
    
  1004.                 ("Moore", "IT", datetime.date(2011, 3, 1)),
    
  1005.                 ("Wilkinson", "IT", datetime.date(2011, 3, 1)),
    
  1006.                 ("Johnson", "Management", datetime.date(2005, 6, 1)),
    
  1007.                 ("Miller", "Management", datetime.date(2005, 6, 1)),
    
  1008.                 ("Johnson", "Marketing", datetime.date(2009, 10, 1)),
    
  1009.                 ("Smith", "Marketing", datetime.date(2009, 10, 1)),
    
  1010.                 ("Brown", "Sales", datetime.date(2007, 6, 1)),
    
  1011.                 ("Smith", "Sales", datetime.date(2007, 6, 1)),
    
  1012.             ],
    
  1013.             transform=lambda row: (
    
  1014.                 row.name,
    
  1015.                 row.department,
    
  1016.                 row.highest_avg_salary_date,
    
  1017.             ),
    
  1018.         )
    
  1019. 
    
  1020.     def test_row_range_rank(self):
    
  1021.         """
    
  1022.         A query with ROWS BETWEEN UNBOUNDED PRECEDING AND 3 FOLLOWING.
    
  1023.         The resulting sum is the sum of the three next (if they exist) and all
    
  1024.         previous rows according to the ordering clause.
    
  1025.         """
    
  1026.         qs = Employee.objects.annotate(
    
  1027.             sum=Window(
    
  1028.                 expression=Sum("salary"),
    
  1029.                 order_by=[F("hire_date").asc(), F("name").desc()],
    
  1030.                 frame=RowRange(start=None, end=3),
    
  1031.             )
    
  1032.         ).order_by("sum", "hire_date")
    
  1033.         self.assertIn("ROWS BETWEEN UNBOUNDED PRECEDING AND 3 FOLLOWING", str(qs.query))
    
  1034.         self.assertQuerysetEqual(
    
  1035.             qs,
    
  1036.             [
    
  1037.                 ("Miller", 100000, "Management", datetime.date(2005, 6, 1), 280000),
    
  1038.                 ("Johnson", 80000, "Management", datetime.date(2005, 7, 1), 325000),
    
  1039.                 ("Jones", 45000, "Accounting", datetime.date(2005, 11, 1), 362000),
    
  1040.                 ("Smith", 55000, "Sales", datetime.date(2007, 6, 1), 415000),
    
  1041.                 ("Jenson", 45000, "Accounting", datetime.date(2008, 4, 1), 453000),
    
  1042.                 ("Williams", 37000, "Accounting", datetime.date(2009, 6, 1), 513000),
    
  1043.                 ("Brown", 53000, "Sales", datetime.date(2009, 9, 1), 553000),
    
  1044.                 ("Smith", 38000, "Marketing", datetime.date(2009, 10, 1), 603000),
    
  1045.                 ("Wilkinson", 60000, "IT", datetime.date(2011, 3, 1), 637000),
    
  1046.                 ("Johnson", 40000, "Marketing", datetime.date(2012, 3, 1), 637000),
    
  1047.                 ("Adams", 50000, "Accounting", datetime.date(2013, 7, 1), 637000),
    
  1048.                 ("Moore", 34000, "IT", datetime.date(2013, 8, 1), 637000),
    
  1049.             ],
    
  1050.             transform=lambda row: (
    
  1051.                 row.name,
    
  1052.                 row.salary,
    
  1053.                 row.department,
    
  1054.                 row.hire_date,
    
  1055.                 row.sum,
    
  1056.             ),
    
  1057.         )
    
  1058. 
    
  1059.     @skipUnlessDBFeature("can_distinct_on_fields")
    
  1060.     def test_distinct_window_function(self):
    
  1061.         """
    
  1062.         Window functions are not aggregates, and hence a query to filter out
    
  1063.         duplicates may be useful.
    
  1064.         """
    
  1065.         qs = (
    
  1066.             Employee.objects.annotate(
    
  1067.                 sum=Window(
    
  1068.                     expression=Sum("salary"),
    
  1069.                     partition_by=ExtractYear("hire_date"),
    
  1070.                     order_by=ExtractYear("hire_date"),
    
  1071.                 ),
    
  1072.                 year=ExtractYear("hire_date"),
    
  1073.             )
    
  1074.             .values("year", "sum")
    
  1075.             .distinct("year")
    
  1076.             .order_by("year")
    
  1077.         )
    
  1078.         results = [
    
  1079.             {"year": 2005, "sum": 225000},
    
  1080.             {"year": 2007, "sum": 55000},
    
  1081.             {"year": 2008, "sum": 45000},
    
  1082.             {"year": 2009, "sum": 128000},
    
  1083.             {"year": 2011, "sum": 60000},
    
  1084.             {"year": 2012, "sum": 40000},
    
  1085.             {"year": 2013, "sum": 84000},
    
  1086.         ]
    
  1087.         for idx, val in zip(range(len(results)), results):
    
  1088.             with self.subTest(result=val):
    
  1089.                 self.assertEqual(qs[idx], val)
    
  1090. 
    
  1091.     def test_fail_update(self):
    
  1092.         """Window expressions can't be used in an UPDATE statement."""
    
  1093.         msg = (
    
  1094.             "Window expressions are not allowed in this query (salary=<Window: "
    
  1095.             "Max(Col(expressions_window_employee, expressions_window.Employee.salary)) "
    
  1096.             "OVER (PARTITION BY Col(expressions_window_employee, "
    
  1097.             "expressions_window.Employee.department))>)."
    
  1098.         )
    
  1099.         with self.assertRaisesMessage(FieldError, msg):
    
  1100.             Employee.objects.filter(department="Management").update(
    
  1101.                 salary=Window(expression=Max("salary"), partition_by="department"),
    
  1102.             )
    
  1103. 
    
  1104.     def test_fail_insert(self):
    
  1105.         """Window expressions can't be used in an INSERT statement."""
    
  1106.         msg = (
    
  1107.             "Window expressions are not allowed in this query (salary=<Window: "
    
  1108.             "Sum(Value(10000), order_by=OrderBy(F(pk), descending=False)) OVER ()"
    
  1109.         )
    
  1110.         with self.assertRaisesMessage(FieldError, msg):
    
  1111.             Employee.objects.create(
    
  1112.                 name="Jameson",
    
  1113.                 department="Management",
    
  1114.                 hire_date=datetime.date(2007, 7, 1),
    
  1115.                 salary=Window(expression=Sum(Value(10000), order_by=F("pk").asc())),
    
  1116.             )
    
  1117. 
    
  1118.     def test_window_expression_within_subquery(self):
    
  1119.         subquery_qs = Employee.objects.annotate(
    
  1120.             highest=Window(
    
  1121.                 FirstValue("id"),
    
  1122.                 partition_by=F("department"),
    
  1123.                 order_by=F("salary").desc(),
    
  1124.             )
    
  1125.         ).values("highest")
    
  1126.         highest_salary = Employee.objects.filter(pk__in=subquery_qs)
    
  1127.         self.assertCountEqual(
    
  1128.             highest_salary.values("department", "salary"),
    
  1129.             [
    
  1130.                 {"department": "Accounting", "salary": 50000},
    
  1131.                 {"department": "Sales", "salary": 55000},
    
  1132.                 {"department": "Marketing", "salary": 40000},
    
  1133.                 {"department": "IT", "salary": 60000},
    
  1134.                 {"department": "Management", "salary": 100000},
    
  1135.             ],
    
  1136.         )
    
  1137. 
    
  1138.     @skipUnlessDBFeature("supports_json_field")
    
  1139.     def test_key_transform(self):
    
  1140.         Detail.objects.bulk_create(
    
  1141.             [
    
  1142.                 Detail(value={"department": "IT", "name": "Smith", "salary": 37000}),
    
  1143.                 Detail(value={"department": "IT", "name": "Nowak", "salary": 32000}),
    
  1144.                 Detail(value={"department": "HR", "name": "Brown", "salary": 50000}),
    
  1145.                 Detail(value={"department": "HR", "name": "Smith", "salary": 55000}),
    
  1146.                 Detail(value={"department": "PR", "name": "Moore", "salary": 90000}),
    
  1147.             ]
    
  1148.         )
    
  1149.         tests = [
    
  1150.             (KeyTransform("department", "value"), KeyTransform("name", "value")),
    
  1151.             (F("value__department"), F("value__name")),
    
  1152.         ]
    
  1153.         for partition_by, order_by in tests:
    
  1154.             with self.subTest(partition_by=partition_by, order_by=order_by):
    
  1155.                 qs = Detail.objects.annotate(
    
  1156.                     department_sum=Window(
    
  1157.                         expression=Sum(
    
  1158.                             Cast(
    
  1159.                                 KeyTextTransform("salary", "value"),
    
  1160.                                 output_field=IntegerField(),
    
  1161.                             )
    
  1162.                         ),
    
  1163.                         partition_by=[partition_by],
    
  1164.                         order_by=[order_by],
    
  1165.                     )
    
  1166.                 ).order_by("value__department", "department_sum")
    
  1167.                 self.assertQuerysetEqual(
    
  1168.                     qs,
    
  1169.                     [
    
  1170.                         ("Brown", "HR", 50000, 50000),
    
  1171.                         ("Smith", "HR", 55000, 105000),
    
  1172.                         ("Nowak", "IT", 32000, 32000),
    
  1173.                         ("Smith", "IT", 37000, 69000),
    
  1174.                         ("Moore", "PR", 90000, 90000),
    
  1175.                     ],
    
  1176.                     lambda entry: (
    
  1177.                         entry.value["name"],
    
  1178.                         entry.value["department"],
    
  1179.                         entry.value["salary"],
    
  1180.                         entry.department_sum,
    
  1181.                     ),
    
  1182.                 )
    
  1183. 
    
  1184.     def test_invalid_start_value_range(self):
    
  1185.         msg = "start argument must be a negative integer, zero, or None, but got '3'."
    
  1186.         with self.assertRaisesMessage(ValueError, msg):
    
  1187.             list(
    
  1188.                 Employee.objects.annotate(
    
  1189.                     test=Window(
    
  1190.                         expression=Sum("salary"),
    
  1191.                         order_by=F("hire_date").asc(),
    
  1192.                         frame=ValueRange(start=3),
    
  1193.                     )
    
  1194.                 )
    
  1195.             )
    
  1196. 
    
  1197.     def test_invalid_end_value_range(self):
    
  1198.         msg = "end argument must be a positive integer, zero, or None, but got '-3'."
    
  1199.         with self.assertRaisesMessage(ValueError, msg):
    
  1200.             list(
    
  1201.                 Employee.objects.annotate(
    
  1202.                     test=Window(
    
  1203.                         expression=Sum("salary"),
    
  1204.                         order_by=F("hire_date").asc(),
    
  1205.                         frame=ValueRange(end=-3),
    
  1206.                     )
    
  1207.                 )
    
  1208.             )
    
  1209. 
    
  1210.     def test_invalid_type_end_value_range(self):
    
  1211.         msg = "end argument must be a positive integer, zero, or None, but got 'a'."
    
  1212.         with self.assertRaisesMessage(ValueError, msg):
    
  1213.             list(
    
  1214.                 Employee.objects.annotate(
    
  1215.                     test=Window(
    
  1216.                         expression=Sum("salary"),
    
  1217.                         order_by=F("hire_date").asc(),
    
  1218.                         frame=ValueRange(end="a"),
    
  1219.                     )
    
  1220.                 )
    
  1221.             )
    
  1222. 
    
  1223.     def test_invalid_type_start_value_range(self):
    
  1224.         msg = "start argument must be a negative integer, zero, or None, but got 'a'."
    
  1225.         with self.assertRaisesMessage(ValueError, msg):
    
  1226.             list(
    
  1227.                 Employee.objects.annotate(
    
  1228.                     test=Window(
    
  1229.                         expression=Sum("salary"),
    
  1230.                         frame=ValueRange(start="a"),
    
  1231.                     )
    
  1232.                 )
    
  1233.             )
    
  1234. 
    
  1235.     def test_invalid_type_end_row_range(self):
    
  1236.         msg = "end argument must be a positive integer, zero, or None, but got 'a'."
    
  1237.         with self.assertRaisesMessage(ValueError, msg):
    
  1238.             list(
    
  1239.                 Employee.objects.annotate(
    
  1240.                     test=Window(
    
  1241.                         expression=Sum("salary"),
    
  1242.                         frame=RowRange(end="a"),
    
  1243.                     )
    
  1244.                 )
    
  1245.             )
    
  1246. 
    
  1247.     @skipUnlessDBFeature("only_supports_unbounded_with_preceding_and_following")
    
  1248.     def test_unsupported_range_frame_start(self):
    
  1249.         msg = (
    
  1250.             "%s only supports UNBOUNDED together with PRECEDING and FOLLOWING."
    
  1251.             % connection.display_name
    
  1252.         )
    
  1253.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1254.             list(
    
  1255.                 Employee.objects.annotate(
    
  1256.                     test=Window(
    
  1257.                         expression=Sum("salary"),
    
  1258.                         order_by=F("hire_date").asc(),
    
  1259.                         frame=ValueRange(start=-1),
    
  1260.                     )
    
  1261.                 )
    
  1262.             )
    
  1263. 
    
  1264.     @skipUnlessDBFeature("only_supports_unbounded_with_preceding_and_following")
    
  1265.     def test_unsupported_range_frame_end(self):
    
  1266.         msg = (
    
  1267.             "%s only supports UNBOUNDED together with PRECEDING and FOLLOWING."
    
  1268.             % connection.display_name
    
  1269.         )
    
  1270.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1271.             list(
    
  1272.                 Employee.objects.annotate(
    
  1273.                     test=Window(
    
  1274.                         expression=Sum("salary"),
    
  1275.                         order_by=F("hire_date").asc(),
    
  1276.                         frame=ValueRange(end=1),
    
  1277.                     )
    
  1278.                 )
    
  1279.             )
    
  1280. 
    
  1281.     def test_invalid_type_start_row_range(self):
    
  1282.         msg = "start argument must be a negative integer, zero, or None, but got 'a'."
    
  1283.         with self.assertRaisesMessage(ValueError, msg):
    
  1284.             list(
    
  1285.                 Employee.objects.annotate(
    
  1286.                     test=Window(
    
  1287.                         expression=Sum("salary"),
    
  1288.                         order_by=F("hire_date").asc(),
    
  1289.                         frame=RowRange(start="a"),
    
  1290.                     )
    
  1291.                 )
    
  1292.             )
    
  1293. 
    
  1294. 
    
  1295. class WindowUnsupportedTests(TestCase):
    
  1296.     def test_unsupported_backend(self):
    
  1297.         msg = "This backend does not support window expressions."
    
  1298.         with mock.patch.object(connection.features, "supports_over_clause", False):
    
  1299.             with self.assertRaisesMessage(NotSupportedError, msg):
    
  1300.                 Employee.objects.annotate(
    
  1301.                     dense_rank=Window(expression=DenseRank())
    
  1302.                 ).get()
    
  1303. 
    
  1304. 
    
  1305. class NonQueryWindowTests(SimpleTestCase):
    
  1306.     def test_window_repr(self):
    
  1307.         self.assertEqual(
    
  1308.             repr(Window(expression=Sum("salary"), partition_by="department")),
    
  1309.             "<Window: Sum(F(salary)) OVER (PARTITION BY F(department))>",
    
  1310.         )
    
  1311.         self.assertEqual(
    
  1312.             repr(Window(expression=Avg("salary"), order_by=F("department").asc())),
    
  1313.             "<Window: Avg(F(salary)) OVER (OrderByList(OrderBy(F(department), "
    
  1314.             "descending=False)))>",
    
  1315.         )
    
  1316. 
    
  1317.     def test_window_frame_repr(self):
    
  1318.         self.assertEqual(
    
  1319.             repr(RowRange(start=-1)),
    
  1320.             "<RowRange: ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING>",
    
  1321.         )
    
  1322.         self.assertEqual(
    
  1323.             repr(ValueRange(start=None, end=1)),
    
  1324.             "<ValueRange: RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING>",
    
  1325.         )
    
  1326.         self.assertEqual(
    
  1327.             repr(ValueRange(start=0, end=0)),
    
  1328.             "<ValueRange: RANGE BETWEEN CURRENT ROW AND CURRENT ROW>",
    
  1329.         )
    
  1330.         self.assertEqual(
    
  1331.             repr(RowRange(start=0, end=0)),
    
  1332.             "<RowRange: ROWS BETWEEN CURRENT ROW AND CURRENT ROW>",
    
  1333.         )
    
  1334. 
    
  1335.     def test_empty_group_by_cols(self):
    
  1336.         window = Window(expression=Sum("pk"))
    
  1337.         self.assertEqual(window.get_group_by_cols(), [])
    
  1338.         self.assertFalse(window.contains_aggregate)
    
  1339. 
    
  1340.     def test_frame_empty_group_by_cols(self):
    
  1341.         frame = WindowFrame()
    
  1342.         self.assertEqual(frame.get_group_by_cols(), [])
    
  1343. 
    
  1344.     def test_frame_window_frame_notimplemented(self):
    
  1345.         frame = WindowFrame()
    
  1346.         msg = "Subclasses must implement window_frame_start_end()."
    
  1347.         with self.assertRaisesMessage(NotImplementedError, msg):
    
  1348.             frame.window_frame_start_end(None, None, None)
    
  1349. 
    
  1350.     def test_invalid_filter(self):
    
  1351.         msg = "Window is disallowed in the filter clause"
    
  1352.         qs = Employee.objects.annotate(dense_rank=Window(expression=DenseRank()))
    
  1353.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1354.             qs.filter(dense_rank__gte=1)
    
  1355.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1356.             qs.annotate(inc_rank=F("dense_rank") + Value(1)).filter(inc_rank__gte=1)
    
  1357.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1358.             qs.filter(id=F("dense_rank"))
    
  1359.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1360.             qs.filter(id=Func("dense_rank", 2, function="div"))
    
  1361.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1362.             qs.annotate(total=Sum("dense_rank", filter=Q(name="Jones"))).filter(total=1)
    
  1363. 
    
  1364.     def test_conditional_annotation(self):
    
  1365.         qs = Employee.objects.annotate(
    
  1366.             dense_rank=Window(expression=DenseRank()),
    
  1367.         ).annotate(
    
  1368.             equal=Case(
    
  1369.                 When(id=F("dense_rank"), then=Value(True)),
    
  1370.                 default=Value(False),
    
  1371.                 output_field=BooleanField(),
    
  1372.             ),
    
  1373.         )
    
  1374.         # The SQL standard disallows referencing window functions in the WHERE
    
  1375.         # clause.
    
  1376.         msg = "Window is disallowed in the filter clause"
    
  1377.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  1378.             qs.filter(equal=True)
    
  1379. 
    
  1380.     def test_invalid_order_by(self):
    
  1381.         msg = (
    
  1382.             "Window.order_by must be either a string reference to a field, an "
    
  1383.             "expression, or a list or tuple of them."
    
  1384.         )
    
  1385.         with self.assertRaisesMessage(ValueError, msg):
    
  1386.             Window(expression=Sum("power"), order_by={"-horse"})
    
  1387. 
    
  1388.     def test_invalid_source_expression(self):
    
  1389.         msg = "Expression 'Upper' isn't compatible with OVER clauses."
    
  1390.         with self.assertRaisesMessage(ValueError, msg):
    
  1391.             Window(expression=Upper("name"))