Fma*_*nez 5 postgresql regular-expression replace
假设我在 Postgres 中有一个具有此值的文本字段:
'bar$foo$john$doe$xxx'
Run Code Online (Sandbox Code Playgroud)
我想将最后一次出现的美元 ( $
) 字符替换为另一个字符,例如“-”。替换后该字段的内容应为:
'bar$foo$john$doe-xxx'
Run Code Online (Sandbox Code Playgroud)
Vér*_*ace 14
这个问题涉及到一些横向思维。当反转后,任何字符的最后一次出现也是该字符在字符串中的第一次出现!所有解决方案(除了一个)都使用这种方法。
对于所提出的所有 5 个解决方案,我们有以下内容(此处提供了包含以下所有代码的小提琴。下面的每个解决方案都包含单独的每个解决方案的小提琴):
CREATE TABLE test
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
t_field TEXT
);
Run Code Online (Sandbox Code Playgroud)
PRIMARY KEY
仅第五个解决方案需要此。然后我们运行以下查询来填充表 - 第一个记录是 OP 自己的数据 - 其余的都是随机生成的!
操作:
INSERT INTO test (t_field)
VALUES ('bar$foo$john$doe$xxx'); -- OP's own data
Run Code Online (Sandbox Code Playgroud)
随机数据:
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
--
Run Code Online (Sandbox Code Playgroud)
解决方案将按性能顺序呈现。它在 db<>fiddle 和家庭虚拟机(16GB RAM,SSD)上进行了测试,测试表中有 10M 记录 - 不要在 fiddle 上尝试 10M!每种方法都会被赋予一个因素,即它比虚拟机上最快的方法花费的时间多长。
bar$foo$john$doe-xxx
在所有情况下, OP 的原始数据和测试查询都会获得所需的结果(LIMIT 2
显示它们的行为符合预期 - 即用$
连字符 ( )替换最后一个美元 ( -
) 符号。您可以更改此限制小提琴来检查。
OVERLAY()
,STRPOS()
AND REVERSE()
(个人小提琴):SELECT
t_field,
OVERLAY(t_field PLACING '-'
FROM
LENGTH(t_field) + 1 - STRPOS(REVERSE(t_field), '$')
) AS result
FROM test;
Run Code Online (Sandbox Code Playgroud)
SELECT
REVERSE(REGEXP_REPLACE(REVERSE(t_field), '\$', '-'))
FROM
test;
Run Code Online (Sandbox Code Playgroud)
正在做的事情(从内到外)是:
REVERSE()
字符串,
REGEXP_REPLACE('xxx', '\$', '-')
在反转的字符串上运行。
请注意,这只会替换第一个实例,$
因为'g'
(全局)标志不存在 - 如果读取代码... , '-', 'g')
,则所有美元都将被替换 - 并且您无论如何都可以使用(便宜得多)REPLACE()
函数来做到这一点。
另请注意,它$
是一个正则表达式meta-character
- 即它在正则表达式中具有特殊功能(它意味着字符串的最后一个字符),因此在替换它时必须使用反斜杠 ( \
) 字符对其进行转义。
然后,最后一步是将编辑后的字符串反转回其原始顺序,我们就得到了结果!
值得记住的是,正则表达式非常强大。不幸的是(释义),强大的能力带来了巨大的复杂性。正则表达式可能会变得非常复杂且难以理解 - 但它们非常值得深入研究 - 它们可以在高手的手中将一页页代码变成一行行代码!
首先尝试找到具有非正则表达式功能的不同解决方案(参见解决方案 1)始终是值得的,但它们有自己的位置,在这种情况下,它工作得相当好!上面链接的网站是开始探索它们的好地方。
SELECT
t_field,
REGEXP_REPLACE(t_field, '(.*)\$', '\1-' )
FROM test
LIMIT 2;
Run Code Online (Sandbox Code Playgroud)
性能:10M 记录的时间 = 16316.768 ms。
与最快的比较 = 2.03 x
SUBSTRING()
, POSITION()
and LENGTH()
(单独的fiddle):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;
Run Code Online (Sandbox Code Playgroud)
ARRAY
手动) -v.缓慢但演示STRING_TO_ARRAY()
,UNNEST()
以及1 (个人小提琴)WITH ORDINALITY
1:请参阅 Erwin Brandstetter 的这些帖子( 1、2和3 )WITH ORDINALITY
个人小提琴展示了多种方法以及性能分析和一些讨论。包含在内只是为了完整性,在本例中并不是一个现实的选择。
尽管在这种特殊情况下,该ARRAY
技术的性能不是很好(由于具有子查询),但服务器的大部分后端代码都使用ARRAY
s,并且它们通常是解决各种问题的最佳方法。PostgreSQL 这个鲜为人知的角落非常值得了解。
首先要做的是:
SELECT
UNNEST
(STRING_TO_ARRAY(REVERSE((SELECT t.t_field
FROM test t
WHERE t.id = 1
)), '$'));
Run Code Online (Sandbox Code Playgroud)
结果(OP 的记录 -xxx
由于 REVERSE(),注释排在第一位):
str
xxx
eod
nhoj
oof
rab
Run Code Online (Sandbox Code Playgroud)
该字符串按字符分为多个字段$
。
然后:
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;
Run Code Online (Sandbox Code Playgroud)
结果:
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
Run Code Online (Sandbox Code Playgroud)
我们需要 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;
Run Code Online (Sandbox Code Playgroud)
结果:
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
Run Code Online (Sandbox Code Playgroud)
其作用是使用 作为$
分隔符将反转的字符串聚合回其原始形式,但排除第一个元素 ( WHERE num > 1;
)。代替第一个元素的是第一个元素 - 数组引用[1]
+ 连字符 ( || '-' ||
),因此,我们xxx-
加上反转字符串的其他元素并将$
它们分隔开。
然后我们只需将其应用于REVERSE()
整个构造即可得到所需的结果!
有一个可能的解决方案不使用WITH ORDINALITY
(ROW_NUMBER()代替) - 请参阅个人小提琴中的讨论。
每个查询都会显示家庭虚拟机上 10M 记录的性能数据 - db<>fiddle(30,000 条记录)结果在相对大小方面相当接近地反映了它们。
因此,在这种情况下,如果可能,请使用基于字符串的方法,但正则表达式可以帮助减少SLOC计数,但它们可能会变慢 - 由 DBA/Dev 在速度和复杂性之间做出选择。
regexp_replace
您可以使用正则表达式和捕获括号来完成此操作regexp_replace
SELECT regexp_replace(t.x, '(.*)\$', '\1-' )
FROM ( VALUES ('bar$foo$john$doe$xxx') ) AS t(x);
Run Code Online (Sandbox Code Playgroud)
将替换最后$
一个-
。最终结果是,
bar$foo$john$doe-xxx
Run Code Online (Sandbox Code Playgroud)
下面是它的工作原理,
$
并\1
保存它。$
然后它恢复捕获并\1
添加一个,-
使字符串的其余部分保持不变。