PostgreSQL ORDER BY问题 - 自然排序

时间:2012-02-07 09:09:14

标签: sql postgresql types sql-order-by natural-sort

我在下表中遇到了Postgres ORDER BY问题:

em_code  name
EM001    AAA
EM999    BBB
EM1000   CCC

要在表格中插入新记录,

  1. 我选择了SELECT * FROM employees ORDER BY em_code DESC
  2. 的最后一条记录
  3. 从em_code中剥离字母表,使用reg exp并存储在ec_alpha
  4. 将重映射部分投射到整数ec_num
  5. 增加一ec_num++
  6. 具有足够的zeors并再次添加前缀ec_alpha
  7. em_code到达EM1000时,上述算法失败。

    第一步将返回EM999而不是EM1000,它将再次生成EM1000为新em_code,打破了唯一键约束。

    知道如何选择EM1000吗?

9 个答案:

答案 0 :(得分:8)

原因是字符串按字母顺序排序(而不是像你想要的那样在数字上排序)和1之前的排序。 你可以像这样解决它:

SELECT * FROM employees ORDER BY substring(em_code, 3)::int DESC

em_code中删除冗余的'EM'会更有效 - 如果可以的话 - 并保存一个整数来开始。


评论中的问题的补充答案

从字符串中删除任何和所有非数字:

SELECT regexp_replace(em_code, E'\\D','','g')
FROM employees

\D是“非数字”的正则表达式class-shorthand 'g'作为第四个参数是“全局”开关,用于将替换应用于字符串中的每个匹配项,而不仅仅是第一个。

所以我用空字符串替换每个非数字,只用字符串中的数字来提取数字。

答案 1 :(得分:5)

您可以采取的一种方法是为此创建naturalsort函数。这是一个由Postgres传奇RhodiumToad撰写的例子。

create or replace function naturalsort(text)
    returns bytea language sql immutable strict as $f$
    select string_agg(convert_to(coalesce(r[2], length(length(r[1])::text) || length(r[1])::text || r[1]), 'SQL_ASCII'),'\x00')
    from regexp_matches($1, '0*([0-9]+)|([^0-9]+)', 'g') r;
$f$;

来源:http://www.rhodiumtoad.org.uk/junk/naturalsort.sql

要使用它,只需按以下方式调用您的订单中的功能:

SELECT * FROM employees ORDER BY naturalsort(em_code) DESC

答案 2 :(得分:4)

这总是出现在问题和我自己的发展中,我终于厌倦了这样做的棘手方法。我终于崩溃并将其实现为PostgreSQL扩展:

https://github.com/Bjond/pg_natural_sort_order

可以免费使用MIT许可证。

基本上它只是对字符串中的数字(零预先挂起的数字)进行规范化,这样就可以创建一个索引列来进行全速排序。自述文件解释道。

优点是你可以有一个触发器来完成工作,而不是你的应用程序代码。它将在PostgreSQL服务器上以机器速度计算,并且迁移添加列变得简单而快速。

答案 3 :(得分:3)

你可以使用这一行 “ORDER BY length(substring(em_code FROM'[0-9] +')),em_code”

答案 4 :(得分:1)

我在这个相关问题中详细描述了这个:

Humanized or natural number sorting of mixed word-and-number strings

(我将此答案仅作为有用的交叉引用发布,因此它是社区维基)。

答案 5 :(得分:1)

我想出了一些不同的东西。

基本思想是创建元组(integer, string)的数组,然后按它们进行排序。魔术数字2147483647是int32_max,用于将字符串按数字排序。

  ORDER BY ARRAY(
    SELECT ROW(
      CAST(COALESCE(NULLIF(match[1], ''), '2147483647') AS INTEGER),
      match[2]
    )
    FROM REGEXP_MATCHES(col_to_sort_by, '(\d*)|(\D*)', 'g')
    AS match
  )

答案 6 :(得分:1)

从 Postgres 9.6 开始,可以指定一个排序规则来自然地对带有数字的列进行排序。

https://www.postgresql.org/docs/10/collation.html

-- First create a collation with numeric sorting
CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');

-- Alter table to use the collation
ALTER TABLE "employees" ALTER COLUMN "em_code" type TEXT COLLATE numeric;

现在只需按原样查询即可。

SELECT * FROM employees ORDER BY em_code

在我的数据上,我得到的结果是这样的(注意它也会对外来数字进行排序):

<头>
0
0001
001
1
2
3
4
5
06
6
7
8
09
9
10
12
13
۱۳
14
15
16

答案 7 :(得分:0)

我想到了另一种方法,即使用比填充更少的数据库存储,并且比在运行中计算节省时间。

https://stackoverflow.com/a/47522040/935122

我也把它放在GitHub上

https://github.com/ccsalway/dbNaturalSort

答案 8 :(得分:0)

以下解决方案结合了 another question 中提出的各种想法,以及 classic solution 中的一些想法:

create function natsort(s text) returns text immutable language sql as $$
  select string_agg(r[1] || E'\x01' || lpad(r[2], 20, '0'), '')
  from regexp_matches(s, '(\D*)(\d*)', 'g') r;
$$;

这个函数的设计目标是简单和纯字符串操作(没有自定义类型和数组),所以它可以很容易地用作一个嵌入式解决方案,并且很容易被索引。

注意:如果您希望数字超过 20 位,则必须将函数中的硬编码最大长度 20 替换为合适的更大的长度。请注意,这将直接影响结果字符串的长度,因此不要将该值设置为大于所需的值。