MySQL IS NULL / IS NOT NULL Comportement incorrect?


18

Veuillez regarder ce tableau:

mysql> desc s_p;

+-------------------------+------------------+------+-----+---------+----------------+    
| Field                   | Type             | Null | Key | Default | Extra          |
+-------------------------+------------------+------+-----+---------+----------------+
| id                      | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| s_pid                   | int(10) unsigned | YES  | MUL | NULL    |                |
| sm_id                   | int(10) unsigned | YES  | MUL | NULL    |                |
| m_id                    | int(10) unsigned | YES  |     | NULL    |                |
| created                 | datetime         | YES  |     | NULL    |                |
| s_date                  | datetime         | YES  |     | NULL    |                |
| estimated_date          | datetime         | YES  | MUL | NULL    |                |
+-------------------------+------------------+------+-----+---------+----------------+

Jetez maintenant un œil à ces requêtes:

mysql> select count(*) from s_p where estimated_date is null;
+----------+
| count(*) |
+----------+
|   190580 |
+----------+
1 row in set (0.05 sec)

mysql> select count(*) from s_p where estimated_date is not null;
+----------+
| count(*) |
+----------+
|    35640 |
+----------+
1 row in set (0.07 sec)

mysql> select count(*) from s_p;
+----------+
| count(*) |
+----------+
|  1524785 |
+----------+

Les chiffres ci-dessus ne correspondent pas. Selon ma compréhension:

Count with IS NULLet Count with IS NOT NULLdoivent être égaux à count lorsqu'ils sont interrogés sans clause where.

Une idée sur ce qui se passe ici?

================================================== =

Mise à jour du 17 février 2012

Depuis, j'ai constaté que beaucoup de gens se demandent quel type de valeurs a actuellement la date estimée. Voici la réponse:

mysql> select distinct date(estimated_date) from s_p;

+----------------------+
| date(estimated_date) |
+----------------------+
| NULL                 |
| 2012-02-17           |
| 2012-02-20           |
| 2012-02-21           |
| 2012-02-22           |
| 2012-02-23           |
| 2012-02-24           |
| 2012-02-27           |
| 2012-02-28           |
+----------------------+
9 rows in set (0.42 sec)

Comme vous pouvez le voir ci-dessus, la date_estimée a une valeur NULL ou une valeur datetime valide. Il n'y a pas de zéros ou de chaînes vides "".

Est-ce que cela (problème d'origine) peut se produire si l'index de la date estimée a des problèmes?

================================================== =

Mise à jour du 18 février 2012

Voici la sortie show create table:

 | s_p | CREATE TABLE `s_p` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `s_id` int(10) unsigned DEFAULT NULL,
  `sm_id` int(10) unsigned DEFAULT NULL,
  `m_id` int(10) unsigned DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `estimated_date` datetime DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `sm_id` (`sm_id`),
   KEY `estimated_date_index` (`estimated_date`) USING BTREE,
  ) ENGINE=InnoDB AUTO_INCREMENT=1602491 DEFAULT CHARSET=utf8 |

Encore une fois, je ne peux que soupçonner l'index de la date estimée ici.

De plus, la version du serveur mysql est 5.5.12.


3
À moins que la table ne soit alimentée avec de nouvelles lignes entre et pendant l'exécution des 3 requêtes, cela ne peut pas se produire!
ypercubeᵀᴹ

6
Êtes-vous certain de faire un select count(*)pas et non select count(estimated_date)? Ces deux résultats renverront des résultats différents car les valeurs NULL sont ignorées si c'est la seule chose que vous comptez.

6
Je ne sais pas si les éléments suivants fonctionneront dans MySQL, mais pouvez-vous essayer de SELECT COUNT(*),SUM(CASE WHEN estimated_date IS NULL THEN 1 ELSE 0 END),SUM(CASE WHEN estimated_date IS NOT NULL THEN 1 ELSE 0 END) from s_plancer : - qui devrait obtenir tous les décomptes en une seule fois.
Damien_The_Unbeliever

1
S'agit-il des requêtes exactes que vous exécutez?
gbn

