TiDB changes numeric values when a heterogeneous UNION crosses a relational boundary
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
Please answer these questions before submitting your issue. Thanks!
TiDB returns different numeric values for a heterogeneous `UNION` depending
only on whether the `UNION` is executed directly or selected through a derived
table/CTE. In the flat form, a `DOUBLE` expression is rounded according to a
`FLOAT(M,D)` branch. Selecting the same `UNION` through a relational boundary
returns the full `DOUBLE` value instead.
The result metadata reported through the MySQL protocol is identical in both
forms, so this is a value-level wrong result rather than a client-side metadata
display difference.
This was independently exposed by VECT in `logic_bug.txt` as round 5 pair 286
and round 7 pair 83. Both candidates change transcendental-function results
such as `LN()` from a scale-limited value to a full-precision value when the
same `UNION` is placed behind a CTE or derived table.
### 1. Minimal reproduce step (Required)
Create a table whose type participates in `UNION` type resolution. The table
is deliberately empty in the first reproducer, so the only returned value is
`LN(45)`:
```sql
DROP DATABASE IF EXISTS tidb_union_numeric_repro;
CREATE DATABASE tidb_union_numeric_repro;
USE tidb_union_numeric_repro;
CREATE TABLE f(v FLOAT(8,2));
```
Run the flat form:
```sql
SELECT 1 AS v WHERE FALSE
UNION
SELECT LN(45)
UNION ALL
SELECT v FROM f WHERE FALSE;
```
Observed result:
```text
3.81
```
Now select the identical `UNION` through a derived table:
```sql
SELECT v
FROM (
SELECT 1 AS v WHERE FALSE
UNION
SELECT LN(45)
UNION ALL
SELECT v FROM f WHERE FALSE
) AS d;
```
Observed result:
```text
3.8066624897703196
```
The CTE form returns the same full-precision value as the derived-table form:
```sql
WITH c AS (
SELECT 1 AS v WHERE FALSE
UNION
SELECT LN(45)
UNION ALL
SELECT v FROM f WHERE FALSE
)
SELECT v FROM c;
```
Observed result:
```text
3.8066624897703196
```
In all three cases, the MySQL protocol metadata observed with PyMySQL was:
```text
type_code=5 (DOUBLE), internal_size=26, precision=26, scale=2
```
The discrepancy also occurs when the `FLOAT(8,2)` branch returns a row:
```sql
INSERT INTO f VALUES (12.34);
SELECT 1 AS v WHERE FALSE
UNION
SELECT LN(45)
UNION ALL
SELECT v FROM f;
```
Flat result:
```text
3.81
12.34
```
Derived-table result:
```sql
SELECT v
FROM (
SELECT 1 AS v WHERE FALSE
UNION
SELECT LN(45)
UNION ALL
SELECT v FROM f
) AS d;
```
```text
3.8066624897703196
12.34000015258789
```
### 2. What did you expect to see? (Required)
Adding a derived-table or CTE boundary around a query must not change its row
values. The flat, derived-table, and CTE forms should apply one consistent
numeric type-resolution and conversion policy and return the same values.
### 3. What did you see instead (Required)
### 4. What is your TiDB version? (Required)
```text
Release Version: v8.5.7
Edition: Community
Git Commit Hash: 202b7f47286a1109b5c957401d34c9358d130ae0
Git Branch: HEAD
UTC Build Time: 2026-07-15 02:06:00
GoVersion: go1.25.10
Race Enabled: false
Check Table Before Drop: false
Store: unistore
```
Contributor guide
Research direction
The report does not identify a source file or test entry point. Start by running the supplied SQL reproducer on TiDB v8.5.7 and compare the flat, derived-table, and CTE forms; done means all forms return consistent numeric values while preserving the reported metadata behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 48/100