Как преобразовать столбец в ASCII на лету без сохранения, чтобы проверить совпадения с внешней строкой ASCII?

У меня есть функция поиска участников, в которой вы можете указывать части имен, и возвращаемым значением должны быть все участники, имеющие хотя бы одно имя пользователя, имя или фамилию, соответствующие этому вводу. Проблема здесь в том, что некоторые имена имеют «странные» символы, такие как é в Renée, и пользователь не хочет вводить странный символ, а обычную замену ASCII e.

В PHP я конвертирую входную строку в ASCII, используя iconv (на всякий случай, если кто-то вводит странные символы). Однако в базе данных я также должен преобразовать странные символы в ASCII (очевидно), чтобы строки совпадали.

Я пробовал следующее:

SELECT
  CONVERT(_latin1'Renée' USING ascii) t1, 
  CAST(_latin1'Renée' AS CHAR CHARACTER SET ASCII) t2;

(Это две попытки.) Обе не работают. Оба имеют Ren?e в качестве вывода. Знак вопроса должен быть e. Ничего страшного, если он выведет Ren?ee, так как я могу просто удалить все вопросительные знаки после преобразования.

Как вы понимаете, столбцы, которые я хочу запросить, имеют кодировку Latin1.

Спасибо.


person Rudie    schedule 20.11.2010    source источник
comment
Возможно ли это как-то по-другому? Слишком много свободы — это нормально, я просто буду фильтровать результаты с помощью PHP строго после фильтра в MySQL. Например. Я хорошо получаю Renee, Renée и Renäe и Renõe в качестве результатов MySQL при вводе Renee. Так или иначе??   -  person Rudie    schedule 22.11.2010
comment
Лучше всего хранить спички. Преобразование «на лету» очень медленное для больших данных, потому что оно не подходит для индексации.   -  person Pacerier    schedule 11.02.2015


Ответы (4)


Вам не нужно ничего конвертировать. Ваше требование состоит в том, чтобы сравнить две строки и спросить, равны ли они, игнорируя акценты; сервер базы данных может использовать сопоставление, чтобы сделать это за вас:

Сопоставления, отличные от UCA, имеют взаимно однозначное соответствие между кодом символа и весом. В MySQL такие сопоставления нечувствительны к регистру и диакритическим знакам. Например, utf8_general_ci: «a», «A», «À» и «á» имеют разные коды символов, но все они имеют вес 0x0041 и сравниваются как равные.

mysql> SET NAMES 'utf8' COLLATE 'utf8_general_ci';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT 'a' = 'A', 'a' = 'À', 'a' = 'á';
+-----------+-----------+-----------+
| 'a' = 'A' | 'a' = 'À' | 'a' = 'á' |
+-----------+-----------+-----------+
|         1 |         1 |         1 |
+-----------+-----------+-----------+
1 row in set (0.06 sec)
person Vince Bowdren    schedule 24.12.2010
comment
вышесказанное неверно, хранилище данных - latin1, и OP может не применять UTF-8 в кодировке страницы - person ajreal; 24.12.2010
comment
К сожалению (?) Это не работает. Я пробую select id from members where lastname like 'test6e%' (со значением lastname из 'test6ë'). Записи не возвращены. База данных, таблица и столбец имеют кодировку UTF8. Когда я пытаюсь select 'Reneé' = 'Renee', 'Renëe' = 'Renee';, они возвращают true. Странный? - person Rudie; 24.12.2010
comment
@Rudie: отлично работает в моей среде, но посмотрите на мой ответ для объяснений/точностей. - person Danosaure; 25.12.2010

Во-первых, это должно работать так:

SELECT * FROM `test` WHERE `name` COLLATE utf8_general_ci LIKE '%renee%';

Где таблица test:

+-----+--------+
| id  | name   |
+-----+--------+
|  1  | Renée  |
|  2  | Renêe  |
|  3  | Renee  |
+-----+--------+

Какая у вас версия MySQL и как вы пытаетесь ее сопоставить?


Одним из других возможных решений является транслитерация.

Связано: Транслитерация PHP

Транслитерация ввода не должна быть проблемой, но транслитерация значений из постоянного хранилища (например, БД) в режиме реального времени во время поиска может быть неосуществимой. Таким образом, вы можете добавить еще три поля, например: username_slug, firstname_slug и lastname_slug. При вставке/изменении записи установите соответствующие значения slug. И при поиске ищите транслитерированный ввод по этим полям слагов.