4
De plus, s'il s'agit de MyISAM, pouvez-vous l'exécuter CHECK TABLE? Compte tenu de la manière extravagante compte complet de la ligne plus grande, je suppose un est DELETEallé quelque part fou.
Naltharial

Réponses:


6

Avez-vous des dates nulles? Les valeurs Datetime de 0000-00-00 00:00:00sont considérées par MySQL pour satisfaire simultanément is nullet is not null:

steve@steve@localhost > create temporary table _tmp (a datetime not null);
Query OK, 0 rows affected (0.02 sec)

steve@steve@localhost > insert into _tmp values ('');
Query OK, 1 row affected, 1 warning (0.00 sec)

Warning (Code 1264): Out of range value for column 'a' at row 1
steve@steve@localhost > select a from _tmp where a is null;
+---------------------+
| a                   |
+---------------------+
| 0000-00-00 00:00:00 |
+---------------------+
1 row in set (0.00 sec)

steve@steve@localhost > select a from _tmp where a is not null;
+---------------------+
| a                   |
+---------------------+
| 0000-00-00 00:00:00 |
+---------------------+
1 row in set (0.00 sec)

Voir: http://bugs.mysql.com/bug.php?id=940

Ceci est classé comme "pas un bug". Ils suggèrent une solution de contournement: utilisez le mode strict, qui convertira l'avertissement d'insertion en erreur.

Cela dit, cela ne peut à lui seul expliquer la variation sauvage des résultats que vous obtenez (la somme des chiffres is nullet is not nulldoit dépasser le nombre illimité) ...


Le bogue apparaît lorsque DATEou DATETIMEest défini comme NOT NULL. Dans la question ici, la colonne est définie comme nullable. Ce bogue est cependant une autre raison d'exécuter MySQL en mode strict uniquement.
ypercubeᵀᴹ

J'ai mis à jour le message d'origine pour afficher les valeurs actuelles dans la colonne estimation_date. Il n'a ni 0000-00-00 ni chaîne vide "".
user1213259

1
@yper ou une raison de choisir un SGBD différent ...
ErikE

1
@ErikE: Ce n'est parfois pas un choix. Et vous trouverez toujours des raisons de choisir un autre SGBD, quel que soit celui avec lequel vous travaillez.
ypercubeᵀᴹ

FYI ToadSQL affiche 0000-00-00 00:00:00 comme {null}, embrouillant encore les eaux! Quel cauchemard. FTR nous n'avons pas d'index sur notre colonne problème. C'est sur 5.6.15-log.
sming

3

@ypercube:

On m'a récemment demandé si je pensais que le bogue de régression "SELECT COUNT (DISTINCT) plante InnoDB lorsque l'opérande est dans la clé primaire ou l'index unique" pourrait être à l'origine de cela.

