@@ -287,10 +287,12 @@ struct DescribeAggregateInfo {
287287 bool numeric_only;
288288};
289289
290- vector<string> CreateExpressionList (const vector<ColumnDefinition> &columns,
291- const vector<DescribeAggregateInfo> &aggregates) {
292- vector<string> expressions;
293- expressions.reserve (columns.size ());
290+ // Build the Describe query as two levels. The inner single-group aggregate computes one
291+ // scalar per (column, aggregate); the outer projection pivots them into rows via parallel
292+ // UNNESTs. This avoids GROUP BY ALL, which core rejects for UNNEST/UNLIST expressions.
293+ void CreateDescribeExpressions (const vector<ColumnDefinition> &columns, const vector<DescribeAggregateInfo> &aggregates,
294+ vector<string> &inner_aggregates, vector<string> &outer_projection) {
295+ outer_projection.reserve (columns.size () + 1 );
294296
295297 string aggr_names = " UNNEST([" ;
296298 for (idx_t i = 0 ; i < aggregates.size (); i++) {
@@ -303,33 +305,35 @@ vector<string> CreateExpressionList(const vector<ColumnDefinition> &columns,
303305 }
304306 aggr_names += " ])" ;
305307 aggr_names += " AS aggr" ;
306- expressions.push_back (aggr_names);
308+ outer_projection.push_back (aggr_names);
309+
307310 for (idx_t c = 0 ; c < columns.size (); c++) {
308311 auto &col = columns[c];
309- string expr = " UNNEST([" ;
312+ bool numeric = col.GetType ().IsNumeric ();
313+ string unnest = " UNNEST([" ;
310314 for (idx_t i = 0 ; i < aggregates.size (); i++) {
315+ auto alias = " __describe_" + std::to_string (c) + " _" + std::to_string (i);
311316 if (i > 0 ) {
312- expr += " , " ;
313- }
314- if (aggregates[i].numeric_only && !col.GetType ().IsNumeric ()) {
315- expr += " NULL" ;
316- continue ;
317+ unnest += " , " ;
317318 }
318- expr += aggregates[i].name ;
319- expr += " (" ;
320- expr += SQLIdentifier (col.GetName ());
321- expr += " )" ;
322- if (col.GetType ().IsNumeric ()) {
323- expr += " ::DOUBLE" ;
319+ unnest += SQLIdentifier (alias);
320+ string stat;
321+ if (aggregates[i].numeric_only && !numeric) {
322+ stat = " NULL" ;
324323 } else {
325- expr += " ::VARCHAR" ;
324+ stat = aggregates[i].name ;
325+ stat += " (" ;
326+ stat += SQLIdentifier (col.GetName ());
327+ stat += " )" ;
328+ stat += numeric ? " ::DOUBLE" : " ::VARCHAR" ;
326329 }
330+ stat += " AS " + SQLIdentifier (alias);
331+ inner_aggregates.push_back (stat);
327332 }
328- expr += " ])" ;
329- expr += " AS " + SQLIdentifier (col.GetName ());
330- expressions .push_back (expr );
333+ unnest += " ])" ;
334+ unnest += " AS " + SQLIdentifier (col.GetName ());
335+ outer_projection .push_back (unnest );
331336 }
332- return expressions;
333337}
334338
335339std::unique_ptr<DuckDBPyRelation> DuckDBPyRelation::Describe () {
@@ -338,8 +342,11 @@ std::unique_ptr<DuckDBPyRelation> DuckDBPyRelation::Describe() {
338342 aggregates = {DescribeAggregateInfo (" count" ), DescribeAggregateInfo (" mean" , true ),
339343 DescribeAggregateInfo (" stddev" , true ), DescribeAggregateInfo (" min" ),
340344 DescribeAggregateInfo (" max" ), DescribeAggregateInfo (" median" , true )};
341- auto expressions = CreateExpressionList (columns, aggregates);
342- return DeriveRelation (rel->Aggregate (expressions));
345+ vector<string> inner_aggregates;
346+ vector<string> outer_projection;
347+ CreateDescribeExpressions (columns, aggregates, inner_aggregates, outer_projection);
348+ auto stats = rel->Aggregate (inner_aggregates);
349+ return DeriveRelation (stats->Project (outer_projection));
343350}
344351
345352string DuckDBPyRelation::ToSQL () {
0 commit comments