order_by: Optional[_ByArgument] = None,
range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ groups: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
) -> Over[_T]:
r"""Produce an :class:`.Over` object against a function.
:param range\_: optional range clause for the window. This is a
tuple value which can contain integer values or ``None``,
and will render a RANGE BETWEEN PRECEDING / FOLLOWING clause.
-
:param rows: optional rows clause for the window. This is a tuple
value which can contain integer values or None, and will render
a ROWS BETWEEN PRECEDING / FOLLOWING clause.
+ :param groups: optional groups clause for the window. This is a
+ tuple value which can contain integer values or ``None``,
+ and will render a GROUPS BETWEEN PRECEDING / FOLLOWING clause.
This function is also available from the :data:`~.expression.func`
construct itself via the :meth:`.FunctionElement.over` method.
:func:`_expression.within_group`
""" # noqa: E501
- return Over(element, partition_by, order_by, range_, rows)
+ return Over(element, partition_by, order_by, range_, rows, groups)
@_document_text_coercion("text", ":func:`.text`", ":paramref:`.text.text`")
range_ = f"RANGE BETWEEN {self.process(over.range_, **kwargs)}"
elif over.rows is not None:
range_ = f"ROWS BETWEEN {self.process(over.rows, **kwargs)}"
+ elif over.groups is not None:
+ range_ = f"GROUPS BETWEEN {self.process(over.groups, **kwargs)}"
else:
range_ = None
("partition_by", InternalTraversal.dp_clauseelement),
("range_", InternalTraversal.dp_clauseelement),
("rows", InternalTraversal.dp_clauseelement),
+ ("groups", InternalTraversal.dp_clauseelement),
]
order_by: Optional[ClauseList] = None
range_: Optional[_FrameClause]
rows: Optional[_FrameClause]
+ groups: Optional[_FrameClause]
def __init__(
self,
order_by: Optional[_ByArgument] = None,
range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ groups: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
):
self.element = element
if order_by is not None:
_literal_as_text_role=roles.ByOfRole,
)
+ self.range_, self.rows, self.groups = None, None, None
if range_:
self.range_ = _FrameClause(range_)
if rows:
raise exc.ArgumentError(
"'range_' and 'rows' are mutually exclusive"
)
- else:
- self.rows = None
- elif rows:
+ if groups:
+ raise exc.ArgumentError(
+ "'range_' and 'groups' are mutually exclusive"
+ )
+ if rows:
self.rows = _FrameClause(rows)
- self.range_ = None
- else:
- self.rows = self.range_ = None
+ if range_:
+ raise exc.ArgumentError(
+ "'rows' and 'range_' are mutually exclusive"
+ )
+ if groups:
+ raise exc.ArgumentError(
+ "'rows' and 'groups' are mutually exclusive"
+ )
+ if groups:
+ self.groups = _FrameClause(groups)
+ if range_:
+ raise exc.ArgumentError(
+ "'groups' and 'range_' are mutually exclusive"
+ )
+ if rows:
+ raise exc.ArgumentError(
+ "'groups' and 'rows' are mutually exclusive"
+ )
if not TYPE_CHECKING:
order_by: Optional[_ByArgument] = None,
rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ groups: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
) -> Over[_T]:
"""Produce an OVER clause against this :class:`.WithinGroup`
construct.
order_by=order_by,
range_=range_,
rows=rows,
+ groups=groups,
)
@overload
] = None,
range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ groups: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
) -> Over[_T]:
"""Produce an OVER clause against this filtered function.
order_by=order_by,
range_=range_,
rows=rows,
+ groups=groups,
)
def within_group(
order_by: Optional[_ByArgument] = None,
rows: Optional[Tuple[Optional[int], Optional[int]]] = None,
range_: Optional[Tuple[Optional[int], Optional[int]]] = None,
+ groups: Optional[Tuple[Optional[int], Optional[int]]] = None,
) -> Over[_T]:
"""Produce an OVER clause against this function.
order_by=order_by,
rows=rows,
range_=range_,
+ groups=groups,
)
def within_group(
"max(users.name) OVER (ROWS BETWEEN CURRENT "
"ROW AND UNBOUNDED FOLLOWING)",
),
+ (
+ lambda: func.max(users.c.name).over(groups=(None, 0)),
+ "max(users.name) OVER (GROUPS BETWEEN UNBOUNDED "
+ "PRECEDING AND CURRENT ROW)",
+ ),
+ (
+ lambda: func.max(users.c.name).over(groups=(0, None)),
+ "max(users.name) OVER (GROUPS BETWEEN CURRENT "
+ "ROW AND UNBOUNDED FOLLOWING)",
+ ),
)
def test_over(self, over_fn, sql):
o = over_fn()
checkparams={"param_1": 10, "param_2": 1},
)
+ self.assert_compile(
+ select(func.row_number().over(order_by=expr, groups=(None, 0))),
+ "SELECT row_number() OVER "
+ "(ORDER BY mytable.myid GROUPS BETWEEN "
+ "UNBOUNDED PRECEDING AND CURRENT ROW)"
+ " AS anon_1 FROM mytable",
+ )
+
+ self.assert_compile(
+ select(func.row_number().over(order_by=expr, groups=(-5, 10))),
+ "SELECT row_number() OVER "
+ "(ORDER BY mytable.myid GROUPS BETWEEN "
+ ":param_1 PRECEDING AND :param_2 FOLLOWING)"
+ " AS anon_1 FROM mytable",
+ checkparams={"param_1": 5, "param_2": 10},
+ )
+
+ self.assert_compile(
+ select(func.row_number().over(order_by=expr, groups=(1, 10))),
+ "SELECT row_number() OVER "
+ "(ORDER BY mytable.myid GROUPS BETWEEN "
+ ":param_1 FOLLOWING AND :param_2 FOLLOWING)"
+ " AS anon_1 FROM mytable",
+ checkparams={"param_1": 1, "param_2": 10},
+ )
+
+ self.assert_compile(
+ select(func.row_number().over(order_by=expr, groups=(-10, -1))),
+ "SELECT row_number() OVER "
+ "(ORDER BY mytable.myid GROUPS BETWEEN "
+ ":param_1 PRECEDING AND :param_2 PRECEDING)"
+ " AS anon_1 FROM mytable",
+ checkparams={"param_1": 10, "param_2": 1},
+ )
+
def test_over_invalid_framespecs(self):
assert_raises_message(
exc.ArgumentError,
"AS anon_1 FROM mytable",
)
+ def test_funcfilter_windowing_groups(self):
+ self.assert_compile(
+ select(
+ func.rank()
+ .filter(table1.c.name > "foo")
+ .over(groups=(1, 5), partition_by=["description"])
+ ),
+ "SELECT rank() FILTER (WHERE mytable.name > :name_1) "
+ "OVER (PARTITION BY mytable.description GROUPS BETWEEN :param_1 "
+ "FOLLOWING AND :param_2 FOLLOWING) "
+ "AS anon_1 FROM mytable",
+ )
+
+ def test_funcfilter_windowing_groups_positional(self):
+ self.assert_compile(
+ select(
+ func.rank()
+ .filter(table1.c.name > "foo")
+ .over(groups=(1, 5), partition_by=["description"])
+ ),
+ "SELECT rank() FILTER (WHERE mytable.name > ?) "
+ "OVER (PARTITION BY mytable.description GROUPS BETWEEN ? "
+ "FOLLOWING AND ? FOLLOWING) "
+ "AS anon_1 FROM mytable",
+ checkpositional=("foo", 1, 5),
+ dialect="default_qmark",
+ )
+
def test_funcfilter_more_criteria(self):
ff = func.rank().filter(table1.c.name > "foo")
ff2 = ff.filter(table1.c.myid == 1)