Skip to content

[fix](nereids) allow constant output column not in GROUP BY under only_full_group_by#64458

Open
924060929 wants to merge 2 commits into
apache:masterfrom
924060929:support_groupby_select_alias
Open

[fix](nereids) allow constant output column not in GROUP BY under only_full_group_by#64458
924060929 wants to merge 2 commits into
apache:masterfrom
924060929:support_groupby_select_alias

Conversation

@924060929

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

Under only_full_group_by (the default), Nereids rejected any non-aggregated select
expression that was neither in GROUP BY nor wrapped in an aggregate function, even when
that expression is constant for every input row. MySQL accepts such expressions via
functional dependency. For example:

SELECT a AS b, b AS c FROM (SELECT 1 AS a, 2 AS b) t1 GROUP BY b, c;

The output alias b collides with the derived-table column b, so column-first name
resolution groups by the column b and leaves the constant column a ungrouped. This
previously failed with:

PROJECT expression 'a' must appear in the GROUP BY clause or be used in an aggregate function

NormalizeAggregate now only rejects non-constant missing slots. Constant (uniform)
slots flow through the existing any_value() wrapping (any_value of a constant is that
constant), so the result stays unambiguous and the query returns (1, 2), matching MySQL.
The same shape over a real table, where the ungrouped column is non-constant, is still
rejected.

Release note

Allow a constant (uniform) non-aggregated column that is not in GROUP BY under
only_full_group_by, matching MySQL functional-dependency behavior.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
  • Behavior changed:

    • Yes. A constant column that is neither grouped nor used in an aggregate is now
      accepted under only_full_group_by (previously rejected). Non-constant columns are
      still rejected.
  • Does this need documentation?

    • No.

…y_full_group_by

Under only_full_group_by, Nereids rejected any non-aggregated select
expression that was neither grouped nor wrapped in an aggregate function,
even when that expression is constant for every input row. MySQL accepts
such expressions via functional dependency, e.g.

  SELECT a AS b, b AS c FROM (SELECT 1 AS a, 2 AS b) t1 GROUP BY b, c

where the output alias 'b' collides with the derived-table column 'b', so
column-first resolution groups by the column 'b' and leaves the constant
column 'a' ungrouped.

NormalizeAggregate now only rejects non-constant missing slots; constant
(uniform) ones flow through the existing any_value() wrapping (any_value of
a constant is that constant), so the result stays unambiguous and the query
returns (1, 2). The same shape over a real table, where the ungrouped
column is non-constant, is still rejected, matching MySQL.
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@924060929

Copy link
Copy Markdown
Contributor Author

run buildall

@924060929

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found blocking issues in the only_full_group_by relaxation.

Critical checkpoint conclusions:

  • Goal/test: The intended constant-output case is covered, but constant group-key elimination and nullable outer-join uniform propagation are not covered; both affect correctness of the new acceptance path.
  • Scope/focus: The change is small, but it relies on DataTrait.isUniform in a context that needs a stronger "same value for every aggregate input row" guarantee.
  • Concurrency/lifecycle/config/compatibility/transactions/persistence/FE-BE protocol: Not involved in this PR.
  • Parallel paths: Sort/HAVING missing-slot paths were considered; no additional issue found for the stated select-output scope.
  • Test coverage/results: Unit and regression tests were added, but the regression test violates Doris regression-test conventions and misses the blocking edge cases above.
  • Observability/performance: No material concern found.
  • User focus: No additional user-provided review focus was supplied.

}
// Under only_full_group_by, a non-aggregated output expression is allowed only when it
// is constant for every input row (a uniform slot). This matches MySQL, which accepts
// such expressions by functional dependency, e.g.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This predicate is too weak for only_full_group_by. DataTrait.isUniform() can be propagated from the nullable side of an outer join via LogicalJoin.computeUniform() -> addUniformSlotForOuterJoinNullableSide(), where a slot can still be NULL on unmatched rows and non-NULL on matched rows in the same later group. For example, with nullable t_v.v: SELECT r.v FROM (SELECT 1 AS g, 1 AS k UNION ALL SELECT 1 AS g, 2 AS k) l LEFT JOIN (SELECT 1 AS k, v FROM t_v LIMIT 1) r ON l.k = r.k GROUP BY l.g. The right LIMIT 1 marks r.v uniform, the left join propagates it, and this branch now wraps r.v in any_value() even though the group can contain both v and NULL. That changes a query that should remain rejected into an ambiguous result. Please require a trait that proves one value for every aggregate input row, or otherwise account for nullable outer-join extension, before allowing the slot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a8e7db0: switched the predicate to isUniformAndNotNull. addUniformSlotForOuterJoinNullableSide only adds entries that are nullable-without-const-value or a NULL literal, so requiring not-null excludes the outer-join case while still allowing genuine non-null constants like 1 AS a. (isUniformAndHasConstValue would be too strict here, since LogicalOneRowRelation.computeUniform records uniform slots without a const value.) Added unit test outerJoinNullableUniformRejected using your repro.

