AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 303245
Accepted
Fmartinez
Fmartinez
Asked: 2021-11-30 03:06:59 +0800 CST2021-11-30 03:06:59 +0800 CST 2021-11-30 03:06:59 +0800 CST

如何替换字符串(文本列)中最后一次出现的字符?

  • 772

假设我在 Postgres 中有一个具有此值的文本字段:

'bar$foo$john$doe$xxx'

我想将最后一次出现的美元 ( $) 字符替换为另一个字符,例如“-”。替换后,该字段的内容应为:

'bar$foo$john$doe-xxx'
postgresql regular-expression
  • 3 3 个回答
  • 2933 Views

3 个回答

  • Voted
  1. Best Answer
    Vérace
    2021-11-30T03:50:54+08:002021-11-30T03:50:54+08:00

    介绍:

    这个问题涉及到一些横向思考。任何字符的最后一次出现也是该字符在反转时字符串中的第一次出现!所有的解决方案(一栏)都使用这种方法。

    对于提出的所有 5 个解决方案,我们有以下内容(以下所有代码的小提琴都可以在此处获得。每个解决方案的单独小提琴包含在下面的每个解决方案中):

    CREATE TABLE test
    (
      id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
      t_field TEXT
    );
    

    PRIMARY KEY只有第 5 个解决方案需要。然后我们运行以下查询来填充表——第一条记录是 OP 自己的数据——其余的都是随机生成的!

    操作:

    INSERT INTO test (t_field)
    VALUES ('bar$foo$john$doe$xxx');  -- OP's own data
    

    随机数据:

    INSERT INTO test (t_field)
    SELECT 
      LEFT(MD5(RANDOM()::TEXT), FLOOR(RANDOM() * (5 - 3 + 1) + 3)::INT) || '$' ||
      LEFT(MD5(RANDOM()::TEXT), FLOOR(RANDOM() * (5 - 3 + 1) + 3)::INT) || '$' ||
      LEFT(MD5(RANDOM()::TEXT), FLOOR(RANDOM() * (5 - 3 + 1) + 3)::INT) || '$' ||
      LEFT(MD5(RANDOM()::TEXT), FLOOR(RANDOM() * (5 - 3 + 1) + 3)::INT) || '$' ||
      LEFT(MD5(RANDOM()::TEXT), FLOOR(RANDOM() * (5 - 3 + 1) + 3)::INT)
    FROM 
      GENERATE_SERIES(1, 29999);  --<<== Vary here
       
    --
    -- For this fiddle, we only have 30,000 (29,999 + the OP's original datum) records 
    -- (although relative magnitudes appear good), the individual fiddles use
    -- 300,000. 
    --
    -- 300,000 appears large enough to give reliable consistent results and small 
    -- enough so that the fiddle doesn't fail too often - rarely fails on 30k.
    --
    --
    -- You can vary this number, but please consider using the individual fiddles for
    -- large numbers of records so as not to hit the db<>fiddle server too hard!
    --
    -- The home test VM used 10,000,000 records - 16 GB RAM, 1 CPU, SSD
    --
    

    解决方案将按性能排序。它在 db<>fiddle 和家庭虚拟机(16GB RAM,SSD)上进行了测试,测试表中有 10M 记录 - 不要在 fiddle 上尝试 10M!每种方法都给出了一个因素,即它比 VM 上最快的方法花费了多长时间。

    在所有情况下,bar$foo$john$doe-xxx对于 OP 的原始数据和测试查询(LIMIT 2显示它们的行为符合预期 - 即用$连字符 ( ) 替换最后一个美元 ( ) 符号) ,都可以获得所需的结果-。您可以在小提琴检查。

    1:Postgresql 字符串函数(参见手册)、使用OVERLAY()、STRPOS()AND REVERSE()(个人小提琴):

    SELECT
      t_field, 
      OVERLAY(t_field PLACING '-' 
        FROM 
          LENGTH(t_field) + 1 - STRPOS(REVERSE(t_field), '$')
             ) AS result
    FROM test;
    
    • 性能:10M 记录的时间 = 8034.787 ms。
    • 与最快的比较 = 1.0 x

    2: Reverse() 和正则表达式函数REGEXP_REPLACE()(个人小提琴):

    SELECT 
      REVERSE(REGEXP_REPLACE(REVERSE(t_field), '\$', '-'))
    FROM
      test;
    

    正在做的事情(从内到外)是:

    • REVERSE()字符串,

    • REGEXP_REPLACE('xxx', '\$', '-')在反转的字符串上运行。

      请注意,这只会替换第一个实例,$因为'g'(global) 标志不存在 - 如果代码 read ... , '-', 'g'),那么所有美元都将被替换 - 无论如何你都可以使用(便宜得多)REPLACE()函数来做到这一点。

      另请注意,这$是一个正则表达式meta-character- 即它在正则表达式中具有特殊功能(它表示字符串的最后一个字符),因此\在替换它时必须使用反斜杠 ( ) 字符对其进行转义。

    • 然后,最后一步是将我们编辑的字符串反转回原来的顺序,我们就有了结果!

    值得记住的是,正则表达式非常强大。不幸的是(释义),强大的力量带来了巨大的复杂性。正则表达式很容易变得复杂且难以理解——但它们非常值得一试——它们可以将代码页面变成专家手中的单行代码!

    首先尝试使用非正则表达式功能找到不同的解决方案总是值得的(参见解决方案 1),但它们有自己的位置,在这种情况下,它工作得相当好!上面链接的站点是开始探索它们的好地方。

    • 性能:10M 记录的时间 = 14298.643 ms。
    • 与最快的比较 = 1.77 x

    3:使用 REGEXP_REPLACE() 的替代正则表达式(不使用 REVERSE() -请参阅 Evan Carroll 的回答(个人小提琴)):

    SELECT
      t_field,
      REGEXP_REPLACE(t_field, '(.*)\$', '\1-' )
    FROM test
    LIMIT 2;
    
    • 性能:10M 记录的时间 = 16316.768 ms。

    • 与最快的比较 = 2.03 x

    4:仅替代字符串函数,使用SUBSTRING(),POSITION()和LENGTH()(单独的小提琴):

    SELECT
      t_field,
      REVERSE(
      SUBSTRING(REVERSE(t_field) FROM 1 FOR POSITION('$' IN REVERSE(t_field)) - 1)
      || '-' ||
      SUBSTRING(REVERSE(t_field) FROM POSITION('$' IN REVERSE(t_field)) + 1 FOR (LENGTH(REVERSE(t_field)))))
     FROM test
    LIMIT 2;
    
    • 性能:10M 记录的时间 = 16316.768 ms。
    • 与最快的比较 = 2.34 x

    5:(ARRAY手动) -v.慢但演示STRING_TO_ARRAY(),UNNEST()和1 (个人小提琴)WITH ORDINALITY

    1:参见Erwin Brandstetter 的这些帖子(1、2和3 )WITH ORDINALITY

    个人小提琴展示了许多方法以及性能分析和一些讨论。仅出于完整性考虑而包含在内,在这种情况下,不作为一个现实的选择。

    尽管在这种特殊情况下,该ARRAY技术的性能不是很高(由于具有子查询),但服务器的许多后端代码都使用ARRAYs,它们通常是解决各种问题的最佳方法。了解 PostgreSQL 这个鲜为人知的角落是非常值得的。

    首先要做的是:

    SELECT
      UNNEST
      (STRING_TO_ARRAY(REVERSE((SELECT t.t_field 
                                        FROM test t
                                        WHERE t.id = 1
                                        )), '$'));
    

    结果(OP的记录-xxx由于REVERSE(),注释首先出现):

    str
    xxx
    eod
    nhoj
    oof
    rab
    

    字符串按字符拆分为字段$。

    然后:

    SELECT
      t.t_field,
      t.id, x.elem, x.num
    FROM test t
    LEFT JOIN LATERAL
      UNNEST(STRING_TO_ARRAY(REVERSE((SELECT t_field 
                                      FROM test
                                      WHERE test.id = t.id
                                      )), '$'))
      WITH ORDINALITY AS x (elem, num) ON TRUE
      LIMIT 5;
    

    结果:

                 t_field    id    elem  num
    bar$foo$john$doe$xxx     1     xxx    1
    bar$foo$john$doe$xxx     1     eod    2
    bar$foo$john$doe$xxx     1    nhoj    3
    bar$foo$john$doe$xxx     1     oof    4
    bar$foo$john$doe$xxx     1     rab    5
    

    我们需要 the 的原因WITH ORDINALITY是没有 is,我们无法区分字符串的第一个元素(即我们感兴趣的元素)和其他元素(elem, num);

    然后,我们这样做:

    SELECT
      (SELECT t_field FROM test WHERE test.id = tab.id),
      REVERSE(
      (STRING_TO_ARRAY((SELECT REVERSE(t_field) FROM test WHERE test.id = tab.id), '$'))[1]
      || '-' || 
      STRING_AGG(elem, '$'))
    FROM
    (
      SELECT
        t.id, x.elem, x.num
      FROM test t
      LEFT JOIN LATERAL
        UNNEST(STRING_TO_ARRAY(REVERSE((SELECT t_field 
                                        FROM test
                                        WHERE test.id = t.id
                                        )), '$'))
        WITH ORDINALITY AS x (elem, num) ON TRUE
    ) AS tab
    WHERE tab.num > 1
    GROUP BY tab.id
    LIMIT 2;
    

    结果:

                 t_field    result
    bar$foo$john$doe$xxx    bar$foo$john$doe-xxx
    7a29f$d06f$20e$21f$1b1  7a29f$d06f$20e$21f-1b1  -- will vary by fiddle run!
    result
    bar$foo$john$doe-xxx
    

    这样做是为了将反转的字符串聚合回其原始形式,使用$作为分隔符,但不包括第一个元素 ( WHERE num > 1;)。代替第一个元素的是第一个元素 - 数组引用[1]+ 连字符 ( || '-' ||) 等等,我们xxx-加上反向字符串的其他元素,并将$它们分开。

    然后,我们只是简单地应用于REVERSE()整个构造以给出所需的结果!

    • 性能:10M 记录的时间 = 80715.198 ms。
    • 与最快的比较 = 10.04 x

    有一个解决方案可能不使用WITH ORDINALITY(ROW_NUMBER()代替) - 请参阅个人小提琴中的讨论。

    表现

    每个查询都会显示家庭 VM 上 1000 万条记录的性能数据 - db<>fiddle(30,000 条记录)结果在相对量级方面相当接近地反映了它们。

    因此,在这种情况下,尽可能使用基于字符串的方法,但是正则表达式可以帮助减少SLOC计数,但是它们可能会更慢 - 由 DBA/Dev 在速度和复杂性之间做出选择。

    • 11
  2. Evan Carroll
    2021-11-30T09:24:44+08:002021-11-30T09:24:44+08:00

    使用regexp_replace

    您可以使用正则表达式和捕获括号来执行此操作regexp_replace

    SELECT regexp_replace(t.x, '(.*)\$', '\1-' )
    FROM ( VALUES ('bar$foo$john$doe$xxx') ) AS t(x);
    

    将最后一个替换$为-. 最终结果是,

    bar$foo$john$doe-xxx
    

    下面是它的工作原理,

    • 捕获最后一个之前的所有内容$并\1保存。
    • 抓取但不抓取最后一个$
    • 留下一切……

    然后它恢复捕获\1并添加一个-保留字符串的其余部分。

    • 6
  3. Phill W.
    2021-12-01T01:51:56+08:002021-12-01T01:51:56+08:00

    为您提供另一个选择。
    诚然,这可能与您的情况相关,也可能不相关,但无论如何我都会提到它,如果只是为了完整性......

    正确规范您的数据,使每个字段只有一个值。
    这样,改变任何一个值就变成了 Childs-Play。这只是一个定期更新声明。

    警告:如果您从不使用该字段值的任何部分查询您的表,那么这种方法 [可能] 过大。

    • 2

相关问题

  • 运行时间偏移延迟复制的最佳实践

  • 存储过程可以防止 SQL 注入吗?

  • 如何使用正则表达式查询名称?

  • PostgreSQL 中 UniProt 的生物序列

  • PostgreSQL 9.0 Replication 和 Slony-I 有什么区别?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve