【问题标题】:PostgreSQL ORDER BY issue - natural sortPostgreSQL ORDER BY 问题 - 自然排序
【发布时间】:2021-12-07 19:52:54
【问题描述】:

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

em_code  name
EM001    AAA
EM999    BBB
EM1000   CCC

要向表中插入一条新记录,

  1. 我用SELECT * FROM employees ORDER BY em_code DESC选择最后一条记录
  2. 使用 reg exp 从 em_code 中剥离字母并存储在 ec_alpha
  3. 将remaing部分转换为整数ec_num
  4. 加一ec_num++
  5. 再次使用足够的 zeors 和前缀 ec_alpha 填充

em_code达到EM1000时,上述算法失败。

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

知道如何选择EM1000吗?

【问题讨论】:

  • 为什么不直接使用序列?
  • 天哪,经典之作。计算机化纸质系统的规则一,智能数字是愚蠢的。将 em 放在一个列中,将 int 放在另一列中。需要时,将其重新构建为愚蠢的人类的一个 ID。
  • @Erwin Brandstetter 感谢您的编辑
  • 请注意,如果一次有多个事务是INSERTing,您描述的方法将惨遭失败,除非您首先LOCK TABLE。关于排序,您可能会发现 this question 信息量很大。

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


【解决方案1】:

您可以采取的一种方法是为此创建一个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】:

    原因是字符串按字母顺序排序(而不是像您想要的那样按数字排序)并且19 之前排序。 你可以这样解决:

    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' 作为第四个参数是“全局”开关,用于将替换应用于字符串中的每个出现,而不仅仅是第一个.

    用空字符串替换每个非数字后,只剩下数字。

    【讨论】:

    • EM 例如可以存储在另一列中,并且这两列都可以声明为表的键。这也将简化插入新行的内容。
    • 有什么办法可以去掉任何非数字而不是使用substring
    • @Mithun:有。我在答案中添加了一些内容。
    【解决方案3】:

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

    https://github.com/Bjond/pg_natural_sort_order

    它可以免费使用,MIT 许可证。

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

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

    【讨论】:

      【解决方案4】:

      从 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

      在我的数据上,我按以下顺序得到结果(注意它也对外国数字进行排序):

      Value
      0
      0001
      001
      1
      06
      6
      13
      ۱۳
      14

      【讨论】:

        【解决方案5】:

        你可以只使用这一行 "ORDER BY 长度(子字符串(em_code FROM '[0-9]+')), em_code"

        【讨论】:

          【解决方案6】:

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

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

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

          【讨论】:

            【解决方案7】:

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

            基本思想是创建一个元组数组(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
              )
            

            【讨论】:

              【解决方案8】:

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

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

              我也放到了 GitHub 上

              https://github.com/ccsalway/dbNaturalSort

              【讨论】:

                【解决方案9】:

                以下解决方案是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 替换为合适的更大长度。请注意,这将直接影响结果字符串的长度,因此不要使该值大于所需值。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2021-05-28
                  • 2014-03-30
                  • 2013-01-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-10-19
                  • 1970-01-01
                  相关资源
                  最近更新 更多