// For any slots missing in aggregate's output, we add an any_value(slot) into aggregate's
// output list and the slot itself into the bottom project's output list. When
// only_full_group_by is enabled, all remaining missing slots are constant (checked above).
bottomProjects = Sets.union(bottomProjects, missingSlotsInAggregate);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appending the any_value alias to the builder is not enough when eliminateGroupByConstant() runs. normalizedAggOutput was built before missing slots are added, and that stale list is passed into eliminateGroupByConstant(). If the group key is a foldable constant, for example SELECT a, b FROM (SELECT 1 AS a, 2 AS b) t GROUP BY 'g', upperProjects is rewritten to these new any_value slots here, but constant-key elimination rebuilds the aggregate from the stale normalizedAggOutput and drops the aliases. The resulting upper project can reference slots the aggregate no longer outputs. Please pass the final aggregate output, or update constant-key elimination to preserve these newly added aliases, and add a test for this combination.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a8e7db0, in two parts:

  1. Pass newAggregate.getOutputExpressions() (the current output, including the any_value aliases) to eliminateGroupByConstant instead of the stale normalizedAggOutput snapshot.
  2. That alone was not enough. I probed the rewritten plans: a later pass collapses the aggregate after constant-key elimination and does not preserve the any_value aliases. This happens for any constant group-by key (e.g. GROUP BY k2, 'g'), not only the all-constant case. So the relaxation now skips cases with a constant group-by key and keeps the original rejection there — same behavior as before this PR, and no dangling plan. Added unit test constantGroupKeyStillRejected.

// column 'b' is grouped and the constant column 'a' is left ungrouped. Because 'a' is constant for
// every input row it is accepted (MySQL functional-dependency behavior), and the query returns (1, 2).
def r1 = sql "SELECT a as b, b as c FROM (SELECT 1 as a, 2 as b) t1 GROUP BY b, c"
assertEquals(1, r1.size())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added regression test is checking deterministic result rows with assertEquals. The repository regression-test rules require determined expected results to be generated via qt_sql/order_qt_* and the generated .out file, rather than manual assertions. Please convert these positive cases to query tests with deterministic ordering as needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a8e7db0: converted the positive result cases to qt_* with a generated .out file.

exception "must appear in the GROUP BY"
}

sql "DROP TABLE IF EXISTS test_gb_const_t"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not drop the regression table at the end. The repository test rules require dropping before use and preserving the final table state for debugging after a failure. The DROP TABLE IF EXISTS before create is enough; remove this final drop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a8e7db0: removed the final DROP and kept only the DROP-before-create.

…-by key

- require isUniformAndNotNull (not isUniform) so a uniform-but-nullable slot from the nullable
  side of an outer join is not wrapped in any_value (it can be NULL on unmatched rows of a group)
- skip the relaxation when a constant group-by key is present: such keys are eliminated and the
  all-constant aggregate is later collapsed without preserving the any_value aliases, so keep the
  original rejection in that case (no behavior change vs before the relaxation)
- pass the current aggregate output to eliminateGroupByConstant instead of the stale snapshot
- regression test: use qt_* with a generated .out instead of manual assertEquals; drop the table
  only before use, not at the end
- add unit tests for the outer-join-nullable and constant-group-by-key cases
@924060929

Copy link
Copy Markdown
Contributor Author

/review

@924060929

Copy link
Copy Markdown
Contributor Author

run buildall

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary: no new blocking issues found in the current PR diff.

Critical checkpoint conclusions:

  • Goal/test coverage: The change now allows non-aggregated output slots only when the aggregate child proves they are uniform and not null under only_full_group_by. The added FE unit tests cover the accepted constant cases, non-constant rejection, mixed missing-slot reporting, nullable outer-join rejection, and constant group-key rejection. The regression test uses qt_* with a generated .out file and drops the table before use.
  • Scope: The current GitHub PR diff is focused on NormalizeAggregate and matching tests.
  • Concurrency/lifecycle/config/compatibility: No new concurrency, lifecycle, config, persistence, storage-format, or FE-BE protocol concerns in the current diff.
  • Parallel paths: This is Nereids aggregate analysis behavior; I did not find another equivalent modified path that also needs the same change.
  • Data correctness: The prior outer-join nullable uniform and constant group-key alias-loss concerns are addressed in the current head and covered by tests.
  • Observability/performance: No new observability requirement; added work is small trait checks and alias rewrites in analysis.
  • Test execution: I did not run the full FE/regression suites in this runner; review was static plus diff/context inspection.

