Проблема з об'єднанням цілих чисел до стелі (десятковий)


10

У мене такий сценарій, схоже, MySQL приймає найбільше десяткове значення і намагається передати інші значення цьому.

Проблема полягає в тому, що цей запит генерується зовнішньою бібліотекою, тому я не маю контролю над цим кодом, принаймні на цьому рівні. Чи маєте ви якесь уявлення, як це виправити?

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

Очікуваний результат

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

Додаючи більше контексту, я використовую Entity Framework 6 з бібліотекою розширень http://entityframework-extensions.net/ для збереження змін у партіях, зокрема методу context.BulkSaveChanges () ;, ця бібліотека створює запити, використовуючи "select union" .


1
20 якось стає 9,9 ?! Це не здається правильним.
dbdemon

2
MySQL 8.0 в DBFiddle показує ту саму нісенітницю: dbfiddle.uk/…
дез

Ще більш заплутано ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;працює чудово
Еван Керролл

Pls розмістити вихід, який вам потрібен. Крім того, скільки ви маєте контролю над згенерованим кодом: наприклад, чи можете ви змінити тип 20 на 20,0 або тип 2,2 на 2?
Qsigma

Зараз я додав більше контексту, одне можливе рішення в моєму випадку - примусити значення до коду до 20,0, я протестував і виправив проблему, але це виглядає як помилка, оскільки це відбувається лише в конкретному сценарії, коли нуль задіяний і в той наказ.
ngcbassman

Відповіді:


9

Мені це здається помилкою, і я можу підтвердити цю дивовижну поведінку в:

10.2.14-MariaDB

Якщо можливо, ви можете присвоїти ціле число до подвійного:

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

або переконайтеся, що у вас є подвійне значення спочатку:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Подальші спостереження після прочитання коментарів у відповіді @Evan Carroll

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

Гаразд, використання значень int, схоже, не призводить до помилок.

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

ПОМИЛКА: Видається, що вихід є десятковим (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    |       |
+-------+--------------+------+-----+---------+-------+

Помилка не відокремлена від інтерфейсу командного рядка, вона існує і для 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'),)

Як не дивно, помилка зникає, якщо нуль переміщено першим чи останнім:

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

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

Якщо нуль ставиться першим, результат отриманий десятково (20,1). Якщо нуль розміщено, останній результат є десятковим (3,1)

Помилка також зникає, якщо до союзу додається ще одна нога:

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

отриманий тип десяткової (20,1)

додавання ще одного нуля в середині зберігає помилку:

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

Але додавання нуля на початку виправляє це:

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

Як очікується, перше значення працює в десятковий (3,1).

Нарешті, явне введення в десятковий (2,1) видає ту ж помилку, але з попередженням:

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 - помилка синтаксису в MySQL. Натомість працює десятковий SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
знак

4
Я взяв на себе смішність
dbdemon

1
Здається, проблема виправлена ​​в MariaDB 10.3. (Я щойно тестував 10.3.6, і 10.3.1 також повинен працювати.)
dbdemon,

2
Цікаво, що проблем немає, якщо вказати 20як 020. Поведінка однакова у MariaDB 10.2 та в MySQL 8.0 . Схоже, що довжина прямокутника впливає на тип комбінованого стовпця. У будь-якому випадку це, безумовно, помилка в моїй книзі.
Андрій М

1
Я бачу проблему в MySQL 8.0 навіть без нуля (хоча це працює в MariaDB 10.2). Існують також відмінності у розмірі стовпців, якщо включити провідний нуль або розрахунок
Мік О'Хеа,

5

Баг МДЕВ-15999

Про це повідомила помилка MDEV-15999, подана dbdemon . З тих пір це було зафіксовано в 10.3.1.

Дивна природа MySQL / MariaDB

З документів,

Імена стовпців з першого SELECTтвердження використовуються як імена стовпців для повернутих результатів. Вибрані стовпці, перелічені у відповідних позиціях кожного SELECTтвердження, повинні мати однаковий тип даних. (Наприклад, перший стовпець, обраний першим оператором, повинен мати той самий тип, що і перший стовпець, обраний іншими операторами.)

Якщо типи даних відповідних SELECTстовпців не збігаються, типи та довжини стовпців у UNIONрезультаті враховують значення, отримані всіма SELECTоператорами.

У такому випадку вони узгоджуються decimalі integer, просуваючи ціле число до а, decimalяке не може містити його. Я знаю, що це жахливо, але однаково жахливо це мовчки поводиться так.

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

Що, здається, прокладає шлях цій проблемі.


SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;дає такий же (9,9) неправильний результат. Але якщо ми будемо використовувати "непризнані", там все йде добре. Переходьте до фігури ...
ypercubeᵀᴹ

SELECT -20 UNION SELECT null UNION SELECT 2.2 ;працює також правильно, як іSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Андрій М

3
Основне розуміння тут полягає в тому, що MySQL обрав тип даних, який може містити, 2.2але занадто вузький, щоб утримувати його 20. Це можна побачити, змінивши останній пункт вибору на CAST(2.2 AS DECIMAL(10,2)), який видається 20.0як перший рядок (неявно запущений CAST(20 AS DECIMAL(10,2))).
IMSoP

1
Дивна річ у тому, що це не просто базування типу даних на 2.2. Якщо ви спробуєте select 20000 union select null union select 2.22отримати 9999.99, в decimal(6,2). Це завжди одна цифра занадто коротка, щоб утримати значення
Мік О'Хеа,

1
@Mick так. Якщо ви кинете NULLзначення в середині, воно обчислює точність 1 короткий.
Салман А
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.