mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:21:58 +00:00
差异说明认得一边套了 ROUND:说「X 套了 ROUND(…, 2):保留 2 位小数,另一边没有取整」,只陈述写法、不断言值不同,判等价照旧不等价;零售样例题集的期望 SQL 去掉 ROUND(…, 2)(取整交给展示,评分本来按容差比数),评测逐题比 SQL 不再因为期望里的取整一律对不上
This commit is contained in:
@@ -146,7 +146,9 @@ WHERE / GROUP BY 里用列别名的),而且只说五类:
|
||||
时 MAX(u.city) 和 MIN(u.city) 一样,LEFT JOIN 把行复制了也一样);去重不起作用的不说(MIN / MAX;不分组的聚合只出
|
||||
一行;已经按选出来的列分组;只取一行;表结构里的键说明每行本来就不一样);每组只有一行时 SUM、AVG、MIN、MAX 不说。
|
||||
同一位置上一边套了 COALESCE(聚合, 常量)、另一边就是那个聚合(SUM / AVG / MIN / MAX,COUNT 不会是空、不说),说
|
||||
「X 套了 COALESCE(…, 0):没有符合条件的行(或参数都为空)时 A 是 0、B 是空」(_coalesced)。
|
||||
「X 套了 COALESCE(…, 0):没有符合条件的行(或参数都为空)时 A 是 0、B 是空」(_coalesced)。同一位置上一边套了
|
||||
ROUND(表达式, 位数)、另一边就是那个表达式,说「X 套了 ROUND(…, 2):保留 2 位小数,B 没有取整」(_rounded):取整改了值,
|
||||
判等价照旧不等价;口径、期望 SQL 里的取整最好交给口径卡、展示的小数位。
|
||||
- 同一列上一边按运行当天现算(CURRENT_DATE、date('now', …)、now())、另一边写定的常量(time_range,_relative):说「A 对 X
|
||||
的条件是 …,按运行当天现算;B 写定的是 …」,要求同 _rewrites。
|
||||
- 关联的表、关联类型(tables / join):换了一张表;一边多关联了一张表(表结构里有指向它的外键、关联条件又用到它的键的
|
||||
@@ -7785,7 +7787,8 @@ class _Explainer:
|
||||
return False
|
||||
covered = True
|
||||
for i, (x, y, fx, fy) in enumerate(zip(A.exprs, B.exprs, ta, tb)):
|
||||
if fx != fy and not self._aggregates(x, y, A, B, mapping) and not self._coalesced(i, x, y, A, B, mapping):
|
||||
if fx != fy and not self._aggregates(x, y, A, B, mapping) and not self._coalesced(i, x, y, A, B, mapping) \
|
||||
and not self._rounded(i, x, y, A, B, mapping):
|
||||
covered = False
|
||||
return covered
|
||||
|
||||
@@ -7838,6 +7841,32 @@ class _Explainer:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _rounded(self, index: int, x: Any, y: Any, A: _Block, B: _Block, mapping: Mapping[str, str]) -> bool:
|
||||
"""同一位置上一边是 ROUND(表达式, 位数常量)、另一边就是那个表达式本身:说「X 套了 ROUND(…, 2):保留 2 位小数,B 没有
|
||||
取整」。位数不写(ROUND(x))按 0 位。只改说明:走到这里时两边已经判了不等价(取整确实改了值)。"""
|
||||
from sqlglot import exp
|
||||
|
||||
for side, (wrapped, plain) in enumerate(((x.unalias(), y.unalias()), (y.unalias(), x.unalias()))):
|
||||
if not isinstance(wrapped, exp.Round) or wrapped.this is None:
|
||||
continue
|
||||
digits = wrapped.args.get("decimals")
|
||||
if digits is not None and not (isinstance(digits, exp.Literal) and digits.is_int):
|
||||
continue
|
||||
same = self.fp(wrapped.this) == self.mfp(plain, mapping) if side == 0 else \
|
||||
self.mfp(wrapped.this, mapping) == self.fp(plain)
|
||||
if not same:
|
||||
continue
|
||||
places = int(digits.this) if digits is not None else 0
|
||||
me, them = (self.la, self.lb) if side == 0 else (self.lb, self.la)
|
||||
name = self._output_name(side, index)
|
||||
what = _say(me, "的", name) if name else _say(me, "的这一列")
|
||||
keep = f"保留 {places} 位小数" if places > 0 else "取整到个位"
|
||||
self.add("aggregate", self.show(x, A), self.show(y, B),
|
||||
_say(what, f"套了 ROUND(…, {places}):" if digits is not None else "套了 ROUND(…):", keep + ",",
|
||||
them, "没有取整"))
|
||||
return True
|
||||
return False
|
||||
|
||||
def _dedup(self, A: _Block, B: _Block, ga: list[str], gb: list[str]) -> bool:
|
||||
"""一边去重(SELECT DISTINCT,规范化写成了按选出来的列 GROUP BY)、另一边不去重,选出来的列一样。去重不起作用的
|
||||
不说:不分组的聚合只出一行;已经按选出来的列分组;只取一行;表结构里的键说明每行本来就不一样。"""
|
||||
|
||||
+4
-4
@@ -24,7 +24,7 @@
|
||||
"gold": {
|
||||
"kind": "sql",
|
||||
"datasource": "retail_demo",
|
||||
"sql": "SELECT ROUND(SUM(amount), 2) AS 销售额 FROM orders WHERE status = 'paid' AND paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}'"
|
||||
"sql": "SELECT SUM(amount) AS 销售额 FROM orders WHERE status = 'paid' AND paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}'"
|
||||
},
|
||||
"expectations": [
|
||||
{
|
||||
@@ -50,7 +50,7 @@
|
||||
"gold": {
|
||||
"kind": "sql",
|
||||
"datasource": "retail_demo",
|
||||
"sql": "SELECT COUNT(*) AS 退款单数, ROUND(SUM(amount), 2) AS 退款金额 FROM orders WHERE status = 'refunded' AND paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}'"
|
||||
"sql": "SELECT COUNT(*) AS 退款单数, SUM(amount) AS 退款金额 FROM orders WHERE status = 'refunded' AND paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}'"
|
||||
},
|
||||
"expectations": [
|
||||
{
|
||||
@@ -108,7 +108,7 @@
|
||||
"gold": {
|
||||
"kind": "sql",
|
||||
"datasource": "retail_demo",
|
||||
"sql": "SELECT s.region AS 地区, ROUND(SUM(o.amount), 2) AS 销售额 FROM orders o JOIN stores s ON s.id = o.store_id WHERE o.status = 'paid' AND o.paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}' AND s.region IN ('华南', '华东') GROUP BY s.region ORDER BY 销售额 DESC"
|
||||
"sql": "SELECT s.region AS 地区, SUM(o.amount) AS 销售额 FROM orders o JOIN stores s ON s.id = o.store_id WHERE o.status = 'paid' AND o.paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}' AND s.region IN ('华南', '华东') GROUP BY s.region ORDER BY 销售额 DESC"
|
||||
},
|
||||
"expectations": [
|
||||
{
|
||||
@@ -137,7 +137,7 @@
|
||||
"gold": {
|
||||
"kind": "sql",
|
||||
"datasource": "retail_demo",
|
||||
"sql": "SELECT s.region AS 地区, ROUND(SUM(o.amount), 2) AS 销售额 FROM orders o JOIN stores s ON s.id = o.store_id WHERE s.region = '华中' AND o.status = 'paid' AND o.paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}' GROUP BY s.region"
|
||||
"sql": "SELECT s.region AS 地区, SUM(o.amount) AS 销售额 FROM orders o JOIN stores s ON s.id = o.store_id WHERE s.region = '华中' AND o.status = 'paid' AND o.paid_date BETWEEN '{{ schedule.last_week_start }}' AND '{{ schedule.last_week_end }}' GROUP BY s.region"
|
||||
},
|
||||
"expectations": [
|
||||
{
|
||||
|
||||
@@ -190,6 +190,26 @@ def test_coalesce_explanation_holds_on_the_virtual_shop():
|
||||
assert not [i for i in counted.items if "COALESCE" in i.text]
|
||||
|
||||
|
||||
def test_round_around_the_value_is_still_not_equivalent_and_says_why():
|
||||
"""一边套了 ROUND(…, 2)、另一边没取整:取整改了值,照旧不等价;说明说清是哪一边取整、保留几位,不退回兜底。
|
||||
评测的期望 SQL、口径的取数里常见这种写法(样例题集原来就是),说不清时整段兜底,人看不出只差在取整上。"""
|
||||
from app.data.sql_semantics import explain_sql_diff
|
||||
|
||||
a = "SELECT SUM(amount) AS s FROM orders WHERE status = 'paid'"
|
||||
b = "SELECT ROUND(SUM(amount), 2) AS s FROM orders WHERE status = 'paid'"
|
||||
db = sqlite3.connect(":memory:")
|
||||
db.executescript("CREATE TABLE orders (id INTEGER, status TEXT, amount REAL);"
|
||||
"INSERT INTO orders VALUES (1, 'paid', 10.005), (2, 'paid', 0.001)")
|
||||
assert db.execute(a).fetchall() != db.execute(b).fetchall(), "取整确实改了值"
|
||||
diff = explain_sql_diff(a, b, "sqlite")
|
||||
assert not diff.equivalent and not diff.fallback
|
||||
assert [(i.aspect, i.text) for i in diff.items] == [("aggregate", "B 的 s 套了 ROUND(…, 2):保留 2 位小数,A 没有取整")]
|
||||
whole = explain_sql_diff("SELECT ROUND(SUM(amount)) AS s FROM orders", "SELECT SUM(amount) AS s FROM orders", "sqlite")
|
||||
assert not whole.equivalent and [i.text for i in whole.items] == ["A 的 s 套了 ROUND(…):取整到个位,B 没有取整"]
|
||||
other = explain_sql_diff("SELECT ROUND(AVG(amount), 2) AS s FROM orders", "SELECT SUM(amount) AS s FROM orders", "sqlite")
|
||||
assert not [i for i in other.items if "ROUND" in i.text], "取整的不是同一个表达式时不说成只差在取整"
|
||||
|
||||
|
||||
def test_dates_computed_from_today_are_not_equivalent_and_say_so():
|
||||
from app.data.sql_semantics import explain_sql_diff
|
||||
|
||||
|
||||
@@ -867,10 +867,10 @@ def test_columns_inside_a_cte_are_not_compared_because_canonical_forms_prune_the
|
||||
|
||||
|
||||
def test_differences_that_cannot_be_checked_get_only_the_fallback():
|
||||
"""两边算法不同、又判断不了是不是真的不同的(ROUND 包了一层、CASE 和 COALESCE、两种写法的时间常量),不说具体的:
|
||||
「A 算的是 X,B 算的是 Y」读起来就是两边不同,说错了比不说更糟。"""
|
||||
for a, b in (("SELECT SUM(amount) FROM orders", "SELECT ROUND(SUM(amount), 2) FROM orders"),
|
||||
("SELECT SUM(COALESCE(discount, 0)) FROM orders",
|
||||
"""两边算法不同、又判断不了是不是真的不同的(CASE 和 COALESCE、两种写法的时间常量),不说具体的:
|
||||
「A 算的是 X,B 算的是 Y」读起来就是两边不同,说错了比不说更糟。ROUND 包了一层的另有一句只陈述写法、不断言值不同的
|
||||
说明(_Explainer._rounded,见 test_ledger_cells 的 ROUND 那条)。"""
|
||||
for a, b in (("SELECT SUM(COALESCE(discount, 0)) FROM orders",
|
||||
"SELECT SUM(CASE WHEN discount IS NULL THEN 0 ELSE discount END) FROM orders"),
|
||||
("SELECT COUNT(*) FROM orders WHERE paid_at >= '2026-09-01'",
|
||||
"SELECT COUNT(*) FROM orders WHERE paid_at >= DATE('2026-09-01')")):
|
||||
|
||||
Reference in New Issue
Block a user