Masalah dengan union casting integer to ceiling (desimal)


10

Saya memiliki skenario ini, sepertinya MySQL mengambil nilai desimal terbesar dan mencoba untuk memberikan nilai-nilai lainnya.

Masalahnya adalah bahwa kueri ini dihasilkan oleh perpustakaan eksternal, jadi saya tidak memiliki kendali atas kode ini, setidaknya pada tingkat ini. Apakah Anda punya ide bagaimana cara memperbaikinya?

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

Hasil yang diharapkan

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

Menambahkan lebih banyak konteks, saya menggunakan Entity Framework 6 dengan perpustakaan ekstensi http://entityframework-extensions.net/ untuk menyimpan perubahan dalam batch, khususnya konteks metode.BulkSaveChanges () ;, perpustakaan ini membuat kueri menggunakan "pilih serikat" .


1
20 entah bagaimana menjadi 9,9 ?! Sepertinya itu tidak benar.
dbdemon

2
MySQL 8.0 pada DBFiddle menunjukkan omong kosong yang sama: dbfiddle.uk/…
dezso

Yang lebih membingungkan ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;berfungsi dengan baik
Evan Carroll

Tolong posting output yang Anda butuhkan. Juga, berapa banyak kontrol yang Anda miliki atas kode yang dihasilkan: misalnya Anda dapat mengubah tipe 20 menjadi 20.0, atau tipe 2.2 ke 2?
Qsigma

Saya menambahkan lebih banyak konteks sekarang, salah satu solusi yang mungkin dalam kasus saya adalah memaksa nilai ke 20.0 dalam kode, saya menguji dan memperbaiki masalah, tetapi sepertinya bug karena hanya terjadi dalam skenario tertentu di mana null terlibat dan dalam pesanan itu.
ngcbassman

Jawaban:


9

Sepertinya bug bagi saya dan saya dapat mengonfirmasi perilaku membingungkan ini di:

10.2.14-MariaDB

Jika memungkinkan, Anda dapat melemparkan nilai integer ke ganda:

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

atau pastikan Anda memiliki nilai ganda terlebih dahulu:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Pengamatan selanjutnya setelah membaca komentar di jawaban @Evan Carroll

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

Ok, menggunakan nilai int sepertinya tidak menghasilkan kesalahan.

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

GALAT: Sepertinya output adalah desimal (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    |       |
+-------+--------------+------+-----+---------+-------+

Kesalahan tidak terisolasi ke antarmuka baris perintah, itu ada untuk python2-mysql-1.3.12-1.fc27.x86_64 juga:

>>> 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'),)

Anehnya kesalahan ini hilang jika nol dipindahkan pertama atau terakhir:

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

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

Jika nol ditempatkan terlebih dahulu, tipe yang dihasilkan adalah desimal (20,1). Jika null ditempatkan, jenis yang dihasilkan terakhir adalah desimal (3,1)

Kesalahan juga hilang jika kaki lain ditambahkan ke serikat:

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

jenis desimal yang dihasilkan (20,1)

menambahkan nol lain di tengah mempertahankan kesalahan:

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

Tetapi menambahkan nol di awal memperbaikinya:

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

Seperti yang diharapkan, casting nilai pertama ke desimal (3,1) berfungsi.

Akhirnya, secara eksplisit casting ke desimal (2,1) menghasilkan kesalahan yang sama tetapi dengan peringatan:

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
CAST to DOUBLE adalah kesalahan sintaksis di MySQL. Sebuah desimal bekerja sebagai gantinya:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma

4
Saya mengambil kebebasan melaporkan ini sebagai bug di MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
dbdemon

1
Masalahnya tampaknya telah diperbaiki di MariaDB 10.3. (Saya baru saja menguji dengan 10.3.6, dan 10.3.1 juga seharusnya berfungsi.)
dbdemon

2
Anehnya tidak ada masalah jika Anda menentukan 20sebagai 020. Perilaku ini sama di MariaDB 10.2 dan di MySQL 8.0 . Terlihat sangat mirip dengan panjang literal yang mempengaruhi tipe kolom gabungan. Bagaimanapun, ini pasti bug di buku saya.
Andriy M

1
Saya melihat masalah di MySQL 8.0 bahkan tanpa nol (meskipun berfungsi dengan baik di MariaDB 10.2). Ada juga perbedaan dalam ukuran kolom jika Anda menyertakan nol di depan atau perhitungan
Mick O'Hea

5

Bug MDEV-15999

Bug MDEV-15999 yang diajukan oleh dbdemon melaporkan ini. Sejak diperbaiki di 10.3.1.

Sifat MySQL / MariaDB yang aneh

Dari dokumen,

Nama kolom dari SELECTpernyataan pertama digunakan sebagai nama kolom untuk hasil yang dikembalikan. Kolom terpilih yang tercantum dalam posisi yang sesuai dari setiap SELECTpernyataan harus memiliki tipe data yang sama. (Misalnya, kolom pertama yang dipilih oleh pernyataan pertama harus memiliki jenis yang sama dengan kolom pertama yang dipilih oleh pernyataan lainnya.)

Jika tipe data SELECTkolom yang sesuai tidak cocok, jenis dan panjang kolom dalam UNIONhasil memperhitungkan nilai yang diambil oleh semua SELECTpernyataan.

Dalam hal ini, mereka melakukan rekonsiliasi decimaldan integerdengan mempromosikan integer ke decimalyang tidak dapat menampungnya. Saya tahu itu mengerikan, tetapi sama mengerikannya adalah ini diam-diam berperilaku seperti itu.

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

Yang sepertinya membuka jalan untuk masalah ini.


SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;menghasilkan hasil yang salah (9,9) yang sama. Tetapi jika kita menggunakan "unisgned" di sana semuanya berjalan dengan baik. Go figure ...
ypercubeᵀᴹ

SELECT -20 UNION SELECT null UNION SELECT 2.2 ;bekerja dengan benar juga, seperti halnyaSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M

3
Wawasan utama di sini adalah bahwa MySQL telah memilih tipe data yang dapat menampung 2.2tetapi terlalu sempit untuk dipegang 20. Anda dapat melihat ini dengan mengubah klausa pilih terakhir menjadi CAST(2.2 AS DECIMAL(10,2)), yang memberikan 20.0sebagai baris pertama (berjalan secara implisit CAST(20 AS DECIMAL(10,2))).
IMSoP

1
Yang aneh adalah bukan hanya mendasarkan tipe data pada 2.2. Jika Anda mencoba , select 20000 union select null union select 2.22dapatkan 9999.99di decimal(6,2). Selalu satu digit terlalu pendek untuk menampung nilainya
Mick O'Hea

1
@Mick yeah. Jika Anda melempar NULLnilai di tengah, ia menghitung pendek presisi 1.
Salman A
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.