Dépannage de l'erreur «Mélange illégal de classements» dans mysql

211

J'obtiens l'erreur ci-dessous lorsque j'essaie de faire une sélection via une procédure stockée dans MySQL.

Mélange illégal de classements (latin1_general_cs, IMPLICIT) et (latin1_general_ci, IMPLICIT) pour l'opération '='

Une idée de ce qui pourrait mal se passer ici?

Le classement de la table est latin1_general_cicelui de la colonne dans la clause where latin1_general_cs.

user355562
la source
2
J'utilise diverses bases de données depuis de longues périodes (depuis 1990), et l'utilisation de la collation et de la coercibiité faite par NySQL apparaît comme "folle", les bases de données résolvent les problèmes imposant "UN" jeu de caractères pour la base de données, puis sont jusqu'à les procédures d'importation / exportation pour convertir de / vers le jeu de caractères unique utilisé par la base de données. Les solutions choisies par Mysql sont perturbantes, car elles mélangent les "problèmes d'application" (conversion de jeu de caractères) avec les problèmes de base de données (utilisation du classement). Pourquoi ne pas "supprimer" ces fonctionnalités stupides et encombrantes de la base de données afin qu'elles deviennent beaucoup plus utilisables et contrôlables par un
Maurizio Pievaioli

Réponses:

216

Cela est généralement dû à la comparaison de deux chaînes de classement incompatible ou à la tentative de sélection de données de classement différent dans une colonne combinée.

La clause COLLATEvous permet de spécifier le classement utilisé dans la requête.

Par exemple, la WHEREclause suivante donnera toujours l'erreur que vous avez publiée:

WHERE 'A' COLLATE latin1_general_ci = 'A' COLLATE latin1_general_cs

Votre solution consiste à spécifier un classement partagé pour les deux colonnes de la requête. Voici un exemple qui utilise la COLLATEclause:

SELECT * FROM table ORDER BY key COLLATE latin1_general_ci;

Une autre option consiste à utiliser l' BINARYopérateur:

BINARY str est le raccourci pour CAST (str AS BINARY).

Votre solution pourrait ressembler à ceci:

SELECT * FROM table WHERE BINARY a = BINARY b;

ou,

SELECT * FROM table ORDER BY BINARY a;
définit
la source
2
Merci. En fait, cela semble se comporter assez bizarrement dans mon cas. Lorsque j'exécute la requête telle quelle, via le navigateur de requêtes, il me récupère les résultats. Mais l'utilisation d'une procédure stockée génère une erreur.
user355562
5
Le binaire semblait être la meilleure solution pour moi. Cela pourrait également être le meilleur pour vous si vous n'utilisez aucun filtre délicat.
Adam F
J'ai le même problème, la façon dont je résous ce problème est de recréer depuis le début. j'ai essayé de changer le classement mais quand je me suis joint, j'ai quand même eu une erreur, j'ai donc essayé de cette façon. cmiiw
Bobby Z
Veuillez noter qu'il y a un bug dans MariaDB utilisant COLLATE latin1_general_ci qui provoque une autre erreur: COLLATION 'utf8_general_ci' is not valid for CHARACTER SET 'latin1''- même si vous n'avez pas de colonne avec CHARACTER SET 'latin1'! La solution consiste à utiliser le plâtre BINARY. Voir aussi cette question
Mel_T
154

TL; DR

Modifiez le classement de l'une (ou des deux) des chaînes afin qu'elles correspondent, ou bien ajoutez une COLLATEclause à votre expression.


  1. Quel est ce truc de "collation" de toute façon?

    Comme indiqué sous Jeux de caractères et classements en général :

    Un jeu de caractères est un ensemble de symboles et d'encodages. Un classement est un ensemble de règles permettant de comparer des caractères dans un jeu de caractères. Précisons la distinction avec un exemple de jeu de caractères imaginaire.

    Supposons que nous ayons un alphabet à quatre lettres: « A», « B», « a», « b». Nous attribuons un numéro à chaque lettre: " A" = 0, " B" = 1, " a" = 2, "b » = 3. La lettre « A» est un symbole, le nombre 0 est le codage de « A» et la combinaison de tous quatre lettres et leur encodage est un jeu de caractères .

    Supposons que nous voulons comparer deux valeurs de chaîne, "A " et " B". La façon la plus simple de le faire est de regarder les encodages: 0 pour " A" et 1 pour " B". Parce que 0 est inférieur à 1, nous disons que " A" est inférieur à " B". Ce que nous venons de faire, c'est d'appliquer un classement à notre jeu de caractères. Le classement est un ensemble de règles (une seule règle dans ce cas): «comparer les encodages». Nous appelons ce classement le plus simple de tous les classements possibles un classement binaire .

    Mais que se passe-t-il si nous voulons dire que les lettres minuscules et majuscules sont équivalentes? Nous aurions alors au moins deux règles: (1) traiter les lettres minuscules « a» et « b» comme équivalentes à « A» et « B»; (2) puis comparez les encodages. Nous appelons cela un classement insensible à la casse . C'est un peu plus complexe qu'un classement binaire.

    Dans la vraie vie, la plupart des jeux de caractères ont de nombreux caractères: pas seulement "A » et « B» mais des alphabets entiers, parfois plusieurs alphabets ou des systèmes d'écriture orientaux avec des milliers de caractères, ainsi que de nombreux symboles spéciaux et signes de ponctuation. Toujours dans la vie réelle, la plupart des classements ont de nombreuses règles, non seulement pour distinguer les majuscules, mais aussi pour distinguer les accents (un «accent» est une marque attachée à un caractère comme en allemand « Ö») et pour les caractères multiples. mappages (comme la règle « Ö» = « OE» dans l'un des deux classements allemands).

    D'autres exemples sont donnés sous Exemples d'effet de classement .

  2. D'accord, mais comment MySQL décide-t-il du classement à utiliser pour une expression donnée?

    Tel que documenté sous le classement d'expressions :

    Dans la grande majorité des déclarations, il est évident quel classement MySQL utilise pour résoudre une opération de comparaison. Par exemple, dans les cas suivants, il doit être clair que le classement est le classement de la colonnecharset_name :

    SELECT x FROM T ORDER BY x;
    SELECT x FROM T WHERE x = x;
    SELECT DISTINCT x FROM T;

    Cependant, avec plusieurs opérandes, il peut y avoir une ambiguïté. Par exemple:

    SELECT x FROM T WHERE x = 'Y';

    Si la comparaison utilise le classement de la colonne xou du littéral de chaîne'Y' ? Les deux xet 'Y'ont des collations, alors quelle collation a la priorité?

    SQL standard résout ces questions en utilisant ce que l'on appelait autrefois des règles de «coercibilité».

    [ deletia ]

    MySQL utilise des valeurs de coercibilité avec les règles suivantes pour résoudre les ambiguïtés:

    • Utilisez le classement avec la valeur de coercibilité la plus faible.

    • Si les deux côtés ont la même coercibilité, alors:

      • Si les deux côtés sont Unicode, ou si les deux côtés ne sont pas Unicode, c'est une erreur.

      • Si l'un des côtés a un jeu de caractères Unicode et un autre côté a un jeu de caractères non Unicode, le côté avec le jeu de caractères Unicode gagne et la conversion automatique du jeu de caractères est appliquée au côté non Unicode. Par exemple, l'instruction suivante ne renvoie pas d'erreur:

        SELECT CONCAT(utf8_column, latin1_column) FROM t1;

        Il renvoie un résultat qui a un jeu de caractères utf8et le même classement que utf8_column. Les valeurs de latin1_columnsont automatiquement converties en utf8avant la concaténation.

      • Pour une opération avec des opérandes du même jeu de caractères mais qui mélangent un _binclassement et un classement _ciou _cs, le _binclassement est utilisé. Cela est similaire à la façon dont les opérations qui mélangent des chaînes non binaires et binaires évaluent les opérandes en tant que chaînes binaires, sauf qu'il s'agit de classements plutôt que de types de données.

  3. Qu'est-ce qu'un "mélange illégal de classements"?

    Un "mélange illégal de classements" se produit lorsqu'une expression compare deux chaînes de classements différents mais de coercibilité égale et que les règles de coercibilité ne peuvent pas aider à résoudre le conflit. C'est la situation décrite sous le troisième point de la citation ci-dessus.

    L'erreur particulière donnée dans la question, Illegal mix of collations (latin1_general_cs,IMPLICIT) and (latin1_general_ci,IMPLICIT) for operation '='nous indique qu'il y avait une comparaison d'égalité entre deux chaînes non Unicode de coercibilité égale. Il nous indique en outre que les classements n'ont pas été indiqués explicitement dans la déclaration, mais ont plutôt été implicites à partir des sources des chaînes (telles que les métadonnées de colonne).

  4. C'est très bien, mais comment résoudre de telles erreurs?

    Comme le suggèrent les extraits manuels cités ci-dessus, ce problème peut être résolu de plusieurs manières, dont deux sont sensées et à recommander:

    • Modifiez le classement de l'une (ou des deux) des chaînes afin qu'elles correspondent et qu'il n'y ait plus d'ambiguïté.

      La façon dont cela peut être fait dépend de l'origine de la chaîne: les expressions littérales prennent le classement spécifié dans la collation_connectionvariable système; les valeurs des tables prennent le classement spécifié dans leurs métadonnées de colonne.

    • Force une chaîne à ne pas être coercitive.

      J'ai omis la citation suivante de ce qui précède:

      MySQL attribue des valeurs de coercibilité comme suit:

      • Une COLLATEclause explicite a une coercibilité de 0. (Pas du tout coercible.)

      • La concaténation de deux chaînes avec des collations différentes a une coercibilité de 1.

      • Le classement d'une colonne ou d'un paramètre de routine stocké ou d'une variable locale a une coercibilité de 2.

      • Une «constante système» (la chaîne renvoyée par des fonctions telles que USER()ou VERSION()) a une coercibilité de 3.

      • Le classement d'un littéral a une coercibilité de 4.

      • NULLou une expression dérivée NULLa une coercibilité de 5.

      Ainsi, le simple fait d'ajouter une COLLATEclause à l'une des chaînes utilisées dans la comparaison forcera l'utilisation de ce classement.

    Alors que les autres seraient une très mauvaise pratique s'ils étaient déployés simplement pour résoudre cette erreur:

    • Forcer l'une (ou les deux) des chaînes à avoir une autre valeur de coercibilité pour que l'une ait priorité.

      L'utilisation de CONCAT()ou CONCAT_WS()entraînerait une chaîne avec une coercibilité de 1; et (si dans une routine stockée) l'utilisation de paramètres / variables locales entraînerait des chaînes avec une coercibilité de 2.

    • Modifiez les encodages de l'une (ou des deux) des chaînes afin que l'une soit Unicode et l'autre non.

      Cela pourrait être fait via le transcodage avec ; ou en changeant le jeu de caractères sous-jacent des données (par exemple en modifiant la colonne, en changeant pour les valeurs littérales, ou en les envoyant du client dans un codage différent et en changeant / ajoutant un introducteur de jeu de caractères). Notez que la modification du codage entraînera d'autres problèmes si certains caractères souhaités ne peuvent pas être codés dans le nouveau jeu de caractères.CONVERT(expr USING transcoding_name)character_set_connectioncharacter_set_client

    • Modifiez les encodages de l'une (ou des deux) des chaînes afin qu'elles soient toutes les deux identiques et modifiez une chaîne pour utiliser le _binclassement approprié .

      Les méthodes de modification des codages et des classements ont été détaillées ci-dessus. Cette approche serait de peu d'utilité si l'on avait réellement besoin d'appliquer des règles de classement plus avancées que celles proposées par le _binclassement.

eggyal
la source
4
Notez qu'un "mélange illégal de classements" peut également survenir lorsqu'il n'y a pas d'ambiguïté sur le classement à utiliser, mais la chaîne à contraindre doit être transcodée en un codage dans lequel certains de ses caractères ne peuvent pas être représentés. J'ai discuté de ce cas dans une réponse précédente .
eggyal
5
Très bonne réponse. Celui-ci devrait être le plus haut, car il plonge dans ce que les développeurs doivent vraiment savoir; pas seulement comment y remédier, mais vraiment comprendre pourquoi les choses se passent comme elles se produisent.
marquez le
Merci mec, tu m'as appris quelque chose aujourd'hui.
briankip
67

Ajout de mon 2c à la discussion pour les futurs googleurs.

J'examinais un problème similaire où j'ai obtenu l'erreur suivante lors de l'utilisation de fonctions personnalisées qui ont reçu un paramètre varchar:

Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and 
(utf8_general_ci,IMPLICIT) for operation '='

En utilisant la requête suivante:

mysql> show variables like "collation_database";
    +--------------------+-----------------+
    | Variable_name      | Value           |
    +--------------------+-----------------+
    | collation_database | utf8_general_ci |
    +--------------------+-----------------+

J'ai pu dire que la base de données utilisait utf8_general_ci , tandis que les tables étaient définies en utilisant utf8_unicode_ci :

mysql> show table status;
    +--------------+-----------------+
    | Name         | Collation       |
    +--------------+-----------------+
    | my_view      | NULL            |
    | my_table     | utf8_unicode_ci |
    ...

Notez que les vues ont un classement NULL . Il semble que les vues et les fonctions ont des définitions de classement même si cette requête affiche null pour une vue. Le classement utilisé est le classement DB qui a été défini lors de la création de la vue / fonction.

La triste solution était à la fois de modifier le classement db et de recréer les vues / fonctions pour les forcer à utiliser le classement actuel.

  • Modification du classement de la base de données:

    ALTER DATABASE mydb DEFAULT COLLATE utf8_unicode_ci;
  • Modification du classement de table:

    ALTER TABLE mydb CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

J'espère que cela aidera quelqu'un.

Ariel T
la source
12
Le classement peut également être défini au niveau de la colonne. Vous pouvez le voir avec:show full columns from my_table;
Jonathan Tran
Je vous remercie. Je viens de supprimer le schéma, de le recréer avec le classement par défaut correct et de tout réimporter.
JRun
1
@JonathanTran Merci! J'avais le jeu de caractères et le classement sur toutes les tables, la base de données et la connexion, mais cela donnait toujours une erreur! Le classement n'a pas été défini sur une colonne! Je l'ai corrigé avecalter table <TABLE> modify column <COL> varchar(255) collate utf8_general_ci;
Chloé
2
Sidenote pour les futurs googleurs: même si votre base de données, vos tables et vos champs ont tous le même classement, vous devez également vous assurer que votre connexion utilise le même classement. Tout a »utf8mb4_unicode_ci« mais SHOW session variables like '%collation%';vous dit que »collation_connection« est »utf8mb4_general_ci«? Exécutez ensuite SET collation_connection = utf8mb4_unicode_ciau préalable.
pixelbrackets
Je vous remercie! Ça m'a pris du temps pour retrouver ça. Non seulement les tables doivent être le même classement, mais la base de données aussi!
moto
15

Parfois, il peut être dangereux de convertir des jeux de caractères, en particulier sur des bases de données contenant d'énormes quantités de données. Je pense que la meilleure option est d'utiliser l'opérateur "binaire":

e.g : WHERE binary table1.column1 = binary table2.column1
Justin Vincent
la source
10

J'ai eu un problème similaire, j'essayais d'utiliser la procédure FIND_IN_SET avec une variable de chaîne .

SET @my_var = 'string1,string2';
SELECT * from my_table WHERE FIND_IN_SET(column_name,@my_var);

et recevait l'erreur

Code d'erreur: 1267. Mélange illégal de classements (utf8_unicode_ci, IMPLICIT) et (utf8_general_ci, IMPLICIT) pour l'opération 'find_in_set'

Réponse courte:

