Problema con la unión de un número entero al techo (decimal)

10

Tengo este escenario, parece que MySQL está tomando el valor decimal más grande e intenta convertir los otros valores a eso.

El problema es que esta consulta es generada por una biblioteca externa, por lo que no tengo control sobre este código, al menos en este nivel. ¿Tienes alguna idea de cómo solucionar esto?

SELECT 20 AS x
  UNION SELECT null
  UNION SELECT 2.2;
+------+
| x    |
+------+
|  9.9 | -- why from 20 to 9.9
| NULL |
|  2.2 |
+------+

Resultado Esperado

+------+
| x    |
+------+
|   20 | -- or 20.0, doesn't matter really in my case
| NULL |
|  2.2 |
+------+

Agregando más contexto, estoy usando Entity Framework 6 con una biblioteca de extensión http://entityframework-extensions.net/ para guardar los cambios en lotes, específicamente el método context.BulkSaveChanges () ;, esta biblioteca crea consultas usando "select union" .

ngcbassman
fuente
1
20 de alguna manera se convierte en 9.9 ?! Eso no parece correcto.
dbdemon
2
MySQL 8.0 en DBFiddle muestra el mismo sinsentido: dbfiddle.uk/…
dezso el
Aún más confuso ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;funciona bien
Evan Carroll
Pls publica la salida que necesita. Además, ¿cuánto control tiene sobre el código que se genera: por ejemplo, puede cambiar el tipo de 20 a 20.0, o el tipo de 2.2 a 2?
Qsigma
Agregué más contexto ahora, una posible solución en mi caso es forzar el valor a 20.0 en el código, probé y solucioné el problema, pero parece un error porque solo sucede en el escenario específico donde está involucrado el nulo y en ese orden
ngcbassman

Respuestas:

9

A mí me parece un error y puedo confirmar este comportamiento desconcertante en:

10.2.14-MariaDB

Si es posible, puede convertir el valor entero a un doble:

SELECT cast(20 as double) UNION SELECT null UNION SELECT 2.2;

o asegúrese de tener el valor doble primero:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Observaciones adicionales después de leer los comentarios en la respuesta de @Evan Carroll

select 20 union select null union select 2;
+------+
| 20   |
+------+
|   20 |
| NULL |
|    2 |
+------+

Ok, el uso de valores int no parece producir el error.

select 20 union select null union select 9.0;
+------+
| 20   |
+------+
| 9.9  |
| NULL |
| 9.0  |
+------+

ERROR: Parece que la salida es decimal (2,1)

create table tmp as select * from (select 20 as x 
                                   union 
                                   select null 
                                   union 
                                   select 9.0) as t

describe tmp;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| x     | decimal(2,1) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+

El error no está aislado de la interfaz de línea de comando, también existe para python2-mysql-1.3.12-1.fc27.x86_64:

>>> import MySQLdb
>>> db = MySQLdb.connect(host="localhost", user="*****", passwd="*****", db="test") 
>>> cur = db.cursor()
>>> cur.execute("SELECT 20 union select null union select 2.2")
3L
>>> for row in cur.fetchall() :
...     print row
... 
(Decimal('9.9'),)
(None,)
(Decimal('2.2'),)

Por extraño que parezca, el error desaparece si nulo se mueve primero o último:

select null union select 20 union select 9.0;
select 20 union select 9.0 union select null;

+------+
| NULL |
+------+
| NULL |
| 20.0 |
| 9.0  |
+------+

Si se coloca nulo primero, el tipo resultante es decimal (20,1). Si se coloca nulo, el último tipo resultante es decimal (3,1)

El error también desaparece si se agrega otro tramo a la unión:

select 20 union select 6 union select null union select 9.0;
+------+
| 20   |
+------+
| 20.0 |
| 6.0  |
| NULL |
| 9.0  |
+------+

tipo resultante decimal (20,1)

agregar otro nulo en el medio conserva el error:

