PHP mysql土耳其字符编码和比较

Posted

技术标签:

【中文标题】PHP mysql土耳其字符编码和比较【英文标题】:PHP mysql turkish character encoding and comparison 【发布时间】:2016-01-03 22:46:45 【问题描述】:

我正在尝试通过 AJAX POST 从 mysql 数据库中过滤土耳其名称,英文字母单词都可以列出,但是如果我发送 Ö(这是带点的字母 O),结果会同时出现 O 和 Ö 不仅 Ö

我还注意到 AJAX 帖子是以 %C3%96 发送 Ö,有人可以帮忙吗?

【问题讨论】:

【参考方案1】:

请接受我有点冗长的回复。 让我们从你的第二个问题开始。 %C3%96 表示传输字节 0xC3 和 0x96。这两个字节编码the character Ö in utf-8。 由此(并且您的查询产生所描述的结果)我假设您使用的是utf-8 all the way through。

given charset 的字符中的lexicographical order 由使用的collation 确定。 这或多或少是一个有序的字符列表。例如。 A,B,C,D,.... 意思是A<B<C.... 但是这些列表在同一个“位置”中包含多个字符,例如 [A,Ä],B,C,D....表示A==Ä->true

___ 游览,与您的问题没有直接关系 ____ 我们来看看Ö这个角色的“名字”,它是LATIN CAPITAL LETTER O WITH DIAERESIS。 所以,基本字符是 O,它只是有一些装饰。 一些系统/库允许您指定比较的“粒度”/级别/强度,例如参见Collator::setStrength 的 php-intl 扩展。

<?php
// utf8 characters
define('SMALL_O_WITH_DIAERESIS', chr(0xC3) . chr(0xB6));
define('CAP_O_WITH_DIAERESIS', chr(0xC3) . chr(0x96));

$coll = collator_create( 'utf-8' );
foreach( array('PRIMARY', 'SECONDARY', 'TERTIARY') as $strength) 
    echo $strength, "\r\n";
    $coll->setStrength( constant('Collator::'.$strength) );
    echo '  o ~ ö = ', $coll->compare('o', SMALL_O_WITH_DIAERESIS), "\r\n";
    echo '  Ö ~ ö = ', $coll->compare(CAP_O_WITH_DIAERESIS, SMALL_O_WITH_DIAERESIS), "\r\n";

打印

PRIMARY
  o ~ ö = 0
  Ö ~ ö = 0
SECONDARY
  o ~ ö = -1
  Ö ~ ö = 0
TERTIARY
  o ~ ö = -1
  Ö ~ ö = 1

在初级水平上,所有涉及的字符(o,O,ö,Ö)只是字符 O 的一些不相关的变体,因此都被视为相等。 在第二层,考虑了附加的“特征”WITH DIAERESIS,在第三层也考虑了它是小写字母还是大写字母。 但是...MySQL 并不是这样工作的...所以,再次抱歉 ;-) ___游览结束____

在 MySQL 中有指定顺序的排序规则表。当您选择一个字符集时,您还隐含地选择该字符集的默认排序规则,除非您明确指定一个。在您的情况下,隐含选择的排序规则可能是 utf8_general_ci 并且它处理 ö==o。 这适用于连接的表定义和字符集/排序规则(后者与您的情况几乎无关)。 另一方面,utf8_turkish_ci 处理 ö!=o。这可能就是您想要的排序规则。

当你有一个像这样的表定义时

CREATE TABLE soFoo (
  x varchar(32)
)
CHARACTER SET utf8

选择了 utf8 的默认排序规则 -> general_ci -> o=ö 您可以在定义表格时指定表格的默认排序规则

CREATE TABLE soFoo (
  x varchar(32)
)
CHARACTER SET utf8 COLLATE utf8_turkish_ci

由于您已经有一个表格和数据,您可以更改表格的排序规则...但是如果您在表格级别执行此操作,则必须使用ALTER TABLE ... CONVERT(如果您使用 MODIFY,该列将保留其“原始”排序规则)。

ALTER TABLE soFoo CONVERT TO CHARACTER SET utf8 COLLATE utf8_turkish_ci

这应该可以解决您的问题。


作为旁注,还有(如前所述)collation assigned to your connection。选择一个字符集意味着选择一个排序规则。当(直接)连接到 MySQL 时,我主要使用PDO,我的默认连接代码如下所示

$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
    PDO::ATTR_EMULATE_PREPARES=>false,
    PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));

注意charset=utf8;没有排序规则,所以又将 general_ci 分配给连接。这就是为什么

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
    PDO::ATTR_EMULATE_PREPARES=>false,
    PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));

$smallodiaresis_utf8 = chr(0xC3) . chr(0xB6);
foreach( $pdo->query("SELECT 'o'='$smallodiaresis_utf8'") as $row ) 
    echo $row[0];

打印1 表示o==ö。语句中使用的字符串文字被视为 utf8/utf8_general_ci。

我可以在语句中明确指定字符串文字的排序规则

SELECT 'o' COLLATE utf8_turkish_ci ='ö'

(仅将其设置为两个文字/操作数之一;有关其工作原理和方式,请参阅Collation of Expressions) 或者我可以通过

设置连接排序规则
$pdo->exec("SET collation_connection='utf8_turkish_ci'");

两者都导致

foreach( $pdo->query("SELECT 'o'[...]='$smallodiaresis_utf8'") as $row ) 
    echo $row[0];

打印0

编辑:让事情更复杂一点: 字符集utf8 不能代表所有 个可能的字符。还有一个更广泛的字符集utf8mb4

【讨论】:

【参考方案2】:

PHP 代码应该接收到%C3%96,并适当地解码回Ö。但如果不是,则将 PHP 函数 urldecode() 应用于字符串。

你仍然会有Ö这个角色,而不是O;可以吗?

如果你得到Ö,那么就是utf8 和latin1 的混合。那是一个不同的问题。

【讨论】:

以上是关于PHP mysql土耳其字符编码和比较的主要内容,如果未能解决你的问题,请参考以下文章

php文件字​​符编码、mysql数据库字符编码、特殊字符

Hibernate 在持久化对象时编码错误 [UTF-8]

PHP和MYSQL的编码问题

Media Temple 的 phpMyAdmin 中有问题的土耳其语字符

php mysql 编码问题

PHP JSON 将输出数字编码为字符串