Vấn đề với số nguyên đúc liên kết lên trần (thập phân)


10

Tôi có kịch bản này, có vẻ như MySQL đang lấy giá trị thập phân lớn nhất và cố gắng truyền các giá trị khác sang đó.

Vấn đề là truy vấn này được tạo bởi một thư viện bên ngoài, vì vậy tôi ít nhất không kiểm soát được mã này, ở cấp độ này. Bạn có một số ý tưởng làm thế nào để khắc phục điều này?

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

Kết quả mong đợi

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

Thêm ngữ cảnh, tôi đang sử dụng Entity Framework 6 với thư viện mở rộng http://entityframework-extensions.net/ để lưu các thay đổi theo lô, cụ thể là bối cảnh phương thức.BulkSaveChanges ();, thư viện này tạo truy vấn bằng cách sử dụng "select union" .


1
20 bằng cách nào đó trở thành 9,9?! Điều đó có vẻ không đúng.
dbdemon

2
MySQL 8.0 trên DBFiddle hiển thị cùng một thứ vô nghĩa: dbfiddle.uk/ Kẻ
dezso

Thậm chí khó hiểu hơn ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;hoạt động tốt
Evan Carroll

Xin gửi đầu ra mà bạn cần. Ngoài ra, bạn có bao nhiêu quyền kiểm soát đối với mã được tạo: ví dụ: bạn có thể thay đổi loại 20 thành 20.0 hoặc loại 2.2 thành 2 không?
Qsigma

Bây giờ tôi đã thêm ngữ cảnh, một giải pháp khả thi trong trường hợp của tôi là buộc giá trị lên 20.0 trong mã, tôi đã kiểm tra và khắc phục sự cố, nhưng có vẻ như là một lỗi vì chỉ xảy ra trong kịch bản cụ thể có null và tham gia vào mệnh lệnh đó
ngcbassman

Câu trả lời:


9

Trông giống như một lỗi với tôi và tôi có thể xác nhận hành vi khó hiểu này trong:

10.2.14-MariaDB

Nếu có thể, bạn có thể truyền giá trị số nguyên thành gấp đôi:

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

hoặc đảm bảo bạn có giá trị gấp đôi trước:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Quan sát thêm sau khi đọc các bình luận trong câu trả lời của @Evan Carroll

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

Ok, sử dụng giá trị int dường như không tạo ra lỗi.

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

LRI: Có vẻ như đầu ra là số thập phân (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ỗi không bị cô lập với giao diện dòng lệnh, nó cũng tồn tại cho 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'),)

Điều kỳ lạ là lỗi sẽ biến mất nếu null được di chuyển trước hoặc cuối:

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

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

Nếu null được đặt đầu tiên, loại kết quả là thập phân (20,1). Nếu null được đặt loại kết quả cuối cùng là số thập phân (3,1)

Lỗi cũng biến mất nếu một chân khác được thêm vào liên minh:

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

kết quả kiểu thập phân (20,1)

thêm một null khác ở giữa sẽ bảo vệ lỗi:

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

Nhưng việc thêm null vào lúc đầu sẽ sửa nó:

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

Như dự kiến ​​đúc giá trị đầu tiên thành thập phân (3,1) hoạt động.

Cuối cùng, chuyển rõ ràng sang thập phân (2.1) tạo ra cùng một lỗi nhưng với một cảnh báo:

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 là một lỗi cú pháp trong MySQL. Thay vào đó, một số thập phân hoạt động:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma

4
Tôi đã tự do báo cáo đây là một lỗi trong MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
dbdemon

1
Sự cố dường như đã được khắc phục trong MariaDB 10.3. (Tôi vừa mới thử nghiệm với 10.3.6 và 10.3.1 cũng được cho là hoạt động.)
dbdemon

2
Thật kỳ lạ không có vấn đề gì nếu bạn chỉ định 20như 020. Hành vi tương tự trong MariaDB 10.2trong MySQL 8.0 . Trông rất giống chiều dài của chữ nghĩa là ảnh hưởng đến loại cột kết hợp. Trong mọi trường hợp, đây chắc chắn là một lỗi trong cuốn sách của tôi.
Andriy M

1
Tôi đang thấy vấn đề trong MySQL 8.0 ngay cả khi không có null (mặc dù nó hoạt động tốt trong MariaDB 10.2). Cũng có sự khác biệt về kích thước cột nếu bạn bao gồm số 0 đứng đầu hoặc phép tính
Mick O'Hea

5

Lỗi MDEV-15999

Bug MDEV-15.999 nộp bởi dbdemon báo cáo này. Nó đã được sửa trong 10.3.1.

Bản chất MySQL / MariaDB kỳ lạ

Từ các tài liệu,

Tên cột từ SELECTcâu lệnh đầu tiên được sử dụng làm tên cột cho kết quả trả về. Các cột được chọn được liệt kê ở các vị trí tương ứng của mỗi SELECTcâu lệnh phải có cùng kiểu dữ liệu. (Ví dụ: cột đầu tiên được chọn bởi câu lệnh đầu tiên phải có cùng loại với cột đầu tiên được chọn bởi các câu lệnh khác.)

Nếu các kiểu dữ liệu của SELECTcác cột tương ứng không khớp nhau, các loại và độ dài của các cột trong UNIONkết quả sẽ tính đến các giá trị được lấy bởi tất cả các SELECTcâu lệnh.

Trong trường hợp này, họ điều hòa decimalintegerbằng cách quảng bá số nguyên thành số decimalkhông thể chứa nó. Tôi biết điều đó thật kinh khủng, nhưng cũng đáng sợ không kém là điều này âm thầm hành xử như thế.

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

Mà dường như mở đường cho vấn đề này.


SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;tạo ra kết quả sai (9,9) giống nhau. Nhưng nếu chúng ta sử dụng "không nhận thức" thì tất cả đều ổn. Đi nào ...
ypercubeᵀᴹ

SELECT -20 UNION SELECT null UNION SELECT 2.2 ;hoạt động chính xác, cũng như vậySELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M

3
Cái nhìn sâu sắc quan trọng ở đây là MySQL đã chọn một loại dữ liệu có thể giữ 2.2nhưng quá hẹp để giữ 20. Bạn có thể thấy điều này bằng cách thay đổi cuối cùng chọn khoản để CAST(2.2 AS DECIMAL(10,2)), mang đến cho 20.0như dòng đầu tiên (ngầm chạy CAST(20 AS DECIMAL(10,2))).
IMSoP

1
Điều kỳ lạ là nó không chỉ dựa vào kiểu dữ liệu trên 2.2. Nếu bạn cố gắng select 20000 union select null union select 2.22bạn nhận được 9999.99, trong một decimal(6,2). Luôn luôn là một chữ số quá ngắn để giữ giá trị
Mick O'Hea

1
@ Xin vâng. Nếu bạn ném NULLgiá trị vào giữa, nó sẽ tính toán độ chính xác 1 ngắn.
Salman A
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.