PG迁移必读(四):实现Sybase默认排序
Barry Chen

有一个问题曾经困扰了客户很久:将数据从Sybase迁移到PG后,发现SQL输出的数据排序方式发生了变化。这种变化导致终端应用的数据显示与用户熟悉的数据排列不同,给最终用户带来了不便。

例如,现有表

CREATE TABLE t1 (   

    name varchar(10)   NULL

); 

插入以下数据:

INSERT INTO t1 values('aaa');

INSERT INTO t1 values(null);     

INSERT INTO t1 values('abc') ;    

INSERT INTO t1 values('ABC') ;    

INSERT INTO t1 values('TTT') ;

INSERT INTO t1 values('bbc') ;

然后执行以下排序查询,比较Sybase和PG的排序结果,会发现两边的结果是不一样的。

SELECT * FROM t1 ORDER BY name

Sybase PG

 

 

 

Sybase默认排序特点:
1. NULL 值排在最前面

2. 优先排大写字母,后再排小写字母。

PG默认排序特点

  1.  NULL 值排在最后面
  2. 字母排序不分大小写

  3. 相同字母情况下,小写字母排在大写字母之前。

 

这时思维敏捷的你会问:是不是PG配置了不区分大小写引起的?能想到这个,首先给你点个赞,但不是这个原因。运行这个SQL: 
SELECT 'ABC'='abc'

 

返回结果为False,说明PG的字符串比较实际上是区分大小写的。

为客户排忧解难是我们APJ的优良传统。当我们项目组获悉这个问题后,进行了深入分析,发现问题的根源在于PG采用了unicode字符集,而Sybase采用了CP850引起的。那CP850是什么呢?是一个古老的西欧字符集,详细资料参考这个page。不同的字符集,排序结果自然不一致。那如何解决呢?自然是要给PG指定一个与CP850一致排序方式。

 

在数据库中,COLLATE子句用于指定列的排序规则,它决定了字符数据的比较和排序行为。不同的COLLATE设置可以导致相同的字符数据在排序时表现出不同的顺序。COLLATE能够解决排序问题,因为它允许我们为特定的列或表达式指定一个明确的排序规则,从而确保数据的排序方式与特定字符集的排序规则相匹配。

因此针对这个排序问题,解决方案是为PG指定一个与CP850一致的排序方式来模拟Sybase的排序行为。经过分析,我们发现CP850是ASCII的一个扩展,而在PG中支持ASCII排序的正是POSIX。

于是,在PG里使用COLLATE指定排序方式,再次执行查询:

 

可以看到,除去[NULL],其他排序已经与Sybase一致了。

 

最后再加上SQL标准的 NULLS FIRST 子句,就能完美实现Sybase的默认排序了。

 

Perfect!

其实我们也可以直接创建表或数据库时指定默认的COLLATE。这样,我们就不需要在每个ORDER BY语句中都加入COLLATE子句,从而避免了重复和潜在的错误。

在PostgreSQL中,通过以下语句在创建表时为特定的列指定COLLATE:
CREATE TABLE t1 (

    name varchar(10) COLLATE "POSIX" NULL

); 

或者,在创建数据库时设置默认的COLLATE。注意,需要使用LC_COLLATE关键字:

CREATE DATABASE mydb LC_COLLATE 'en_US.utf8';

在CP8项目中,我们通过指定COLLATE为”POSIX“,完美地复现了Sybase默认排序,解决了客户的难题,客户非常满意。