Comment puis-je faire une comparaison de chaînes sensible à la casse SQL sur MySQL?

285

J'ai une fonction qui renvoie cinq caractères avec une casse mixte. Si je fais une requête sur cette chaîne, elle retournera la valeur indépendamment de la casse.

Comment puis-je rendre les requêtes de chaînes MySQL sensibles à la casse?

StevenB
la source
8
Notez que BINARY n'est pas la même chose que la comparaison sensible à la casse: sélectionnez 'à' comme 'a' // renvoie vrai sélectionnez 'à' comme BINARY 'a' // retourne faux !!! sélectionnez 'à' comme 'a' COLLATE latin1_general_cs // renvoie true Ainsi, la suggestion d'utiliser BINARY pour la comparaison sensible à la casse est incorrecte.
cquezel
3
@cquezel: Donc, vous dites que [sélectionnez 'à' comme BINARY 'a'] devrait retourner vrai ?? Dans tous les cas, qu'est-ce que cela a à voir avec les comparaisons sensibles à la casse?
Francisco Zarabozo
3
@FranciscoZarabozo certaines personnes ci-dessous ont suggéré d'utiliser la comparaison BINARY pour effectuer une comparaison sensible à la casse. Je souligne simplement que dans d'autres langues, cela ne fonctionnera probablement pas comme prévu car BINARY n'est pas identique à la casse.
cquezel
3
@cquezel Je pense que «à» est une lettre différente de «a». La comparaison entre les deux devrait donc être fausse en tout état de cause.
Stéphane

Réponses:

159

http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

Le jeu de caractères et le classement par défaut sont latin1 et latin1_swedish_ci, donc les comparaisons de chaînes non binaires ne respectent pas la casse par défaut. Cela signifie que si vous recherchez avec col_name LIKE 'a%', vous obtenez toutes les valeurs de colonne qui commencent par A ou a. Pour rendre cette recherche sensible à la casse, assurez-vous que l'un des opérandes a un classement sensible à la casse ou binaire. Par exemple, si vous comparez une colonne et une chaîne qui ont toutes deux le jeu de caractères latin1, vous pouvez utiliser l'opérateur COLLATE pour que l'un ou l'autre opérande ait le classement latin1_general_cs ou latin1_bin:

col_name COLLATE latin1_general_cs LIKE 'a%'
col_name LIKE 'a%' COLLATE latin1_general_cs
col_name COLLATE latin1_bin LIKE 'a%'
col_name LIKE 'a%' COLLATE latin1_bin

Si vous souhaitez qu'une colonne soit toujours traitée de manière sensible à la casse, déclarez-la avec un classement sensible à la casse ou binaire.

peiner
la source
4
une astuce sur la façon de le faire dans phpmyadmin?
StevenB
4
@StevenB: cliquez sur le bouton Modifier de la colonne, puis définissez le classement
drudge
32
@BT Pour rendre la colonne utf8 sensible à la casse, vous pouvez utiliser la colation bin comme:SELECT 'email' COLLATE utf8_bin = 'Email'
piotrekkr
@drudge Comment déclareriez-vous une colonne avec un classement sensible à la casse?
Stéphane
1
@StephaneEybert si vous recherchez une sensibilité à la casse directe, j'ai eu de la chance en utilisant varbinary au lieu de varchar pour un champ dans la table ut8. HTH
Andrew T
726

La bonne nouvelle est que si vous avez besoin de faire une requête sensible à la casse, c'est très simple:

SELECT *  FROM `table` WHERE BINARY `column` = 'value'
Craig White
la source
34
Ceci est exactement ce que je cherchais. Je le ferais plus haut si je le pouvais. Une question cependant, quel effet cela a-t-il sur les performances? Je l'utilise sur un rapport limité, donc ce n'est pas important dans mon cas, mais je suis curieux.
adjwilli
23
Pourquoi n'est-ce pas la réponse? C'est exactement ce dont j'avais besoin aussi.
Art Geigel
7
@adjwilli Si la colonne faisait partie d'un index, vous subirez un impact sur les performances des requêtes qui dépendent de cet index. Pour maintenir les performances, vous devez réellement modifier la table.
dshin
6
Qu'est-ce que cela fera pour les chaînes UTF-8 contenant le même caractère avec une représentation différente, par exemple en utilisant un caractère de combinaison pour ajouter un tréma? Ces chaînes UTF-8 pourraient être traitées comme égales: convert(char(0x65,0xcc,0x88) using utf8)(c'est- eà- dire avec ¨ajouté) et convert(char(0xc3,0xab) using utf8)(c'est-à-dire ë), mais l'ajout BINARYles rendra inégales.
mvds
3
À titre d'exemple de performances: ma requête passe de 3,5 ms (négligeable) à 1,570 ms (environ une seconde et demie), interrogeant une table avec 1,8 M de lignes environ.
Lluís Suñol
64

Réponse publiée par Craig White, a une grosse pénalité de performance

SELECT *  FROM `table` WHERE BINARY `column` = 'value'

car il n'utilise pas d'index. Donc, soit vous devez modifier le classement du tableau comme mentionné ici https://dev.mysql.com/doc/refman/5.7/en/case-sensitivity.html .

OU

Solution la plus simple, vous devez utiliser un BINAIRE de valeur.

SELECT *  FROM `table` WHERE `column` = BINARY 'value'

Par exemple.

mysql> EXPLAIN SELECT * FROM temp1 WHERE BINARY col1 = "ABC" AND col2 = "DEF" ;
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | temp1  | ALL  | NULL          | NULL | NULL    | NULL | 190543 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

CONTRE

mysql> EXPLAIN SELECT * FROM temp1 WHERE col1 = BINARY "ABC" AND col2 = "DEF" ;
+----+-------------+-------+-------+---------------+---------------+---------+------+------+------------------------------------+
| id | select_type | table | type  | possible_keys | key           | key_len | ref  | rows | Extra                              |
+----+-------------+-------+-------+---------------+---------------+---------+------+------+------------------------------------+
|  1 | SIMPLE      | temp1 | range | col1_2e9e898e | col1_2e9e898e | 93      | NULL |    2 | Using index condition; Using where |
+----+-------------+-------+-------+---------------+---------------+---------+------+------+------------------------------------+
enter code here

1 rangée en jeu (0,00 sec)

Nitesh
la source
Cela ne semble pas être sensible à la casse sur 10.3.22-MariaDB (en utilisant libmysql - 5.6.43)
user10398534
40

Au lieu d'utiliser l'opérateur =, vous pouvez utiliser LIKE ou LIKE BINARY

// this returns 1 (true)
select 'A' like 'a'

// this returns 0 (false)
select 'A' like binary 'a'


select * from user where username like binary 'a'

Il prendra "A" et non "A" dans son état

insoftservice
la source
Cela ne semble pas être sensible à la casse sur 10.3.22-MariaDB (en utilisant libmysql - 5.6.43)
user10398534
17

Pour utiliser un index avant d'utiliser BINARY, vous pouvez faire quelque chose comme ceci si vous avez de grandes tables.

SELECT
   *
FROM
   (SELECT * FROM `table` WHERE `column` = 'value') as firstresult
WHERE
   BINARY `column` = 'value'

La sous-requête entraînerait un très petit sous-ensemble insensible à la casse dont vous sélectionnez ensuite la seule correspondance sensible à la casse.

Eric
la source
Il vaut la peine de commenter pour dire que ce qui précède n'aidera qu'en fonction de vos données - votre recherche insensible à la casse pourrait potentiellement renvoyer un sous-ensemble assez important de données.
BrynJ
15

La façon la plus correcte d'effectuer une comparaison de chaînes sensible à la casse sans modifier le classement de la colonne interrogée consiste à spécifier explicitement un jeu de caractères et un classement pour la valeur à laquelle la colonne est comparée.

select * from `table` where `column` = convert('value' using utf8mb4) collate utf8mb4_bin;

Pourquoi ne pas utiliser binary?

L'utilisation de l' binaryopérateur est déconseillée car elle compare les octets réels des chaînes codées. Si vous comparez les octets réels de deux chaînes codées en utilisant les différents jeux de caractères deux chaînes qui doivent être considérées comme identiques, elles peuvent ne pas être égales. Par exemple, si vous avez une colonne qui utilise le latin1jeu de caractères et que votre jeu de caractères de serveur / session l'est utf8mb4, alors lorsque vous comparez la colonne avec une chaîne contenant un accent tel que «café», elle ne correspondra pas aux lignes contenant la même chaîne! En effet , dans latin1é est codé comme l'octet 0xE9mais utf8il est deux octets: 0xC3A9.

Pourquoi l'utiliser convertaussi bien collate?

Les classements doivent correspondre au jeu de caractères. Donc, si votre serveur ou session est configuré pour utiliser le latin1jeu de caractères, vous devez utiliser, collate latin1_binmais si votre jeu de caractères est que utf8mb4vous devez utiliser collate utf8mb4_bin. Par conséquent, la solution la plus robuste consiste à toujours convertir la valeur dans le jeu de caractères le plus flexible et à utiliser le classement binaire pour ce jeu de caractères.

Pourquoi appliquer le convertet collateà la valeur et non à la colonne?

Lorsque vous appliquez une fonction de transformation à une colonne avant d'effectuer une comparaison, cela empêche le moteur de requête d'utiliser un index s'il en existe un pour la colonne, ce qui pourrait considérablement ralentir votre requête. Par conséquent, il est toujours préférable de transformer la valeur à la place lorsque cela est possible. Lorsqu'une comparaison est effectuée entre deux valeurs de chaîne et que l'une d'entre elles a un classement explicitement spécifié, le moteur de requête utilise le classement explicite, quelle que soit la valeur à laquelle il est appliqué.

Sensibilité d'accent

Il est important de noter que MySql est non seulement sensible à la casse pour les colonnes en utilisant un _ciassemblage (qui est généralement la valeur par défaut), mais aussi l' accent insensible. Cela veut dire que 'é' = 'e'. L'utilisation d'un classement binaire (ou de l' binaryopérateur) rendra les comparaisons de chaînes sensibles à l'accent ainsi qu'à la casse.