User focus: review_focus.txt says there are no additional user-provided focus points; no extra issue was found there.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 96.00% (24/25) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29417 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit aa46e759d324264378ddff74b74543b979c43754, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17735	4234	4134	4134
q2	q3	10759	1352	825	825
q4	4681	466	342	342
q5	7525	855	591	591
q6	185	173	139	139
q7	777	834	625	625
q8	9342	1474	1693	1474
q9	5778	4512	4518	4512
q10	6805	1782	1534	1534
q11	423	266	257	257
q12	628	426	300	300
q13	18123	3439	2803	2803
q14	264	252	259	252
q15	q16	822	772	713	713
q17	923	930	906	906
q18	7087	5633	5598	5598
q19	1357	1265	1095	1095
q20	513	402	262	262
q21	6171	2881	2722	2722
q22	456	385	333	333
Total cold run time: 100354 ms
Total hot run time: 29417 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	5218	4941	4925	4925
q2	q3	4903	5307	4758	4758
q4	2164	2241	1415	1415
q5	4738	4912	4723	4723
q6	235	176	130	130
q7	1883	1811	1581	1581
q8	2544	2248	2228	2228
q9	7982	7649	7475	7475
q10	4746	4678	4203	4203
q11	554	402	379	379
q12	740	746	531	531
q13	3019	3362	2802	2802
q14	284	280	266	266
q15	q16	683	699	624	624
q17	1321	1302	1288	1288
q18	7541	6944	6918	6918
q19	1073	1068	1089	1068
q20	2262	2227	1953	1953
q21	5378	4677	4529	4529
q22	532	457	398	398
Total cold run time: 57800 ms
Total hot run time: 52194 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 168881 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit aa46e759d324264378ddff74b74543b979c43754, data reload: false

query5	4310	615	487	487
query6	434	188	176	176
query7	4808	539	296	296
query8	382	211	208	208
query9	8757	4068	4120	4068
query10	456	309	259	259
query11	5907	2330	2208	2208
query12	163	101	100	100
query13	1272	590	426	426
query14	6799	5478	5078	5078
query14_1	4457	4471	4452	4452
query15	214	199	177	177
query16	1003	454	386	386
query17	1159	710	597	597
query18	2710	483	361	361
query19	204	191	146	146
query20	117	115	107	107
query21	221	144	122	122
query22	13695	13623	13533	13533
query23	17429	16537	16186	16186
query23_1	16392	16299	16320	16299
query24	7464	1781	1331	1331
query24_1	1346	1328	1325	1325
query25	564	478	402	402
query26	1288	318	170	170
query27	2632	512	342	342
query28	4419	2058	2059	2058
query29	1077	631	507	507
query30	313	241	205	205
query31	1137	1073	952	952
query32	114	64	63	63
query33	555	324	269	269
query34	1163	1174	663	663
query35	747	765	698	698
query36	1397	1370	1180	1180
query37	155	107	88	88
query38	3215	3122	3042	3042
query39	932	905	885	885
query39_1	881	877	868	868
query40	224	120	100	100
query41	64	62	62	62
query42	95	89	89	89
query43	320	317	284	284
query44	
query45	197	186	175	175
query46	1129	1223	725	725
query47	2380	2343	2206	2206
query48	421	408	289	289
query49	644	458	348	348
query50	939	356	265	265
query51	4367	4305	4290	4290
query52	89	87	83	83
query53	247	265	186	186
query54	259	218	186	186
query55	81	76	68	68
query56	226	232	202	202
query57	1440	1415	1321	1321
query58	231	208	211	208
query59	1599	1685	1473	1473
query60	278	267	232	232
query61	159	154	152	152
query62	708	652	594	594
query63	226	189	190	189
query64	2491	784	612	612
query65	
query66	1748	460	338	338
query67	29730	29619	29403	29403
query68	
query69	414	294	266	266
query70	997	899	962	899
query71	314	217	210	210
query72	2941	2623	2357	2357
query73	824	770	453	453
query74	5136	4971	4829	4829
query75	2658	2572	2250	2250
query76	2327	1139	772	772
query77	382	365	283	283
query78	12373	12489	11937	11937
query79	1405	1066	720	720
query80	600	485	392	392
query81	451	278	240	240
query82	567	151	122	122
query83	359	280	245	245
query84	
query85	870	502	410	410
query86	368	320	281	281
query87	3356	3363	3145	3145
query88	3660	2754	2746	2746
query89	432	380	326	326
query90	1962	176	175	175
query91	167	167	134	134
query92	60	63	53	53
query93	1445	1423	932	932
query94	545	335	327	327
query95	669	472	338	338
query96	1071	794	357	357
query97	2690	2689	2545	2545
query98	213	205	198	198
query99	1164	1165	1028	1028
Total cold run time: 251307 ms
Total hot run time: 168881 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29368 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit a8e7db00dc23c3b0bfd45662f315fbfdb16fda63, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17625	4148	4148	4148
q2	q3	10824	1438	805	805
q4	4685	467	340	340
q5	7571	878	584	584
q6	185	174	135	135
q7	769	831	663	663
q8	9339	1561	1518	1518
q9	5861	4491	4489	4489
q10	6772	1829	1560	1560
q11	433	269	247	247
q12	634	419	300	300
q13	18195	3476	2790	2790
q14	266	258	234	234
q15	q16	816	773	711	711
q17	950	903	1032	903
q18	6958	5779	5584	5584
q19	1346	1341	1154	1154
q20	515	402	263	263
q21	6331	2855	2621	2621
q22	462	368	319	319
Total cold run time: 100537 ms
Total hot run time: 29368 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	5161	4817	4677	4677
q2	q3	4874	5285	4667	4667
q4	2089	2190	1399	1399
q5	4733	4822	4719	4719
q6	235	174	128	128
q7	1927	1797	1626	1626
q8	2387	2100	2107	2100
q9	7889	7613	7352	7352
q10	4774	4688	4275	4275
q11	537	389	358	358
q12	728	739	535	535
q13	3031	3430	2808	2808
q14	277	287	255	255
q15	q16	683	690	608	608
q17	1298	1252	1256	1252
q18	7347	6916	6884	6884
q19	1119	1107	1050	1050
q20	2222	2226	1931	1931
q21	5279	4620	4420	4420
q22	521	471	416	416
Total cold run time: 57111 ms
Total hot run time: 51460 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 169534 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit a8e7db00dc23c3b0bfd45662f315fbfdb16fda63, data reload: false

