Problème avec la conversion d'un entier en plafond (décimal)

10

J'ai ce scénario, il semble que MySQL prenne la plus grande valeur décimale et essaie de convertir les autres valeurs en cela.

Le problème est que cette requête est générée par une bibliothèque externe, donc je n'ai pas de contrôle sur ce code, à ce niveau au moins. Avez-vous une idée de comment résoudre ce problème?

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

résultat attendu

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

Ajoutant plus de contexte, j'utilise Entity Framework 6 avec une bibliothèque d'extensions http://entityframework-extensions.net/ pour enregistrer les modifications par lots, en particulier la méthode context.BulkSaveChanges () ;, cette bibliothèque crée des requêtes en utilisant "select union" .

ngcbassman
la source
1
20 devient en quelque sorte 9,9?! Cela ne semble pas correct.
dbdemon
2
MySQL 8.0 sur DBFiddle montre le même non-sens: dbfiddle.uk/…
dezso
Encore plus déroutant ... SELECT 20 UNION SELECT null UNION SELECT 40 UNION SELECT 4.3;fonctionne bien
Evan Carroll
Veuillez poster la sortie dont vous avez besoin. De plus, quel contrôle avez-vous sur le code généré: par exemple, pouvez-vous changer le type de 20 en 20,0 ou le type de 2,2 en 2?
Qsigma
J'ai ajouté plus de contexte maintenant, une solution possible dans mon cas est de forcer la valeur à 20,0 dans le code, j'ai testé et corrigé le problème, mais cela ressemble à un bogue car cela ne se produit que dans le scénario spécifique où le null est impliqué et dans cet ordre.
ngcbassman le

Réponses:

9

Cela ressemble à un bug pour moi et je peux confirmer ce comportement déroutant dans:

10.2.14-MariaDB

Si possible, vous pouvez convertir la valeur entière en double:

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

ou assurez-vous que vous avez d'abord la valeur double:

SELECT 2.2 UNION SELECT null UNION SELECT 22;

Observations supplémentaires après lecture des commentaires dans la réponse d'Evan Carroll

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

Ok, l'utilisation de valeurs int ne semble pas produire l'erreur.

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

ERREUR: Il semble que la sortie soit décimale (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'erreur n'est pas isolée de l'interface de ligne de commande, elle existe également pour 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'),)

Curieusement, l'erreur disparaît si null est déplacé en premier ou en dernier:

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

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

Si null est placé en premier, le type résultant est décimal (20,1). Si null est placé, le dernier type résultant est décimal (3,1)

L'erreur disparaît également si une autre jambe est ajoutée à l'union:

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

type décimal résultant (20,1)

l'ajout d'un autre null au milieu conserve l'erreur:

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

Mais l'ajout d'un null au début le corrige:

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

Comme prévu, la conversion de la première valeur en décimal (3,1) fonctionne.

Enfin, la conversion explicite en décimal (2,1) produit la même erreur mais avec un avertissement:

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
la source
1
CAST to DOUBLE est une erreur de syntaxe dans MySQL. Une décimale fonctionne à la place:SELECT CAST(20 AS DECIMAL) AS x UNION SELECT NULL UNION SELECT 2.2;
Qsigma
4
J'ai pris la liberté de signaler cela comme un bug dans MariaDB Jira: jira.mariadb.org/browse/MDEV-15999
dbdemon
1
Le problème semble avoir été résolu dans MariaDB 10.3. (Je viens de tester avec 10.3.6, et 10.3.1 est également censé fonctionner.)
dbdemon
2
Curieusement, il n'y a pas de problème si vous spécifiez le 20as 020. Le comportement est le même dans MariaDB 10.2 et dans MySQL 8.0 . Ressemble beaucoup à la longueur du littéral qui affecte le type de la colonne combinée. En tout cas, c'est définitivement un bug dans mon livre.
Andriy M
1
Je vois le problème dans MySQL 8.0 même sans null (bien que cela fonctionne bien dans MariaDB 10.2). Il y a aussi des différences dans la taille de la colonne si vous incluez un zéro non significatif ou un calcul
Mick O'Hea
5

Bogue MDEV-15999

Le bogue MDEV-15999 déposé par dbdemon l'a signalé. Il a depuis été corrigé dans 10.3.1.

Nature étrange de MySQL / MariaDB

Depuis les documents,

Les noms de colonne de la première SELECTinstruction sont utilisés comme noms de colonne pour les résultats renvoyés. Les colonnes sélectionnées répertoriées dans les positions correspondantes de chaque SELECTinstruction doivent avoir le même type de données. (Par exemple, la première colonne sélectionnée par la première instruction doit avoir le même type que la première colonne sélectionnée par les autres instructions.)

Si les types de données des SELECTcolonnes correspondantes ne correspondent pas, les types et les longueurs des colonnes dans le UNIONrésultat prennent en compte les valeurs récupérées par toutes les SELECTinstructions.

Dans ce cas, ils se réconcilient decimalet integeren promouvant l'entier à un decimalqui ne peut pas le contenir. Je sais que c'est horrible, mais tout aussi horrible est ce comportement silencieux comme ça.

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

Ce qui semble ouvrir la voie à ce problème.

Evan Carroll
la source
SELECT cast(20 as signed) UNION SELECT null UNION SELECT 2.2 ;produit le même (9,9) mauvais résultat. Mais si nous utilisons "unisgned", tout se passe bien. Allez comprendre ...
ypercubeᵀᴹ
SELECT -20 UNION SELECT null UNION SELECT 2.2 ;fonctionne correctement aussi, tout commeSELECT 20. UNION SELECT null UNION SELECT 2.2 ;
Andriy M
3
L'idée clé ici est que MySQL a sélectionné un type de données qui peut contenir 2.2mais est trop étroit pour être conservé 20. Vous pouvez le voir en remplaçant la dernière clause select par CAST(2.2 AS DECIMAL(10,2)), qui donne 20.0comme première ligne (en cours d'exécution implicite CAST(20 AS DECIMAL(10,2))).
IMSoP
1
Ce qui est étrange, ce n'est pas simplement de baser le type de données sur 2.2. Si vous essayez, select 20000 union select null union select 2.22vous obtenez 9999.99, dans un decimal(6,2). C'est toujours un chiffre trop court pour contenir la valeur
Mick O'Hea
1
@ Mick ouais. Si vous ajoutez la NULLvaleur au milieu, elle calcule la précision de 1 short.
Salman A