diff --git a/ibis/backends/sql/dialects.py b/ibis/backends/sql/dialects.py index 41e416c9a001..be8be5ac6a39 100644 --- a/ibis/backends/sql/dialects.py +++ b/ibis/backends/sql/dialects.py @@ -360,7 +360,7 @@ class MSSQL(TSQL): class Generator(TSQL.Generator): TRANSFORMS = TSQL.Generator.TRANSFORMS.copy() | { sge.ApproxDistinct: rename_func("approx_count_distinct"), - sge.Stddev: rename_func("stdevp"), + sge.Stddev: rename_func("stdev"), sge.StddevPop: rename_func("stdevp"), sge.StddevSamp: rename_func("stdev"), sge.Variance: rename_func("var"), diff --git a/ibis/backends/sql/tests/test_compiler.py b/ibis/backends/sql/tests/test_compiler.py index 3596a7287b8c..f3966ced8517 100644 --- a/ibis/backends/sql/tests/test_compiler.py +++ b/ibis/backends/sql/tests/test_compiler.py @@ -4,7 +4,7 @@ import ibis from ibis import _ -from ibis.backends.sql.dialects import Trino +from ibis.backends.sql.dialects import MSSQL, Trino def test_window_with_row_number_compiles(): @@ -26,3 +26,24 @@ def test_transpile_join(): "SELECT * FROM t1 JOIN t2 ON x = y", read="duckdb", write=Trino ) assert "CROSS JOIN" not in result + + +def test_mssql_stdev_roundtrips_as_sample(): + # GH #12057: sqlglot parses T-SQL STDEV (sample) into sge.Stddev. The MSSQL + # dialect used to render that node as STDEVP (population), so a same- + # dialect round trip through Table.sql() / add_query_to_expr silently + # changed the statistic. Bare Stddev must render as STDEV; StddevPop + # already covers STDEVP. + (result,) = sg.transpile( + "SELECT STDEV([x]) AS [s] FROM [t]", read=MSSQL, write=MSSQL + ) + assert result == "SELECT STDEV([x]) AS [s] FROM [t]" + + +def test_mssql_std_how_emits_distinct_functions(): + t = ibis.table({"x": "float"}, name="t") + sample_sql = ibis.to_sql(t.x.std(how="sample"), dialect="mssql") + pop_sql = ibis.to_sql(t.x.std(how="pop"), dialect="mssql") + assert "STDEV(" in sample_sql + assert "STDEVP(" not in sample_sql + assert "STDEVP(" in pop_sql