diff --git a/src/api/routes.py b/src/api/routes.py index e4080ce..2fa1915 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -141,9 +141,12 @@ def schema(): "la colonne de sortie est nommée `colonne__count`, `colonne__sum`, " "`colonne__avg`, `colonne__min`, `colonne__max`\n\n" "En mode agrégation, la réponse contient des lignes groupées, " - "`columns` est interdit et `meta` ne contient pas `total`.\n\n" + "`columns` est interdit et `meta` ne contient pas `total`. " + "`sort` peut être appliqué sur une colonne `groupby` (ex. `acheteur_departement_code__sort=asc`) ; " + "il n'est pas supporté sur les alias d'agrégats (ex. `uid__count__sort=desc` → 400).\n\n" "Exemples : `acheteur_id__contains=VILLE`, `montant__greater=10000`, " - "`acheteur_departement_code__groupby&montant__sum`." + "`acheteur_departement_code__groupby&montant__sum`, " + "`acheteur_departement_code__groupby&uid__count&acheteur_departement_code__sort=asc`." ), }, ], @@ -159,7 +162,8 @@ def data(): Agrégation (drapeaux sans valeur) : `__groupby`, `__count|sum|avg|min|max`. Les colonnes agrégées sont nommées `__`. `columns` est interdit avec une agrégation et - `meta` ne contient alors pas `total`. + `meta` ne contient alors pas `total`. `sort` est supporté sur les colonnes + `groupby` ; non supporté sur les alias d'agrégats (→ 400). Paramètres réservés : page (défaut 1), page_size (défaut 50, max 1000), columns (csv), count_results (true|false ; mettre false pour économiser @@ -193,6 +197,7 @@ def data(): where_sql=where_sql, params=params, group_by=agg.group_by_sql, + order_by=order_sql or None, limit=page_size, offset=(page - 1) * page_size, ) diff --git a/src/db.py b/src/db.py index 0235f6f..cc148ed 100644 --- a/src/db.py +++ b/src/db.py @@ -198,18 +198,21 @@ def aggregate_marches( where_sql: str = "TRUE", params: tuple | list = (), group_by: str | None = None, + order_by: str | None = None, limit: int | None = None, offset: int | None = None, ) -> pl.DataFrame: """SELECT agrégé paramétré contre la table decp. - `select_sql` et `group_by` sont des fragments SQL construits depuis des - noms de colonnes validés (jamais de valeur utilisateur libre). Les - valeurs de filtre passent par le binding `?` via `params`. + `select_sql`, `group_by` et `order_by` sont des fragments SQL construits + depuis des noms de colonnes validés (jamais de valeur utilisateur libre). + Les valeurs de filtre passent par le binding `?` via `params`. """ sql = f"SELECT {select_sql} FROM decp WHERE {where_sql}" if group_by: sql += f" GROUP BY {group_by}" + if order_by: + sql += f" ORDER BY {order_by}" if limit is not None: sql += f" LIMIT {int(limit)}" if offset is not None: