Проблема с целочисленным приведением к потолку (десятичному)

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» ,

ngcbassman
источник
1
20 как-то становится 9,9 ?! Это не кажется правильным.
dbdemon
2
MySQL 8.0 на DBFiddle показывает ту же ерунду: dbfiddle.uk/…
dezso
Еще более сбивает с толку ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;работает отлично
Эван Кэрролл
Просьба опубликовать вывод, что вам нужно. Кроме того, насколько вы контролируете сгенерированный код: например, можете ли вы изменить тип с 20 на 20,0 или тип с 2,2 на 2?
Qsigma
Теперь я добавил больше контекста, одно из возможных решений в моем случае - принудительно установить значение 20.0 в коде, я протестировал и исправил проблему, но это выглядит как ошибка, потому что это происходит только в конкретном сценарии, где задействовано значение NULL, и в этот порядок.
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;

Дальнейшие наблюдения после прочтения комментариев в ответе Эвана Кэрролла

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;
Qsigma
4
Я позволил себе сообщить об этом как об ошибке в MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
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

Ошибка MDEV-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) неправильный результат. Но если мы используем «без опрыскивания» там все идет хорошо. Пойди
разберись
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;работает правильно, как иSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Андрей М
3
Ключевым моментом здесь является то, что MySQL выбрал тип данных, который может содержать, 2.2но слишком узок для хранения 20. Вы можете увидеть это, изменив последнее предложение select на 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
@ Мик, да. Если вы добавите NULLзначение в середине, он вычислит точность 1 короткое.
Салман А