SQL 按字母表顺序排列字符

SQL 按字母表顺序排列字符,你想按照字母表顺序对字符串里的字符进行排序,考虑下面的结果集。

SQL 按字母表顺序排列字符 问题描述

你想按照字母表顺序对字符串里的字符进行排序,考虑下面的结果集。

ENAME
----------
ADAMS
ALLEN
BLAKE
CLARK
FORD
JAMES
JONES
KING
MARTIN
MILLER
SCOTT
SMITH
TURNER
WARD

你希望得到如下所示的结果集。

OLD_NAME   NEW_NAME
---------- --------
ADAMS      AADMS
ALLEN      AELLN
BLAKE      ABEKL
CLARK      ACKLR
FORD       DFOR
JAMES      AEJMS
JONES      EJNOS
KING       GIKN
MARTIN     AIMNRT
MILLER     EILLMR
SCOTT      COSTT
SMITH      HIMST
TURNER     ENRRTU
WARD       ADRW

SQL 按字母表顺序排列字符 解决方案

本问题是一个绝佳的例证,它表明为什么理解一种数据库并掌握其提供的各项功能是多么重要。如果我们正在使用的数据库没有提供合适的内置函数来帮我们解决问题,我们就需要想一些别出心裁的办法。不妨比较下面的 MySQL 解决方案和其他数据库的解决方案。
DB2
为了对多行字符串进行排序,必须遍历每个字符串,然后对其中的字符进行排序。

 1  select ename,
 2         max(case when pos=1 then c else '' end)||
 3         max(case when pos=2 then c else '' end)||
 4         max(case when pos=3 then c else '' end)||
 5         max(case when pos=4 then c else '' end)||
 6         max(case when pos=5 then c else '' end)||
 7         max(case when pos=6 then c else '' end)
 8    from (
 9  select e.ename,
10         cast(substr(e.ename,iter.pos,1) as varchar(100)) c,
11         cast(row_number()over(partition by e.ename
12                              order by substr(e.ename,iter.pos,1))
13              as integer) pos
14    from emp e,
15         (select cast(row_number()over() as integer) pos
16            from emp) iter
17   where iter.pos <= length(e.ename)
18          ) x
19   group by ename

MySQL
这里的关键是 GROUP_CONCAT 函数,该函数不仅能连接员工名字字符串里的每个字符,还能对它们进行排序。

1  select ename, group_concat(c order by c separator '')
2    from (
3  select ename, substr(a.ename,iter.pos,1) c
4    from emp a,
5         ( select id pos from t10 ) iter
6   where iter.pos <= length(a.ename)
7         ) x
8   group by ename

Oracle
SYS_CONNECT_BY_PATH 函数能迭代地创建一个列表。

 1  select old_name, new_name
 2    from (
 3  select old_name, replace(sys_connect_by_path(c,' '),' ') new_name
 4    from (
 5  select e.ename old_name,
 6         row_number() over(partition by e.ename
 7                           order by substr(e.ename,iter.pos,1)) rn,
 8         substr(e.ename,iter.pos,1) c
 9    from emp e,
10         ( select rownum pos from emp ) iter
11   where iter.pos <= length(e.ename)
12   order by 1
13         ) x
14   start with rn = 1
15 connect by prior rn = rn-1 and prior old_name = old_name
16         )
17   where length(old_name) = length(new_name)

PostgreSQL
PostgreSQL 中没有能够方便地对字符串中的字符进行排序的内置函数,因此我们不仅要遍历每个字符串,还需要提前知道长度最大的员工名字。为了提高代码的可读性,本解决方案使用视图 V

create or replace view V as
select x.*
  from (
select a.ename,
       substr(a.ename,iter.pos,1) as c
  from emp a,
       (select id as pos from t10) iter
 where iter.pos <= length(a.ename)
 order by 1,2
       ) x

下面的 SELECT 语句使用了上述视图。

 1 select ename,
 2        max(case when pos=1 then
 3                 case when cnt=1 then c
 4                      else rpad(c,cast(cnt as integer),c)
 5                 end
 6                 else ''
 7            end)||
 8        max(case when pos=2 then
 9                 case when cnt=1 then c
10                      else rpad(c,cast(cnt as integer),c)
11                 end
12                 else ''
13            end)||
14        max(case when pos=3 then
15                 case when cnt=1 then c
16                      else rpad(c,cast(cnt as integer),c)
17                 end
18                 else ''
19            end)||
20        max(case when pos=4 then
21                 case when cnt=1 then c
22                      else rpad(c,cast(cnt as integer),c)
23                 end
24                 else ''
25            end)||
26        max(case when pos=5 then
27                 case when cnt=1 then c
28                      else rpad(c,cast(cnt as integer),c)
29                 end
30                 else ''
31            end)||
32        max(case when pos=6 then
33                 case when cnt=1 then c
34                      else rpad(c,cast(cnt as integer),c)
35                 end
36                 else ''
37            end)
38   from (
39 select a.ename, a.c,
40        (select count(*)
41           from v b
42          where a.ename=b.ename and a.c=b.c ) as cnt,
43        (select count(*)+1
44           from v b
45          where a.ename=b.ename and b.c<a.c) as pos
46   from v a
47        ) x
48  group by ename

