mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-22 20:02:36 +00:00
* perf(query): infer safe aggregate input bounds from HAVING Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): refresh explain snapshots for aggregate bound inference Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(query): avoid reinferring bounds across MergeScan Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(query): validate aggregate bound column provenance Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): retain zero-match groups for nonpositive HAVING thresholds Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): assert inferred bounds in remote scan plans Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> --------- Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
127 lines
4.9 KiB
SQL
127 lines
4.9 KiB
SQL
-- Matching spans and the earliest/latest spans deliberately live in different regions.
|
|
CREATE TABLE having_input_bounds (
|
|
trace_id STRING,
|
|
service_name STRING,
|
|
span_name STRING,
|
|
product STRING,
|
|
duration BIGINT,
|
|
ts TIMESTAMP(9) TIME INDEX,
|
|
PRIMARY KEY (service_name)
|
|
)
|
|
PARTITION ON COLUMNS (service_name) (
|
|
service_name < 'm',
|
|
service_name >= 'm'
|
|
)
|
|
WITH ('append_mode' = 'true');
|
|
|
|
INSERT INTO having_input_bounds VALUES
|
|
('a', 'other', 'parent', NULL, 30, 5),
|
|
('a', 'cart', 'GetCart', NULL, 6, 10),
|
|
('a', 'frontend', 'GetProduct', 'product-7', 8, 15),
|
|
('a', 'cart', 'GetCart', NULL, 6, 20),
|
|
('a', 'other', 'parent', NULL, 30, 25),
|
|
('b', 'other', 'parent', NULL, 1, 2),
|
|
('b', 'frontend', 'GetProduct', 'product-x', 18, 12),
|
|
('b', 'other', 'parent', NULL, 30, 22),
|
|
('c', 'cart', 'GetCart', NULL, 5, 30),
|
|
('d', 'frontend', NULL, 'product-7', 8, 15),
|
|
(NULL, 'other', 'parent', NULL, 2, 3),
|
|
(NULL, 'cart', 'GetCart', NULL, 6, 11),
|
|
(NULL, 'other', 'parent', NULL, 30, 25);
|
|
|
|
-- T02 shape: only the upper bound may be inferred; preserve earlier spans and NULL keys.
|
|
SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'cart' AND span_name = 'GetCart'
|
|
AND CAST(ts AS BIGINT) >= 10 AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY trace_start, trace_id
|
|
LIMIT 20;
|
|
|
|
-- Pin the optimization itself: the remote scan has only the inferred time upper
|
|
-- bound, while HAVING stays above aggregation. Result equality alone cannot prove this.
|
|
-- SQLNESS REPLACE (-+) -
|
|
-- SQLNESS REPLACE (\s\s+) _
|
|
-- SQLNESS REPLACE (RepartitionExec:.*) RepartitionExec: REDACTED
|
|
-- SQLNESS REPLACE (MergeScanExec:.*) MergeScanExec: REDACTED
|
|
EXPLAIN SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'cart' AND span_name = 'GetCart'
|
|
AND CAST(ts AS BIGINT) >= 10 AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY trace_start, trace_id
|
|
LIMIT 20;
|
|
|
|
-- T03 shape: a span attribute is part of the existence condition, not an input filter.
|
|
SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'frontend' AND span_name = 'GetProduct'
|
|
AND product = 'product-7' AND CAST(ts AS BIGINT) >= 10
|
|
AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY trace_start, trace_id;
|
|
|
|
-- T04 shape: bounds on a different expression must not become aggregate input bounds.
|
|
SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'frontend' AND span_name = 'GetProduct'
|
|
AND duration >= 5 AND duration < 20 AND CAST(ts AS BIGINT) >= 10
|
|
AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY trace_start, trace_id;
|
|
|
|
-- MAX is symmetric: preserve later spans by inferring only the lower bound.
|
|
-- Also check that the conditional SUM itself remains unchanged.
|
|
SELECT trace_id, MAX(CAST(ts AS BIGINT)) AS trace_end,
|
|
SUM(CASE WHEN service_name = 'cart' AND CAST(ts AS BIGINT) >= 10
|
|
AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) AS matches
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING matches >= 1
|
|
ORDER BY trace_end, trace_id;
|
|
|
|
-- Full-group aggregates forbid pruning, even when the MIN would be preserved.
|
|
SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start,
|
|
MAX(CAST(ts AS BIGINT)) AS trace_end, COUNT(*) AS spans
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'cart' AND CAST(ts AS BIGINT) >= 10
|
|
AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY trace_start, trace_id;
|
|
|
|
-- OR does not guarantee a matching span in every surviving group.
|
|
SELECT trace_id, MIN(CAST(ts AS BIGINT)) AS trace_start
|
|
FROM having_input_bounds
|
|
GROUP BY trace_id
|
|
HAVING SUM(CASE WHEN service_name = 'cart' AND CAST(ts AS BIGINT) < 20 THEN 1 ELSE 0 END) > 0
|
|
OR MIN(CAST(ts AS BIGINT)) > 20
|
|
ORDER BY trace_start, trace_id;
|
|
|
|
DROP TABLE having_input_bounds;
|
|
|
|
-- A derived column must not move fallible arithmetic/casts ahead of its WHERE guard.
|
|
CREATE TABLE having_guarded(k STRING, v BIGINT, d BIGINT, text_value STRING, ts TIMESTAMP TIME INDEX);
|
|
INSERT INTO having_guarded VALUES ('a', 12, 0, 'bad', 1), ('a', 5, 1, '5', 2), ('b', 25, 1, '25', 3);
|
|
|
|
SELECT k, MIN(r) FROM (
|
|
SELECT k, v / d AS r FROM having_guarded WHERE abs(d) > 0
|
|
) s GROUP BY k
|
|
HAVING SUM(CASE WHEN r < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY k;
|
|
|
|
SELECT k, MIN(r) FROM (
|
|
SELECT k, CAST(text_value AS BIGINT) AS r FROM having_guarded WHERE abs(d) > 0
|
|
) s GROUP BY k
|
|
HAVING SUM(CASE WHEN r < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY k;
|
|
|
|
CREATE VIEW having_guarded_view AS
|
|
SELECT k, v / d AS r FROM having_guarded WHERE abs(d) > 0;
|
|
|
|
SELECT k, MIN(r) FROM having_guarded_view GROUP BY k
|
|
HAVING SUM(CASE WHEN r < 20 THEN 1 ELSE 0 END) > 0
|
|
ORDER BY k;
|
|
|
|
DROP VIEW having_guarded_view;
|
|
DROP TABLE having_guarded;
|