Qu'est-ce que c'est utf8mb4?

Le utf8jeu de caractères dans MySql est un alias utf8mb3qui a été déprécié dans les versions récentes car il ne prend pas en charge les caractères à 4 octets (ce qui est important pour coder des chaînes comme 🐈). Si vous souhaitez utiliser le codage de caractères UTF8 avec MySql, vous devez utiliser le utf8mb4jeu de caractères.

Paul Wheeler
la source
8

Ce qui suit est pour les versions de MySQL égales ou supérieures à 5.5.

Ajouter à /etc/mysql/my.cnf

  [mysqld]
  ...
  character-set-server=utf8
  collation-server=utf8_bin
  ...

Toutes les autres collations que j'ai essayées ne semblaient pas sensibles à la casse, seul "utf8_bin" fonctionnait.

N'oubliez pas de redémarrer mysql après ceci:

   sudo service mysql restart

Selon http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html, il existe également un "latin1_bin".

Le "utf8_general_cs" n'a pas été accepté par le démarrage de mysql. (J'ai lu "_cs" comme "sensible à la casse" - ???).

fritzthecat
la source
7

Vous pouvez utiliser BINARY pour respecter la casse comme celui-ci

select * from tb_app where BINARY android_package='com.Mtime';

malheureusement, ce sql ne peut pas utiliser l'index, vous subirez un impact sur les performances des requêtes dépendantes de cet index

mysql> explain select * from tb_app where BINARY android_package='com.Mtime';
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | tb_app | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1590351 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+

Heureusement, j'ai quelques astuces pour résoudre ce problème

mysql> explain select * from tb_app where android_package='com.Mtime' and BINARY android_package='com.Mtime';
+----+-------------+--------+------------+------+---------------------------+---------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table  | partitions | type | possible_keys             | key                       | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+--------+------------+------+---------------------------+---------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_app | NULL       | ref  | idx_android_pkg           | idx_android_pkg           | 771     | const |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+------+---------------------------+---------------------------+---------+-------+------+----------+-----------------------+  
xiezefan
la source
Cela ne semble pas être sensible à la casse sur 10.3.22-MariaDB (en utilisant libmysql - 5.6.43)
user10398534
2

Excellent!

Je partage avec vous le code d'une fonction qui compare les mots de passe:

SET pSignal =
(SELECT DECODE(r.usignal,'YOURSTRINGKEY') FROM rsw_uds r WHERE r.uname =
in_usdname AND r.uvige = 1);

SET pSuccess =(SELECT in_usdsignal LIKE BINARY pSignal);

IF pSuccess = 1 THEN
      /*Your code if match*/
ELSE
      /*Your code if don't match*/

END IF;
Victor Enrique
la source
Besoin d'ajouter declare pSuccess BINARY;au début
adinas
2

Pas besoin de changer quoi que ce soit au niveau de la base de données, il vous suffit de modifier les requêtes SQL, cela fonctionnera.

Exemple -

"SELECT * FROM <TABLE> where userId = '" + iv_userId + "' AND password = BINARY '" + iv_password + "'";

Le mot-clé binaire rendra la casse sensible.

Pappu Mehta
la source
1

mysql n'est pas sensible à la casse par défaut, essayez de changer le classement de langue en latin1_general_cs

ohmusama
la source