select 20 union select null union select null union select 9.0;
+------+
| 20   |
+------+
| 9.9  |
| NULL |
| 9.0  |
+------+

Pero agregar un valor nulo al principio lo corrige:

select null union select 20 union select null union select null union select 9.0;
+------+
| NULL |
+------+
| NULL |
| 20.0 |
| 9.0  |
+------+

Como se esperaba, el primer valor de conversión a decimal (3,1) funciona.

Finalmente, la conversión explícita a decimal (2,1) produce el mismo error pero con una advertencia:

select cast(20 as decimal(2,1));
+--------------------------+
| cast(20 as decimal(2,1)) |
+--------------------------+
| 9.9                      |
+--------------------------+
1 row in set, 1 warning (0.00 sec)
Lennart
fuente
1
CAST to DOUBLE es un error de sintaxis en MySQL. Un decimal funciona en su lugar:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma
44
Me tomé la libertad de informar esto como un error en MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
dbdemon
1
El problema parece haberse solucionado en MariaDB 10.3. (Acabo de probar con 10.3.6, y también se supone que 10.3.1 funciona).
dbdemon
2
Curiosamente no hay problema si especificas el 20como 020. El comportamiento es el mismo en MariaDB 10.2 y en MySQL 8.0 . Parece que la longitud del literal afecta el tipo de columna combinada. En cualquier caso, este es definitivamente un error en mi libro.
Andriy M
1
Estoy viendo el problema en MySQL 8.0 incluso sin nulo (aunque eso funciona bien en MariaDB 10.2). También hay diferencias en el tamaño de la columna si incluye un cero inicial o un cálculo
Mick O'Hea
5

Error MDEV-15999

El error MDEV-15999 presentado por dbdemon informó esto. Desde entonces se ha solucionado en 10.3.1.

Naturaleza extraña de MySQL / MariaDB

De los documentos,

Los nombres de columna de la primera SELECTinstrucción se utilizan como nombres de columna para los resultados devueltos. Las columnas seleccionadas que figuran en las posiciones correspondientes de cada SELECTdeclaración deben tener el mismo tipo de datos. (Por ejemplo, la primera columna seleccionada por la primera declaración debe tener el mismo tipo que la primera columna seleccionada por las otras declaraciones).

Si los tipos de datos de las SELECTcolumnas correspondientes no coinciden, los tipos y longitudes de las columnas en el UNIONresultado tienen en cuenta los valores recuperados por todas las SELECTdeclaraciones.

En este caso, se reconcilian decimaly integerpromueven el número entero a un decimalque no puede contenerlo. Sé que es horrible, pero igualmente horrible es este comportamiento silencioso de esa manera.

SELECT CAST(20 AS decimal(2,1));
+--------------------------+
| CAST(20 AS decimal(2,1)) |
+--------------------------+
|                      9.9 |
+--------------------------+

Lo que parece allanar el camino para este problema.

Evan Carroll
fuente
SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;produce el mismo (9.9) resultado incorrecto. Pero si usamos "sin diseñar" allí todo va bien. Go figure ...
ypercubeᵀᴹ
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;funciona correctamente también, como lo haceSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M
3
La idea clave aquí es que MySQL ha seleccionado un tipo de datos que puede contener, 2.2pero es demasiado estrecho para mantener 20. Puede ver esto cambiando la última cláusula select a CAST(2.2 AS DECIMAL(10,2)), que aparece 20.0como la primera fila (en ejecución implícita CAST(20 AS DECIMAL(10,2))).
IMSoP
1
Lo extraño es que no solo basa el tipo de datos en 2.2. Si lo intentas select 20000 union select null union select 2.22, obtienes 9999.99, en a decimal(6,2). Siempre es un dígito demasiado corto para mantener el valor
Mick O'Hea
1
@Mick sí. Si introduce el NULLvalor en el medio, calcula la precisión 1 corta.
Salman A