Pas besoin de modifier les variables collation_YYYY, ajoutez simplement le classement correct à côté de votre déclaration de variable , c'est-à-dire

SET @my_var = 'string1,string2' COLLATE utf8_unicode_ci;
SELECT * from my_table WHERE FIND_IN_SET(column_name,@my_var);

Longue réponse:

J'ai d'abord vérifié les variables de classement:

mysql> SHOW VARIABLES LIKE 'collation%';
    +----------------------+-----------------+
    | Variable_name        | Value           |
    +----------------------+-----------------+
    | collation_connection | utf8_general_ci |
    +----------------------+-----------------+
    | collation_database   | utf8_general_ci |
    +----------------------+-----------------+
    | collation_server     | utf8_general_ci |
    +----------------------+-----------------+

Ensuite, j'ai vérifié le classement de la table:

mysql> SHOW CREATE TABLE my_table;

CREATE TABLE `my_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `column_name` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=125 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Cela signifie que ma variable a été configurée avec le classement par défaut de utf8_general_ci alors que ma table était configurée comme utf8_unicode_ci .

En ajoutant la commande COLLATE à côté de la déclaration de variable, le classement des variables correspond au classement configuré pour la table.

nkatsar
la source
5

Vous pouvez essayer ce script , qui convertit toutes vos bases de données et tables en utf8.

Mirat Can Bayrak
la source
1
ligne 24 "cur" au lieu de "curseur"
RTOSkit
2
Et triple la taille de certains index.
Damian Yerrick
2

Solution si des littéraux sont impliqués.

J'utilise Pentaho Data Integration et je ne précise pas la syntaxe SQL. L'utilisation d'une recherche de base de données très simple a donné l'erreur "Mélange illégal de classements (cp850_general_ci, COERCIBLE) et (latin1_swedish_ci, COERCIBLE) pour l'opération '='"

Le code généré était "SELECT DATA_DATE AS latest_DATA_DATE FROM hr_cc_normalised_data_date_v WHERE PSEUDO_KEY =?"

Pour faire court, la recherche était une vue et quand j'ai émis

mysql> show full columns from hr_cc_normalised_data_date_v;
+------------+------------+-------------------+------+-----+
| Field      | Type       | Collation         | Null | Key |
+------------+------------+-------------------+------+-----+
| PSEUDO_KEY | varchar(1) | cp850_general_ci  | NO   |     |
| DATA_DATE  | varchar(8) | latin1_general_cs | YES  |     |
+------------+------------+-------------------+------+-----+

ce qui explique d'où vient le 'cp850_general_ci'.

La vue a été simplement créée avec 'SELECT' X ', ......' Selon le manuel, les littéraux comme celui-ci devraient hériter leur jeu de caractères et leur classement des paramètres du serveur qui ont été correctement définis comme 'latin1' et 'latin1_general_cs' comme ceci clairement ne s'est pas produit, je l'ai forcé dans la création de la vue

CREATE OR REPLACE VIEW hr_cc_normalised_data_date_v AS
SELECT convert('X' using latin1) COLLATE latin1_general_cs        AS PSEUDO_KEY
    ,  DATA_DATE
FROM HR_COSTCENTRE_NORMALISED_mV
LIMIT 1;

maintenant, il affiche latin1_general_cs pour les deux colonnes et l'erreur a disparu. :)

jc508
la source
1

MySQL n'aime vraiment pas mélanger les classements à moins qu'il ne puisse les contraindre au même (ce qui n'est clairement pas faisable dans votre cas). Ne pouvez-vous pas simplement forcer le même classement à être utilisé via une clause COLLATE ? (ou le BINARYraccourci le plus simple le cas échéant ...).

Alex Martelli
la source
Est-ce unique à MySQL? Comment les autres systèmes gèrent-ils un mélange de classements incompatibles de priorité apparemment égale?
eggyal
Votre lien n'est pas valide.
Benubird
1

Si les colonnes avec lesquelles vous rencontrez des problèmes sont des "hachages", considérez ce qui suit ...

Si le "hachage" est une chaîne binaire, vous devez vraiment utiliser le BINARY(...)type de données.

Si le "hachage" est une chaîne hexadécimale, vous n'avez pas besoin d'utf8, et vous devriez éviter cela à cause des vérifications de caractères, etc. Par exemple, MySQL MD5(...)produit une chaîne hexadécimale de 32 octets de longueur fixe. SHA1(...)donne une chaîne hexadécimale de 40 octets. Cela pourrait être stocké dans CHAR(32) CHARACTER SET ascii(ou 40 pour sha1).

Ou, mieux encore, stocker UNHEX(MD5(...))dans BINARY(16). Cela réduit de moitié la taille de la colonne. (Cela le rend cependant peu imprimable.) SELECT HEX(hash) ... Si vous voulez qu'il soit lisible.

La comparaison de deux BINARYcolonnes n'a aucun problème de classement.

Rick James
la source
1

Très intéressant ... Maintenant, soyez prêt. J'ai regardé toutes les solutions «ajouter collation» et pour moi, ce sont des correctifs de pansement. La réalité est que la conception de la base de données était "mauvaise". Oui, des changements standard et de nouvelles choses sont ajoutés, bla bla, mais cela ne change pas le fait de la mauvaise conception de la base de données. Je refuse de suivre la voie de l'ajout de "collation" partout dans les instructions SQL juste pour que ma requête fonctionne. La seule solution qui fonctionne pour moi et éliminera pratiquement la nécessité de modifier mon code à l'avenir est de repenser la base de données / tables pour correspondre au jeu de caractères avec lequel je vivrai et que j'adopterai à long terme. Dans ce cas, j'ai choisi de choisir le jeu de caractères " utf8mb4 ".

Ainsi, la solution ici lorsque vous rencontrez ce message d'erreur "illégal" consiste à reconcevoir votre base de données et vos tables. C'est beaucoup plus facile et plus rapide que ça. L'exportation de vos données et leur réimportation à partir d'un CSV peuvent même ne pas être nécessaires. Modifiez le jeu de caractères de la base de données et assurez-vous que tous les jeux de caractères de vos tables correspondent.

Utilisez ces commandes pour vous guider:

SHOW VARIABLES LIKE "collation_database";
SHOW TABLE STATUS;

Maintenant, si vous aimez ajouter "assembler" ici et là et renforcer votre code avec des "remplacements" forcés, soyez ma conjecture.

Nya Nguyen
la source
0

Une autre source du problème avec les classements est la mysql.proctable. Vérifiez les classements de vos procédures et fonctions de stockage:

SELECT
  p.db, p.db_collation, p.type, COUNT(*) cnt
FROM mysql.proc p
GROUP BY p.db, p.db_collation, p.type;

Faites également attention aux colonnes mysql.proc.collation_connectionet mysql.proc.character_set_client.

ruvim
la source
-1

J'ai utilisé ALTER DATABASE mydb DEFAULT COLLATE utf8_unicode_ci;, mais n'a pas fonctionné.

Dans cette requête:

Select * from table1, table2 where table1.field = date_format(table2.field,'%H');

Ce travail pour moi:

Select * from table1, table2 where concat(table1.field) = date_format(table2.field,'%H');

Oui, seulement a concat.

Knito Auron
la source
Vérifiez le classement de vos tables et de leurs colonnes (affichez l'état de la table et affichez les colonnes complètes de la table1;). L'utilisation de la base de données alter ne fonctionnerait pas si les tables sont déjà créées avec le mauvais classement.
Ariel T
ALTER DATABASE mydb DEFAULT COLLATE ... a fonctionné pour moi, donc vote positif. Peut-être que j'avais un avantage car je pouvais supprimer et recréer la base de données et charger à partir de sauvegardes.
tobixen le
-2

Ce code doit être placé dans Run SQL query / queries on database

FENÊTRE DE QUESTION SQL

ALTER TABLE `table_name` CHANGE `column_name` `column_name`   VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL;

Veuillez remplacer table_name et column_name par le nom approprié.

Sukumar
la source