+------+----------+---------------+----------+---------------+ ...
| id   | username | username_slug | lastname | lastname_slug | ...
+------+----------+---------------+----------+---------------+ ...
|    1 | Renée    |    renee      | La Niña  | la-nina       | ...
|    2 | Renêe    |    renee      | ...      | ...           | ...
|    3 | Renee    |    renee      | ...      | ...           | ...
+------+----------+---------------+----------+---------------+ ...

Поиск по словам «renee» или «renèe» будет соответствовать всем записям.

В качестве побочного эффекта вы можете использовать эти поля для создания ссылок SEF (дружественных для поисковых систем), поэтому они называются ,..._slug, например. example.com/users/renee. Конечно, в этом случае вы должны проверить уникальность поля slug.

person Halil Özgür    schedule 26.12.2010
comment
Поначалу это может быть хорошей идеей, но этот тип настройки — кошмар обновления... если только у вас нет прочной основы и никто никогда не будет обновлять базу данных напрямую вручную. Я бы предпочел транслитерацию в реальном времени, а не хранить ее в базе данных. - person Danosaure; 27.12.2010
comment
Обычно да, но обычно происходит денормализация, если производительность начинает беспокоить :) - person Halil Özgür; 28.12.2010

Ответ @vincebowdren выше работает, я просто добавляю это как ответ для целей форматирования:

CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `lastname` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL
);
insert into members values (1, 'test6ë');
select id from members where lastname like 'test6e%';

Урожайность

+------+
| id   |
+------+
|    1 |
+------+

И используя Latin1,

set names latin1;
CREATE TABLE `members2` (
  `id` int(11) DEFAULT NULL,
  `lastname` varchar(20) CHARACTER SET latin1 DEFAULT NULL
);
insert into members2 values (1, 'Renée');
select id from members2 where lastname like '%Renee%';

даст:

+------+
| id   |
+------+
|    1 |
+------+

Конечно, OP должен иметь одинаковую кодировку в приложении (PHP), соединении (MySQL в Linux по умолчанию использовал latin1 в 5.0, но по умолчанию используется UTF8 в 5.1) и в типе данных поля, чтобы иметь меньше неизвестных. Сопоставления позаботятся обо всем остальном.

EDIT: я написал должен иметь лучший контроль над всем, но следующее также работает:

set names latin1;
select id from members where lastname like 'test6ë%';

Потому что, как только кодировка соединения установлена, MySQL выполняет внутреннее преобразование. В этом случае он каким-то образом преобразует и сравнивает строку UTF8 (из БД) с latin1 (из запроса).

EDIT 2: Некоторый скептицизм требует от меня привести еще более убедительный пример:

Учитывая заявления выше, вот что я сделал больше. Убедитесь, что терминал находится в UTF8.

set names utf8;
insert into members values (5, 'Renée'), (6, 'Renêe'), (7, 'Renèe');
select members.id, members.lastname, members2.id, members2.lastname
from members inner join members2 using (lastname);

Помните, что members в utf8, а members2 в latin1.

+------+----------+------+----------+
| id   | lastname | id   | lastname |
+------+----------+------+----------+
|    5 | Renée    |    1 | Renée    |
|    6 | Renêe    |    1 | Renée    |
|    7 | Renèe    |    1 | Renée    |
+------+----------+------+----------+

что доказывает, что при правильных настройках сопоставление работает за вас.

