@@ -287,10 +287,13 @@ 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,
294+ const vector<DescribeAggregateInfo> &aggregates, vector<string> &inner_aggregates,
295+ vector<string> &outer_projection) {
296+ outer_projection.reserve (columns.size () + 1 );
294297
295298 string aggr_names = " UNNEST([" ;
296299 for (idx_t i = 0 ; i < aggregates.size (); i++) {
@@ -303,33 +306,35 @@ vector<string> CreateExpressionList(const vector<ColumnDefinition> &columns,
303306 }
304307 aggr_names += " ])" ;
305308 aggr_names += " AS aggr" ;
306- expressions.push_back (aggr_names);
309+ outer_projection.push_back (aggr_names);
310+
307311 for (idx_t c = 0 ; c < columns.size (); c++) {
308312 auto &col = columns[c];
309- string expr = " UNNEST([" ;
313+ bool numeric = col.GetType ().IsNumeric ();
314+ string unnest = " UNNEST([" ;
310315 for (idx_t i = 0 ; i < aggregates.size (); i++) {
316+ auto alias = " __describe_" + std::to_string (c) + " _" + std::to_string (i);
311317 if (i > 0 ) {
312- expr += " , " ;
313- }
314- if (aggregates[i].numeric_only && !col.GetType ().IsNumeric ()) {
315- expr += " NULL" ;
316- continue ;
318+ unnest += " , " ;
317319 }
318- expr += aggregates[i].name ;
319- expr += " (" ;
320- expr += SQLIdentifier (col.GetName ());
321- expr += " )" ;
322- if (col.GetType ().IsNumeric ()) {
323- expr += " ::DOUBLE" ;
320+ unnest += SQLIdentifier (alias);
321+ string stat;
322+ if (aggregates[i].numeric_only && !numeric) {
323+ stat = " NULL" ;
324324 } else {
325- expr += " ::VARCHAR" ;
325+ stat = aggregates[i].name ;
326+ stat += " (" ;
327+ stat += SQLIdentifier (col.GetName ());
328+ stat += " )" ;
329+ stat += numeric ? " ::DOUBLE" : " ::VARCHAR" ;
326330 }
331+ stat += " AS " + SQLIdentifier (alias);
332+ inner_aggregates.push_back (stat);
327333 }
328- expr += " ])" ;
329- expr += " AS " + SQLIdentifier (col.GetName ());
330- expressions .push_back (expr );
334+ unnest += " ])" ;
335+ unnest += " AS " + SQLIdentifier (col.GetName ());
336+ outer_projection .push_back (unnest );
331337 }
332- return expressions;
333338}
334339
335340std::unique_ptr<DuckDBPyRelation> DuckDBPyRelation::Describe () {
@@ -338,8 +343,11 @@ std::unique_ptr<DuckDBPyRelation> DuckDBPyRelation::Describe() {
338343 aggregates = {DescribeAggregateInfo (" count" ), DescribeAggregateInfo (" mean" , true ),
339344 DescribeAggregateInfo (" stddev" , true ), DescribeAggregateInfo (" min" ),
340345 DescribeAggregateInfo (" max" ), DescribeAggregateInfo (" median" , true )};
341- auto expressions = CreateExpressionList (columns, aggregates);
342- return DeriveRelation (rel->Aggregate (expressions));
346+ vector<string> inner_aggregates;
347+ vector<string> outer_projection;
348+ CreateDescribeExpressions (columns, aggregates, inner_aggregates, outer_projection);
349+ auto stats = rel->Aggregate (inner_aggregates);
350+ return DeriveRelation (stats->Project (outer_projection));
343351}
344352
345353string DuckDBPyRelation::ToSQL () {
0 commit comments