Voici ma réponse (à l'origine ici):

http://www.chriscalender.com/?p=315&cpage=1#comment-1460

Je ne pense pas que ce soit le même bug. Ce bogue concerne davantage le plantage et nécessite spécifiquement SELECT COUNT (DISTINCT), plus l'opérande WHERE se trouve dans la clé primaire ou l'index Unique.

Votre bogue / problème n'a pas le DISTINCT, il ne plante pas et l'index sur la colonne datetime n'est pas une clé primaire ni unique. Cependant, c'est un peu étrange, donc j'ai fait quelques recherches et j'ai rencontré ce bogue, qui semble plus susceptible d'être impliqué / lié:

http://bugs.mysql.com/bug.php?id=60105

En fait, il est désigné comme «pas un bug», mais il montre / décrit comment vous pouvez rencontrer un comportement étrange lorsque vous avez des dates / heures avec «0000-00-00» et en utilisant IS NULL et IS NOT NULL.

Je me demande si vous avez une de ces lignes «0000-00-00» qui pourrait affecter les comptes?

Notez que le développeur qui commente dans le rapport de bogue mentionne également cette page:

Si ce n'est pas le cas, je recommanderais certainement de mettre à niveau et d'essayer cela sur la dernière version 5.5, qui est 5.5.21 (au 22/02/2012), car cela fait 9 mois (et 9 versions) depuis 5.5.12 a été libéré.

Notez que vous devriez pouvoir vider la table (et les données) et l'importer dans une autre instance de test, juste pour la tester. De cette façon, vous n'affectez pas une machine de production et vous pouvez configurer une instance de test en quelques minutes.

Ensuite, si cela ne fait toujours pas de différence, vous seriez en mesure de tester d'autres éléments, tels que peut-être convertir la table en MyISAM pour voir si le problème est global ou spécifique à InnoDB.

Ou, j'ai remarqué que l'index de la "date_estimée" était:

KEY estimated_date_index( estimated_date) UTILISATION DE BTREE

Notez la «UTILISATION DE BTREE». Peut-être essayez-le sans UTILISER BTREE et voyez si vous voyez toujours le même comportement. (Ou supprimez complètement l'index juste pour tester .. tout cela aidera à réduire le problème).

J'espère que cela t'aides.


1

Essayez la requête

select * from s_p where estimated_date is null and estimated_date is not null limit 5;

Je ne pense pas que vous compreniez la question.

2
La requête ci-dessus montrerait les lignes qui se comportent mal à partir desquelles vous pouvez trouver la solution.

1
Si cette requête renvoie des lignes, je serais sérieusement inquiet pour l'intégrité de vos données.
Naltharial

@Naltharial Ce ne sont pas mes données, la question ci-dessus donne une sortie bizarre.

mysql> sélectionnez * dans s_p où la date_estimée est nulle et la date_estimée n'est pas une limite nulle 5; Ensemble vide (0,00 sec)
user1213259

1

Je vois quelque chose d'intéressant dans la disposition du tableau qui crie «Je n'ai pas envie de compter». Ce que je vais dire n'est qu'un pressentiment.

Vous avez exécuté cette requête avant

select distinct date(estimated_date) from s_p;

Exécutez-le en tant que COUNT / GROUP BY

select count(1) rowcount,date(estimated_date) from s_p group by date(estimated_date);

Vous devez obtenir le nombre définitif que vous recherchiez.

Pourtant, pourquoi les nombres pour NULL et NOT NULL seraient-ils calculés correctement? Encore une fois, ce n'est qu'une supposition éclairée.

Vous avez la colonne estimated_dateindexée. Voici ce que je veux que vous essayiez:

SHOW INDEX FROM s_p;
SHOW INDEX FROM s_p;
SHOW INDEX FROM s_p;
SHOW INDEX FROM s_p;

Ce n'est pas une faute de frappe. Je veux que tu cours SHOW INDEX FROM s_p;quatre (4) fois. Regardez la Cardinalitycolonne. Depuis la table s_pdans InnoDB, je m'attends à ce que la colonne Cardinalité soit différente à chaque fois. Pourquoi?

InnoDB obtient la valeur de cardinalité en l'estimant (NO PUN INTENDED) en comptant via les entrées de la page BTREE. Vérifiez votre variable système innodb_stats_on_metadata . Il doit être activé. S'il est déjà activé, désactivez-le et réexécutez vos requêtes d'origine pour voir si cela améliore les choses. FAITES CECI UNIQUEMENT COMME DERNIER RESORT !!!

Donc, au lieu de ces requêtes:

select count(*) from s_p where estimated_date is null;
select count(*) from s_p where estimated_date is not null;

Essayer

select count(estimated_date) from s_p;

Cela devrait vous donner le nombre de lignes avec une date estimée non nulle.

Une autre approche que vous voudrez peut-être expérimenter avec cette requête de force brute à l'aide de la fonction ISNULL :

select count(*) rowcount,isnull(estimated_date) IsItNull
from s_p group by isnull(estimated_date);

J'espère que ces suggestions aident !!!


-4

C'est attendu. Pour une colonne qui peut être annulée, 0 == NULL = "" et ainsi de suite. Ainsi, la première vérification renvoie en fait des lignes où aucune date n'a été définie ou son analogue à "0 / NULL"


2
0n'est jamais égal à NULL. La chaîne vide ( '') n'est pas la même que les NULLdeux, sauf si vous travaillez avec Oracle.
ypercubeᵀᴹ
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.