SQL Server
为了对多行字符串进行排序,必须遍历每个字符串,并对其中的字符进行排序。

 1  select ename,
 2            max(case when pos=1 then c else '' end)+
 3            max(case when pos=2 then c else '' end)+
 4            max(case when pos=3 then c else '' end)+
 5            max(case when pos=4 then c else '' end)+
 6            max(case when pos=5 then c else '' end)+
 7            max(case when pos=6 then c else '' end)
 8       from (
 9     select e.ename,
10           substring(e.ename,iter.pos,1) as c,
11           row_number() over (
12            partition by e.ename
13                order by substring(e.ename,iter.pos,1)) as pos
14       from emp e,
15            (select row_number()over(order by ename) as pos
16               from emp) iter
17      where iter.pos <= len(e.ename)
18            )  x
19      group by ename

SQL 按字母表顺序排列字符 扩展知识

DB2 和 SQL Server
内嵌视图 X 把每个名字字符串里的每个字符都提取出来,并当作一行返回。函数 SUBSTR 或函数 SUBSTRING 提取出名字的每个字符,并且 ROW_NUMBER 函数按照字母表顺序排序每个字符。

ENAME    C    POS
-----    -    ---
ADAMS    A    1
ADAMS    A    2
ADAMS    D    3
ADAMS    M    4
ADAMS    S    5
...

为了把字符串中的每个字母都提取出来,并当作一行返回,我们必须遍历整个字符串。这个工作由内嵌视图 ITER 来完成。
现在,每个名字的字母都已经按照字母表顺序排列,最后一步就是将这些字母按顺序连接成新的字符串。CASE 语句(第 2 ~ 7 行)评估每个字母的位置。如果某个特定位置的字符被发现了,那么它就会被连接到下一个评估(后续的 CASE 语句)的结果里。因为调用了聚合函数 MAX,对应于每个 POS 值只有一个字符会被返回,因此对应于每个名字只会返回一行数据。CASE 语句一共有 6 个,这是因为 EMP 表里最长的名字只含有 6 个字符。
MySQL
内嵌视图 X(第 3 ~ 6 行)把每个名字的字符都提取出来,并当作一行返回。SUBSTR 函数可以提取名字字符串里的每个字符。

ENAME    C
-----    -
ADAMS    A
ADAMS    A
ADAMS    D
ADAMS    M
ADAMS    S
...

内嵌视图 ITER 用于遍历字符串。其余的工作都交由 GROUP_CONCAT 函数完成。通过指定排序方式,GROUP_CONCAT 函数不仅能串接每个字母,还能按照字母表顺序对它们进行排序。
Oracle
最重要的工作是由视图 X(第 5 ~ 11 行)完成的,它提取出每个名字的字符,并按照字母表顺序排列好。通过遍历字符串并对字符执行排序实现了这一点。查询语句的剩余部分只是将排好序的名字字符粘结到一起而已。
只执行内嵌视图 X 的话,就能看到把名字拆解之后得到的各个字符了。

OLD_NAME           RN C
---------- ---------- -
ADAMS               1 A
ADAMS               2 A
ADAMS               3 D
ADAMS               4 M
ADAMS               5 S
...

然后,提取出排好序的字符并重建每个名字。可以使用 SYS_CONNECT_BY_PATH 函数来完成这一步,它把所有的字符按顺序串接起来。

OLD_NAME   NEW_NAME
---------- ----------
ADAMS      A
ADAMS      AA
ADAMS      AAD
ADAMS      AADM
ADAMS      AADMS
...

最后,只保留那些和原名字具有相同长度的字符串。
PostgreSQL
为了提高可读性,本解决方案使用视图 V 来遍历字符串。视图里的 SUBSTR 函数会提取每个名字的全部字符,得到如下结果集。

ENAME    C
-----    -
ADAMS    A
ADAMS    A
ADAMS    D
ADAMS    M
ADAMS    S
...

该视图也按照 ENAME 和从每个名字提取出来的字母做了排序。内嵌视图 X(第 15 ~ 18 行)从视图 V 里检索出名字、字符、字符在名字里出现的次数及其位置(按字母表顺序排列)。

 ename | c | cnt | pos
-------+---+-----+-----
 ADAMS | A |   2 |   1
 ADAMS | A |   2 |   1
 ADAMS | D |   1 |   3
 ADAMS | M |   1 |   4
 ADAMS | S |   1 |   5

对于该解决方案而言,由内嵌视图 X 返回的 CNT 列和 POS 列非常重要。POS 列用来对每个字符进行排序,而 CNT 列用于确定每个字符在名字中出现的次数。最后一步是,评估每个字符的位置并重建名字。注意,每个 CASE 语句实际上都包含两个 CASE 子句。这是为了确认一个字符在名字中是否出现了不止一次。如果出现多次的话,那么返回的就不是该字符,而是由 CNT 个该字符串接而成的字符串。聚合函数 MAX 能确保每个名字只返回一行数据。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程