Problema com a união de número inteiro para o teto (decimal)

10

Eu tenho esse cenário, parece que o MySQL está usando o maior valor decimal e tenta converter os outros valores para isso.

O problema é que essa consulta é gerada por uma biblioteca externa, portanto, não tenho controle sobre esse código, pelo menos nesse nível. Você tem alguma idéia de como consertar isso?

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 |
+------+

Adicionando mais contexto, estou usando o Entity Framework 6 com uma biblioteca de extensões http://entityframework-extensions.net/ para salvar alterações em lotes, especificamente o método context.BulkSaveChanges () ;, essa biblioteca cria consultas usando "select union" .

ngcbassman
fonte
11
20 de alguma forma se torna 9,9 ?! Isso não parece certo.
dbdemon
2
MySQL 8.0 em DBFiddle mostra o mesmo absurdo: dbfiddle.uk/...
Dezso
Ainda mais confuso ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;funciona bem
Evan Carroll
Pls postar a saída que você precisa. Além disso, quanto controle você tem sobre o código gerado: por exemplo, você pode alterar o tipo de 20 para 20,0 ou o tipo de 2,2 para 2?
Qsigma
Adicionei mais contexto agora, uma solução possível no meu caso é forçar o valor para 20,0 no código, testei e corrijo o problema, mas parece um bug porque só acontece no cenário específico em que o nulo está envolvido e em essa ordem.
Ngcbassman

Respostas:

9

Parece um bug para mim e posso confirmar esse comportamento intrigante em:

10.2.14-MariaDB

Se possível, você pode converter o valor inteiro em um dobro:

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

ou verifique se você tem o valor duplo primeiro:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Outras observações depois de ler os comentários na resposta de @Evan Carroll

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

Ok, o uso de valores int parece não produzir o erro.

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

ERRO: Parece que a saída é 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    |       |
+-------+--------------+------+-----+---------+-------+

O erro não está isolado na interface da linha de comandos, existe também 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'),)

Curiosamente, o erro desaparece se o nulo for movido primeiro ou por ú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  |
+------+

Se null for colocado em primeiro lugar, o tipo resultante será decimal (20,1). Se nulo for colocado, o último tipo resultante será decimal (3,1)

O erro também desaparece se outra perna for adicionada à união:

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)

adicionar outro nulo no meio preserva o erro:

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

Mas adicionar um nulo no começo o corrige:

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

Como esperado, o lançamento do primeiro valor para decimal (3,1) funciona.

Por fim, converter explicitamente para decimal (2,1) produz o mesmo erro, mas com um aviso:

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
fonte
11
O CAST to DOUBLE é um erro de sintaxe no MySQL. Um decimal funciona:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma 24/04
4
Tomei a liberdade de relatar isso como um bug no MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
dbdemon
11
O problema parece ter sido corrigido no MariaDB 10.3. (Eu apenas testada com 10.3.6 e 10.3.1 também é suposto para trabalhar.)
dbdemon
2
Curiosamente, não há problema se você especificar o 20as 020. O comportamento é o mesmo no MariaDB 10.2 e no MySQL 8.0 . Parece muito com o comprimento do literal afeta o tipo da coluna combinada. De qualquer forma, esse é definitivamente um bug no meu livro.
Andriy M
11
Estou vendo o problema no MySQL 8.0, mesmo sem o nulo (embora isso funcione bem no MariaDB 10.2). Há também diferenças no tamanho da coluna, se você incluir um zero ou um cálculo
Mick O'Hea
5

Bug MDEV-15999

O bug MDEV-15999 registrado por dbdemon relatou isso. Desde então, foi corrigido no 10.3.1.

Natureza estranha do MySQL / MariaDB

Dos documentos,

Os nomes das colunas da primeira SELECTinstrução são usados ​​como os nomes das colunas para os resultados retornados. As colunas selecionadas listadas nas posições correspondentes de cada SELECTinstrução devem ter o mesmo tipo de dados. (Por exemplo, a primeira coluna selecionada pela primeira instrução deve ter o mesmo tipo que a primeira coluna selecionada pelas outras instruções.)

Se os tipos de dados das SELECTcolunas correspondentes não corresponderem, os tipos e comprimentos das colunas no UNIONresultado levarão em conta os valores recuperados por todas as SELECTinstruções.

Nesse caso, eles se reconciliam decimale integerpromovendo o número inteiro para um decimalque não pode contê-lo. Eu sei que isso é horrível, mas igualmente horrível é esse comportamento silencioso assim.

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

O que parece abrir o caminho para esse problema.

Evan Carroll
fonte
SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;produz o mesmo resultado (9.9) errado. Mas se usarmos "unisgned", tudo correrá bem. Vai entender ...
ypercubeᵀᴹ
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;funciona corretamente também, como fazSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M
3
A principal dica aqui é que o MySQL selecionou um tipo de dados que pode conter, 2.2mas é muito estreito para armazenar 20. Você pode ver isso alterando a última cláusula de seleção para CAST(2.2 AS DECIMAL(10,2)), que é 20.0a primeira linha (implicitamente em execução CAST(20 AS DECIMAL(10,2))).
IMSoP
11
O estranho é que não é apenas basear o tipo de dados no 2.2. Se você tentar select 20000 union select null union select 2.22que você começa 9999.99, em um decimal(6,2). É sempre um dígito muito curta para conter o valor
Mick O'Hea
11
@ Rick sim. Se você jogar o NULLvalor no meio, ele calculará a precisão 1 curta.
Salman Um