Problema con la fusione dei numeri interi sul soffitto (decimale)


10

Ho questo scenario, sembra che MySQL stia prendendo il valore decimale più grande e cerca di trasmettere gli altri valori a quello.

Il problema è che questa query è generata da una libreria esterna, quindi non ho il controllo su questo codice, almeno a questo livello. Hai idea di come risolvere questo problema?

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

Risultato atteso

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

Aggiungendo più contesto, sto usando Entity Framework 6 con una libreria di estensioni http://entityframework-extensions.net/ per salvare le modifiche in batch, in particolare il metodo context.BulkSaveChanges (); questa libreria crea query usando "seleziona unione" .


1
20 diventa in qualche modo 9,9 ?! Non sembra giusto.
dbdemon,

2
MySQL 8.0 su DBFiddle mostra le stesse sciocchezze: dbfiddle.uk/…
dezso

Ancora più confuso ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;funziona bene
Evan Carroll

Pls pubblica l'output di cui hai bisogno. Inoltre, quanto controllo hai sul codice generato: ad esempio puoi cambiare il tipo da 20 a 20.0 o il tipo da 2.2 a 2?
Qsigma,

Ho aggiunto più contesto ora, una possibile soluzione nel mio caso è forzare il valore a 20,0 nel codice, ho testato e risolto il problema, ma sembra un bug perché si verifica solo nello scenario specifico in cui è coinvolto il null e in quell'ordine.
ngcbassman,

Risposte:


9

Mi sembra un bug e posso confermare questo comportamento sconcertante in:

10.2.14-MariaDB

Se possibile, puoi trasmettere il valore intero in un doppio:

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

o assicurati di avere prima il doppio valore:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Ulteriori osservazioni dopo aver letto i commenti nella risposta di @Evan Carroll

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

Ok, l'utilizzo di valori int non sembra produrre l'errore.

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

ERRORE: sembra che l'output sia decimale (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    |       |
+-------+--------------+------+-----+---------+-------+

L'errore non è isolato nell'interfaccia della riga di comando, esiste anche per 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'),)

Stranamente, l'errore scompare se il valore null viene spostato per primo o per ultimo:

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 il valore null viene inserito per primo, il tipo risultante è decimale (20,1). Se viene inserito null l'ultimo tipo risultante è decimale (3,1)

L'errore scompare anche se viene aggiunta un'altra gamba all'unione:

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

risultante tipo decimale (20,1)

l'aggiunta di un altro null al centro preserva l'errore:

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

Ma l'aggiunta di un null all'inizio lo risolve:

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

Come previsto, il casting del primo valore in decimale (3,1) funziona.

Infine, il cast esplicito in decimale (2,1) produce lo stesso errore ma con un avviso:

select cast(20 as decimal(2,1));
+--------------------------+
| cast(20 as decimal(2,1)) |
+--------------------------+
| 9.9                      |
+--------------------------+
1 row in set, 1 warning (0.00 sec)

1
Il CAST su DOUBLE è un errore di sintassi in MySQL. Un decimale funziona invece:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma

4
Mi sono preso la libertà di segnalarlo come un bug nel Jira di MariaDB: jira.mariadb.org/browse/MDEV-15999
dbdemon

1
Il problema sembra essere stato risolto in MariaDB 10.3. (Ho appena provato con 10.3.6 e anche 10.3.1 dovrebbe funzionare.)
dbdemon

2
Curiosamente non ci sono problemi se si specifica 20as 020. Il comportamento è lo stesso in MariaDB 10.2 e in MySQL 8.0 . Sembra che la lunghezza del valore letterale influisca sul tipo di colonna combinata. In ogni caso, questo è sicuramente un bug nel mio libro.
Andriy M,

1
Sto vedendo il problema in MySQL 8.0 anche senza il null (anche se funziona bene in MariaDB 10.2). Ci sono anche differenze nella dimensione della colonna se includi uno zero iniziale o un calcolo
Mick O'Hea

5

Bug MDEV-15999

Bug MDEV-15999 presentato da dbdemon segnalato questo. Da allora è stato risolto in 10.3.1.

Strana natura MySQL / MariaDB

Dai documenti,

I nomi di colonna della prima SELECTistruzione vengono utilizzati come nomi di colonna per i risultati restituiti. Le colonne selezionate elencate nelle posizioni corrispondenti di ciascuna SELECTistruzione devono avere lo stesso tipo di dati. (Ad esempio, la prima colonna selezionata dalla prima istruzione dovrebbe avere lo stesso tipo della prima colonna selezionata dalle altre istruzioni.)

Se i tipi di dati delle SELECTcolonne corrispondenti non corrispondono, i tipi e le lunghezze delle colonne nel UNIONrisultato tengono conto dei valori recuperati da tutte le SELECTistruzioni.

In questo caso, si riconciliano decimale integerpromuovendo il numero intero in un decimalche non può contenerlo. So che è orribile, ma altrettanto orribile è che si comporta in silenzio in quel modo.

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

Il che sembra aprire la strada a questo problema.


SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;produce lo stesso (9,9) risultato errato. Ma se usiamo "unisgned" lì tutto va bene. Vai a capire ...
ypercubeᵀᴹ

SELECT -20 UNION SELECT null UNION SELECT 2.2 ;funziona anche correttamente, come faSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M

3
L'intuizione chiave qui è che MySQL ha selezionato un tipo di dati che può contenere 2.2ma è troppo stretto per essere conservato 20. Puoi vederlo cambiando l' ultima clausola di selezione in CAST(2.2 AS DECIMAL(10,2)), che fornisce 20.0come prima riga (implicitamente in esecuzione CAST(20 AS DECIMAL(10,2))).
IMSoP

1
La cosa strana è che non si basa solo sul tipo di dati 2.2. Se provi select 20000 union select null union select 2.22a ottenere 9999.99, in a decimal(6,2). È sempre una cifra troppo corta per contenere il valore
Mick O'Hea

1
@Mick sì. Se si NULLinserisce il valore nel mezzo, viene calcolata la precisione 1 in breve.
Salman A
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.