query5	4328	628	476	476
query6	453	201	195	195
query7	4906	542	315	315
query8	371	219	203	203
query9	8778	4013	4044	4013
query10	451	309	261	261
query11	5916	2377	2180	2180
query12	160	101	100	100
query13	1256	585	451	451
query14	6438	5401	5045	5045
query14_1	4330	4336	4357	4336
query15	203	193	173	173
query16	987	443	415	415
query17	920	692	566	566
query18	2430	461	328	328
query19	199	185	136	136
query20	108	108	101	101
query21	225	135	111	111
query22	13589	13615	13381	13381
query23	17255	16507	16186	16186
query23_1	16223	16226	16213	16213
query24	7569	1741	1302	1302
query24_1	1321	1287	1275	1275
query25	522	439	372	372
query26	1334	327	176	176
query27	2645	583	339	339
query28	4490	2043	2009	2009
query29	1086	609	493	493
query30	315	240	201	201
query31	1112	1072	961	961
query32	125	63	62	62
query33	522	329	254	254
query34	1166	1118	666	666
query35	754	780	681	681
query36	1390	1405	1248	1248
query37	159	110	91	91
query38	3227	3115	3036	3036
query39	929	918	887	887
query39_1	874	890	875	875
query40	225	126	105	105
query41	71	69	67	67
query42	96	99	97	97
query43	321	325	283	283
query44	
query45	199	194	184	184
query46	1129	1208	785	785
query47	2342	2344	2208	2208
query48	403	412	300	300
query49	654	484	371	371
query50	980	353	278	278
query51	4344	4278	4249	4249
query52	90	90	77	77
query53	259	269	191	191
query54	282	236	207	207
query55	79	77	71	71
query56	272	234	234	234
query57	1427	1449	1296	1296
query58	255	223	220	220
query59	1578	1652	1397	1397
query60	288	271	246	246
query61	175	175	201	175
query62	710	633	590	590
query63	224	184	184	184
query64	2514	755	603	603
query65	
query66	1853	457	334	334
query67	29779	29749	29480	29480
query68	
query69	429	315	263	263
query70	980	969	946	946
query71	307	222	210	210
query72	2964	2589	2276	2276
query73	831	772	452	452
query74	5128	4933	4736	4736
query75	2633	2578	2251	2251
query76	2318	1147	776	776
query77	383	371	290	290
query78	12505	12384	11963	11963
query79	1464	1004	793	793
query80	592	492	395	395
query81	455	278	241	241
query82	606	154	121	121
query83	331	274	244	244
query84	
query85	874	512	440	440
query86	366	281	283	281
query87	3371	3346	3168	3168
query88	3615	2775	2743	2743
query89	421	378	326	326
query90	1871	177	176	176
query91	168	159	142	142
query92	64	62	57	57
query93	1519	1517	886	886
query94	541	329	304	304
query95	667	362	355	355
query96	1069	802	372	372
query97	2665	2660	2567	2567
query98	211	209	200	200
query99	1134	1167	1033	1033
Total cold run time: 250454 ms
Total hot run time: 169534 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 96.30% (26/27) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 3.29% (25/760) 🎉
Increment coverage report
Complete coverage report

1 similar comment
@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 3.29% (25/760) 🎉
Increment coverage report
Complete coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants