From 455793c43ee1d0ce8fb20daaa376c30a81fdc6c1 Mon Sep 17 00:00:00 2001 From: bhuvan Date: Tue, 13 Oct 2026 19:51:56 +0530 Subject: [PATCH] fix(teradata): transpile SAMPLE row counts and fractions [CLAUDE] Teradata's SAMPLE clause was always stored as a list of expressions, which other dialects can't generate, so it became an empty `TABLESAMPLE ()`. A single integer sample is now parsed as a row count (size) and a single fraction as a percentage, e.g. SAMPLE 0.25 -> Snowflake TABLESAMPLE (25). Multiple sample sets have no equivalent, so they're dropped with an unsupported warning. Closes #8669 Co-Authored-By: Claude Opus 5.5 --- sqlglot/generator.py | 4 ++++ sqlglot/generators/teradata.py | 12 +++++++++++- sqlglot/parsers/teradata.py | 15 +++++++++++++++ tests/dialects/test_teradata.py | 30 +++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/sqlglot/generator.py b/sqlglot/generator.py index dbaac49f58..e011278c76 100644 --- a/sqlglot/generator.py +++ b/sqlglot/generator.py @@ -2566,6 +2566,10 @@ def tablesample_sql( expression: exp.TableSample, tablesample_keyword: str | None = None, ) -> str: + if expression.expressions: + self.unsupported("Multiple sample sets are not supported") + return "" + method = self.sql(expression, "method") method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" numerator = self.sql(expression, "bucket_numerator") diff --git a/sqlglot/generators/teradata.py b/sqlglot/generators/teradata.py index 7f2375b810..02ce329557 100644 --- a/sqlglot/generators/teradata.py +++ b/sqlglot/generators/teradata.py @@ -1,6 +1,7 @@ from __future__ import annotations import typing as t +from decimal import Decimal from sqlglot import exp, generator, transforms from sqlglot.dialects.dialect import ( @@ -116,7 +117,16 @@ def tablesample_sql( expression: exp.TableSample, tablesample_keyword: str | None = None, ) -> str: - return f"{self.sql(expression, 'this')} SAMPLE {self.expressions(expression)}" + sample = expression.args.get("size") + percent = expression.args.get("percent") + if percent and percent.is_number: + fraction = Decimal(percent.to_py()).scaleb(-2) + # 100 percent keeps its decimal point, otherwise it'd be read back as a row count + sample = exp.Literal.number( + format(fraction.normalize() if fraction < 1 else fraction, "f") + ) + + return f" SAMPLE {self.sql(sample) or self.expressions(expression)}" def window_sql(self, expression: exp.Window) -> str: spec = expression.args.get("spec") diff --git a/sqlglot/parsers/teradata.py b/sqlglot/parsers/teradata.py index e65e888a7f..8713f6ba77 100644 --- a/sqlglot/parsers/teradata.py +++ b/sqlglot/parsers/teradata.py @@ -146,6 +146,21 @@ def _parse_update(self) -> exp.Update: ) ) + def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: + sample = super()._parse_table_sample(as_modifier=as_modifier) + + # A single integer sample is a row count, while a fraction is a proportion of rows + # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Manipulation-Language/SELECT-Statements/SAMPLE-Clause/SAMPLE-Clause-Syntax + if sample and len(sample.expressions) == 1 and sample.expressions[0].is_number: + num = sample.expressions[0].pop() + if num.is_int: + sample.set("size", num) + else: + percent = format(num.to_py().scaleb(2).normalize(), "f") + sample.set("percent", exp.Literal.number(percent)) + + return sample + def _parse_rangen(self): this = self._parse_id_var() self._match(TokenType.BETWEEN) diff --git a/tests/dialects/test_teradata.py b/tests/dialects/test_teradata.py index eeecc9b751..3894bfe09a 100644 --- a/tests/dialects/test_teradata.py +++ b/tests/dialects/test_teradata.py @@ -1,4 +1,4 @@ -from sqlglot import exp +from sqlglot import ErrorLevel, UnsupportedError, exp from tests.dialects.test_dialect import Validator @@ -20,6 +20,34 @@ def test_teradata(self): "SELECT * FROM tbl SAMPLE 0.33, .25, .1", "SELECT * FROM tbl SAMPLE 0.33, 0.25, 0.1", ) + self.validate_all( + "SELECT * FROM t SAMPLE 100", + write={ + "teradata": "SELECT * FROM t SAMPLE 100", + "snowflake": "SELECT * FROM t TABLESAMPLE (100 ROWS)", + }, + ) + self.validate_all( + "SELECT * FROM t SAMPLE 0.25", + read={ + "snowflake": "SELECT * FROM t TABLESAMPLE (25)", + }, + write={ + "teradata": "SELECT * FROM t SAMPLE 0.25", + "snowflake": "SELECT * FROM t TABLESAMPLE (25)", + }, + ) + self.validate_all( + "SELECT * FROM t SAMPLE 0.6, 0.25", + write={ + "teradata": "SELECT * FROM t SAMPLE 0.6, 0.25", + "snowflake": "SELECT * FROM t", + }, + ) + with self.assertRaises(UnsupportedError): + self.parse_one("SELECT * FROM t SAMPLE 10, 20").sql( + "snowflake", unsupported_level=ErrorLevel.RAISE + ) self.validate_all( "DATABASE tduser",