Problem z rzutowaniem liczb całkowitych na pułap (dziesiętny)

10

Mam taki scenariusz, wygląda na to, że MySQL pobiera największą wartość dziesiętną i próbuje dorzucić inne wartości.

Problem polega na tym, że to zapytanie jest generowane przez bibliotekę zewnętrzną, więc nie mam kontroli nad tym kodem, przynajmniej na tym poziomie. Czy masz jakiś pomysł, jak to naprawić?

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

Spodziewany wynik

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

Dodając więcej kontekstu, używam Entity Framework 6 z biblioteką rozszerzeń http://entityframework-extensions.net/ do zapisywania zmian w partiach, a konkretnie metody kontekstu.BulkSaveChanges () ;, ta biblioteka tworzy zapytania używając „select union” .

ngcbassman
źródło
1
20 jakoś staje się 9.9 ?! To nie wydaje się właściwe.
dbdemon
2
MySQL 8.0 na DBFiddle pokazuje ten sam nonsens: dbfiddle.uk/…
dezso
Jeszcze bardziej mylące ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;działa dobrze
Evan Carroll
Pls publikują potrzebne dane wyjściowe. Ponadto, ile masz kontroli nad generowanym kodem: na przykład czy możesz zmienić typ 20 na 20,0 lub typ 2,2 na 2?
Qsigma
Dodałem teraz więcej kontekstu, jednym z możliwych rozwiązań w moim przypadku jest wymuszenie wartości do 20.0 w kodzie, przetestowałem i naprawiłem problem, ale wygląda to na błąd, ponieważ zdarza się tylko w konkretnym scenariuszu, w którym występuje wartość null i to zamówienie.
ngcbassman

Odpowiedzi:

9

Wygląda mi to na błąd i mogę potwierdzić to zagadkowe zachowanie w:

10.2.14-MariaDB

Jeśli to możliwe, możesz rzucić liczbę całkowitą na podwójną:

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

lub upewnij się, że najpierw masz podwójną wartość:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Dalsze spostrzeżenia po przeczytaniu komentarzy w odpowiedzi @Evan Carroll

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

Ok, użycie wartości int nie wydaje się powodować błędu.

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

BŁĄD: Wydaje się, że dane wyjściowe są dziesiętne (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    |       |
+-------+--------------+------+-----+---------+-------+

Błąd nie jest izolowany dla interfejsu wiersza poleceń, istnieje również dla 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'),)

Co dziwne, błąd znika, jeśli null zostanie przesunięty pierwszy lub ostatni:

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

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

Jeśli null zostanie umieszczony jako pierwszy, wynikowy typ jest dziesiętny (20,1). Jeśli zostanie wstawiona wartość null, ostatni wynikowy typ jest dziesiętny (3,1)

Błąd znika również, jeśli do związku zostanie dodana kolejna noga:

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

wynikowy typ dziesiętny (20,1)

dodanie kolejnego pustego miejsca w środku zachowuje błąd:

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

Ale dodanie null na początku naprawia to:

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

Zgodnie z oczekiwaniami działa rzutowanie pierwszej wartości na dziesiętną (3,1).

Wreszcie jawne rzutowanie na dziesiętne (2,1) powoduje ten sam błąd, ale z ostrzeżeniem:

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
źródło
1
CAST to DOUBLE to błąd składniowy w MySQL. Zamiast tego działa dziesiętnie:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma
4
Zezwoliłem
dbdemon
1
Wydaje się, że problem został rozwiązany w MariaDB 10.3. (Właśnie przetestowałem z 10.3.6, a 10.3.1 również powinno działać.)
dbdemon 24.04.2018
2
Co ciekawe, nie ma problemu, jeśli podasz 20as 020. Zachowanie jest takie samo w MariaDB 10.2 i MySQL 8.0 . Wygląda bardzo podobnie, jak długość literału wpływa na typ połączonej kolumny. W każdym razie jest to zdecydowanie błąd w mojej książce.
Andriy M.
1
Widzę problem w MySQL 8.0 nawet bez wartości null (chociaż działa to dobrze w MariaDB 10.2). Istnieją również różnice w wielkości kolumny, jeśli podasz wiodące zero lub obliczenia
Mick O'Hea
5

Błąd MDEV-15999

Zgłoszono ten błąd MDEV-15999 zgłoszony przez dbdemon . Od tego czasu został naprawiony w 10.3.1.

Dziwna natura MySQL / MariaDB

Z dokumentów

Nazwy kolumn z pierwszej SELECTinstrukcji są używane jako nazwy kolumn dla zwróconych wyników. Wybrane kolumny wymienione w odpowiednich pozycjach każdej SELECTinstrukcji powinny mieć ten sam typ danych. (Na przykład pierwsza kolumna wybrana przez pierwszą instrukcję powinna mieć ten sam typ, co pierwsza kolumna wybrana przez inne instrukcje).

Jeśli typy danych odpowiadających SELECTkolumn nie są zgodne, typy i długości kolumn w UNIONwyniku uwzględniają wartości pobrane przez wszystkie SELECTinstrukcje.

W tym przypadku uzgadniają decimali integerpromując liczbę całkowitą do wartości, decimalktóra nie może jej zawierać. Wiem, że to okropne, ale równie okropne jest to, że tak cicho się zachowuje.

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

Co wydaje się torować drogę dla tego problemu.

Evan Carroll
źródło
SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;daje ten sam (9.9) zły wynik. Ale jeśli użyjemy „nieprzygotowanego”, wszystko pójdzie dobrze. Idź rysunek ...
ypercubeᵀᴹ
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;działa również poprawnie, podobnie jakSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M
3
Kluczowym spostrzeżeniem jest to, że MySQL wybrał typ danych, który może pomieścić, 2.2ale jest zbyt wąski, aby go pomieścić 20. Można to zobaczyć, zmieniając ostatnią klauzulę select na CAST(2.2 AS DECIMAL(10,2)), która daje 20.0jako pierwszy wiersz (domyślnie uruchomiony CAST(20 AS DECIMAL(10,2))).
IMSoP
1
Dziwne jest to, że nie opiera się to tylko na typie danych 2.2. Jeśli spróbujesz select 20000 union select null union select 2.22dostać 9999.99, w decimal(6,2). Jest zawsze o jedną cyfrę za krótka, aby pomieścić wartość
Mick O'Hea
1
@ Mick tak. Jeśli wrzucisz NULLwartość na środku, oblicza precyzję 1 krótką.
Salman