person Danosaure    schedule 25.12.2010
comment
@Danosaure - это неправда, то, что вы сравниваете с Renée, - это UTF8, а не latin1 (iso-8859-*) - person ajreal; 25.12.2010
comment
@ajreal: это переведено. Я протестировал его перед публикацией. Вам необходимо соответствующим образом настроить свою среду (терминал, соединение и сопоставление). Вы проверили это, прежде чем сказать, что я не прав? - person Danosaure; 25.12.2010
comment
@Danosaure - очевидно, вы ошибаетесь ... ваш метод предполагает, что все символы находятся в utf-8, что неверно. мои подсказки вам, используйте char_length - person ajreal; 26.12.2010
comment
@ajreal: вы неправильно читаете. Одна таблица в utf8, а другая в latin1. Пожалуйста, прочитайте внимательно, прежде чем говорить, что я что-то предполагаю. Вы пробовали, прежде чем сказать, что я не прав? Используйте терминал, а не PHP. Правильно настройте среду, чтобы она работала. - person Danosaure; 26.12.2010
comment
@Danasaure - вздох, пользовательский ввод может быть любым, он не ограничивается только utf-8, кто вам сказал, что это utf-8? Очевидно, это iso-8859-*. И что еще более важно, не имеет значения, как он хранится в базе данных, важно, как ввод сравнивается с базой данных. - person ajreal; 26.12.2010
comment
@ajreal: Очевидно, ты не хочешь понимать. В моем примере используется сопоставление, чтобы доказать, что строка utf8 правильно сравнивается со строкой latin1. Очевидно, что строка latin1 не нуждается в каком-либо преобразовании, чтобы делать то, что должен делать OP, если кодировка соединения - latin1. Возможно, вы не понимаете, как работают сопоставления, чтобы спорить так много. Я не буду продолжать эту бессмысленную дискуссию, если вы продолжите сосредотачиваться на кодировке, потому что вы не докажете, что она не работает. Вы ошибаетесь, потому что кодировка хранилища имеет значение. - person Danosaure; 26.12.2010
comment
@Danosaure Я уверен, что ваши тесты работают, но в моей среде это не так. Я пробовал SET NAMES 'utf8' COLLATE 'utf8_general_ci'; и SET NAMES 'utf8';, прежде чем запрашивать таблицу участников. Входная строка - ASCII (PHP гарантирует это). Вся база данных (столбцы/таблицы/база данных) — UTF8. Я не знаю о «связи» (откуда мне знать?). Запрос select id, lastname from members where lastname like 'test6e%'; не дает результатов. - person Rudie; 26.12.2010
comment
Я провел еще несколько тестов, и что-то странное происходит при сохранении фамилий (и других столбцов, которые я предполагаю) в базе данных. Когда я выполняю update members set lastname = 'test6é' where id = 226948; на странице UTF8, а затем запрашиваю lastname с этим идентификатором, он возвращает 'test6é'. Когда я сохраняю его в PhpMyAdmin (также на странице UTF8), он сохраняет и возвращает правильное значение: 'test6é'. Что случается?? Обе страницы используют одно и то же соединение MySQLi и имеют заголовок <meta charset=utf8> (в HTML и HTTP). (Значения отображаются правильно в моем приложении!) - person Rudie; 26.12.2010
comment
@Rudie: должен быть параметр соединения для указания кодировки. Извините, я не владею PHP свободно, только MySQL. Просто интересно, почему в OP вы использовали latin1 и свои тесты в UTF8? - person Danosaure; 26.12.2010
comment
@Rudie: если вы хотите настроить соединение mysqli вашего php для использования определенной кодировки (в идеале utf-8), вы можете использовать метод mysqli::set_charset. См. php.net/manual/en/mysqli.set-charset.php подробности. Я использую это в своем php, обнаружив, что без него я по ошибке сохранял плохо закодированные данные в своей базе данных. - person Vince Bowdren; 26.12.2010

Оператор CAST() в контексте кодировок символов переводит из одного метода хранения символов в другой — он не меняет фактические символы, что вам и нужно. Символ é — это то, что есть в любом наборе символов, это не e. Вам нужно преобразовать символы с акцентом в символы без акцента, что является другой проблемой и уже несколько раз спрашивалось ранее (нормализация символов с диакритическими знаками в запросах MySQL).

Я не уверен, есть ли способ сделать это непосредственно в MySQL, за исключением таблицы перевода и последовательного просмотра букв. Скорее всего, было бы проще написать PHP-скрипт для просмотра базы данных и выполнения переводов.

person Orbling    schedule 20.11.2010
comment
Я не хочу сохранять «переводы». Переводы предназначены только для поиска. Результаты отображения должны быть такими, какие они есть: с акцентами и прочими экзотическими символами. Если в PHP есть для этого функция (icon), то почему не в MySQL? Я не (хочу) верить в это! - person Rudie; 22.11.2010
comment
@Rudie PHP имеет очень много функций, которых нет в MySQL. Языки SQL имеют тенденцию быть очень легкими с точки зрения их стандартной библиотеки. Можно было бы написать функцию для достижения желаемого, хотя производительность может быть не фантастической, если вы не написали ее как UDF или собственную функцию (хотя в последнем случае вы могли бы просто вызвать библиотеку iconv). - person Orbling; 22.11.2010