Problem mit Union Casting Ganzzahl zur Decke (dezimal)

10

Ich habe dieses Szenario, es sieht so aus, als würde MySQL den größten Dezimalwert annehmen und versuchen, die anderen Werte in diesen umzuwandeln.

Das Problem ist, dass diese Abfrage von einer externen Bibliothek generiert wird, sodass ich zumindest auf dieser Ebene keine Kontrolle über diesen Code habe. Haben Sie eine Idee, wie Sie das beheben können?

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

erwartetes Ergebnis

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

Wenn ich mehr Kontext hinzufüge , verwende ich Entity Framework 6 mit einer Erweiterungsbibliothek http://entityframework-extensions.net/ , um Änderungen in Stapeln zu speichern, insbesondere die Methode context.BulkSaveChanges (). Diese Bibliothek erstellt Abfragen mit "select union". .

ngcbassman
quelle
1
20 wird irgendwie 9.9?! Das scheint nicht richtig zu sein.
Dbdemon
2
MySQL 8.0 auf DBFiddle zeigt den gleichen Unsinn: dbfiddle.uk/…
dezso
Noch verwirrender ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;funktioniert gut
Evan Carroll
Bitte veröffentlichen Sie die Ausgabe, die Sie benötigen. Wie viel Kontrolle haben Sie auch über den generierten Code: Können Sie beispielsweise den Typ 20 bis 20.0 oder den Typ 2.2 bis 2 ändern?
Qsigma
Ich habe jetzt mehr Kontext hinzugefügt. Eine mögliche Lösung in meinem Fall besteht darin, den Wert im Code auf 20,0 zu erzwingen. Ich habe das Problem getestet und behoben, aber es sieht aus wie ein Fehler, da er nur in dem bestimmten Szenario auftritt, in dem die Null beteiligt ist und in dem diese Reihenfolge.
Ngcbassman

Antworten:

9

Sieht für mich wie ein Fehler aus und ich kann dieses rätselhafte Verhalten bestätigen in:

10.2.14-MariaDB

Wenn möglich, können Sie den ganzzahligen Wert in ein Double umwandeln:

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

oder stellen Sie sicher, dass Sie zuerst den doppelten Wert haben:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Weitere Beobachtungen nach dem Lesen der Kommentare in der Antwort von @Evan Carroll

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

Ok, die Verwendung von int-Werten scheint den Fehler nicht zu erzeugen.

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

FEHLER: Die Ausgabe scheint dezimal zu sein (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    |       |
+-------+--------------+------+-----+---------+-------+

Der Fehler ist nicht auf die Befehlszeilenschnittstelle beschränkt, sondern existiert auch für 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'),)

Seltsamerweise verschwindet der Fehler, wenn null zuerst oder zuletzt verschoben wird:

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

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

Wenn null an erster Stelle steht, ist der resultierende Typ dezimal (20,1). Wenn null gesetzt wird, ist der letzte resultierende Typ dezimal (3,1).

Der Fehler verschwindet auch, wenn der Vereinigung ein weiteres Bein hinzugefügt wird:

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

resultierende Typ Dezimalzahl (20,1)

Durch Hinzufügen einer weiteren Null in der Mitte bleibt der Fehler erhalten:

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

Das Hinzufügen einer Null am Anfang behebt das Problem:

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

Wie erwartet funktioniert das Umwandeln des ersten Werts in eine Dezimalzahl (3,1).

Schließlich führt das explizite Umwandeln in eine Dezimalzahl (2,1) denselben Fehler aus, jedoch mit einer Warnung:

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
quelle
1
CAST to DOUBLE ist ein Syntaxfehler in MySQL. Eine Dezimalstelle funktioniert stattdessen:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma
4
Ich habe mir
erlaubt,
1
Das Problem scheint in MariaDB 10.3 behoben worden zu sein. (Ich habe gerade mit 10.3.6 getestet, und 10.3.1 soll auch funktionieren.)
dbdemon
2
Seltsamerweise gibt es kein Problem, wenn Sie das 20als angeben 020. Das Verhalten ist in MariaDB 10.2 und in MySQL 8.0 dasselbe . Es sieht sehr nach der Länge des Literal aus, die sich auf den Typ der kombinierten Spalte auswirkt. Auf jeden Fall ist dies definitiv ein Fehler in meinem Buch.
Andriy M
1
Ich sehe das Problem in MySQL 8.0 auch ohne die Null (obwohl das in MariaDB 10.2 in Ordnung funktioniert). Es gibt auch Unterschiede in der Spaltengröße, wenn Sie eine führende Null oder eine Berechnung
einschließen
5

Fehler MDEV-15999

Bug MDEV-15999 durch eingereicht dbdemon berichtet dies. Es wurde seitdem in 10.3.1 behoben.

Seltsame MySQL / MariaDB-Natur

Aus den Dokumenten,

Die Spaltennamen aus der ersten SELECTAnweisung werden als Spaltennamen für die zurückgegebenen Ergebnisse verwendet. Ausgewählte Spalten, die an den entsprechenden Positionen jeder SELECTAnweisung aufgeführt sind, sollten denselben Datentyp haben. (Beispielsweise sollte die erste Spalte, die von der ersten Anweisung ausgewählt wurde, denselben Typ haben wie die erste Spalte, die von den anderen Anweisungen ausgewählt wurde.)

Wenn die Datentypen der entsprechenden SELECTSpalten nicht übereinstimmen, UNIONberücksichtigen die Typen und Längen der Spalten im Ergebnis die Werte, die von allen SELECTAnweisungen abgerufen wurden .

In diesem Fall versöhnen sie sich decimalund integerfördern die Ganzzahl zu einer Zahl, decimaldie sie nicht enthalten kann. Ich weiß, dass das schrecklich ist, aber genauso schrecklich ist, dass sich das so still verhält.

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

Das scheint den Weg für dieses Problem zu ebnen.

Evan Carroll
quelle
SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;erzeugt das gleiche (9.9) falsche Ergebnis. Aber wenn wir dort "unisgned" verwenden, geht alles gut. Go figure ...
ypercubeᵀᴹ
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;funktioniert auch richtig, genauso wieSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M
3
Die wichtigste Erkenntnis hier ist, dass MySQL einen Datentyp ausgewählt hat, der halten kann, 2.2aber zu eng ist , um ihn zu halten 20. Sie können dies sehen, indem Sie die letzte Auswahlklausel in ändern CAST(2.2 AS DECIMAL(10,2)), die 20.0als erste Zeile angegeben wird (implizit ausgeführt CAST(20 AS DECIMAL(10,2))).
IMSoP
1
Das Seltsame ist, dass der Datentyp nicht nur auf dem basiert 2.2. Wenn Sie versuchen select 20000 union select null union select 2.22, bekommen Sie 9999.99in einem decimal(6,2). Es ist immer eine Ziffer zu kurz, um den Wert zu halten
Mick O'Hea
1
@ Mick ja. Wenn Sie den NULLWert in die Mitte eingeben, wird die Genauigkeit 1 kurz berechnet.
Salman A