【问题标题】:How to replace all subsets of characters based on values of other tables in pl/pgsql?如何根据pl/pgsql中其他表的值替换所有字符子集?
【发布时间】:2015-06-22 09:07:27
【问题描述】:

我一直在研究如何根据其他行的列的值替换单行的字符串子集,但由于更新仅适用于第一个,因此无法这样做另一个表的行值。所以我打算将它插入到 plpsql 函数的循环中。

这是我的表的 sn-p。主表:

 Table "public.tbl_main"
        Column         |  Type  | Modifiers 
-----------------------+--------+-----------
 maptarget             | text   | 
 expression            | text   | 


 maptarget |                 expression
-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 43194-0   | 363787002:70434600=(386053000:704347000=(414237002:704320005=259470008,704318007=118539007,704319004=50863008),704327008=122592007,246501002=703690001,370132008=30766002)

查找表:

Table "public.tbl_values"
        Column         |  Type  | Modifiers 
-----------------------+--------+-----------
 conceptid             | bigint | 
 term                  | text   |

 conceptid |                   term                   
-----------+------------------------------------------
 386053000 | Patient evaluation procedure (procedure)
 363787002 | Observable entity (observable entity)
 704347000 | Observes (attribute)
 704320005 | Towards (attribute)
 704318007 | Property type (attribute)

我想创建一个函数,将tbl_main.expression 列中的所有数值替换为其对应的tbl_values.term,使用tbl_values.conceptid 作为表达式字符串中每个数值的链接。

我目前被困在循环部分,因为我是 plpgsql 的LOOP 的新手。这是我的函数的草稿。

--create first a test table
drop table if exists tbl_test;
create table tbl_test as select * from tbl_main limit 1;
--

create or replace function test () 
 RETURNS SETOF tbl_main
 LANGUAGE plpgsql
AS $function$
declare
 resultItem tbl_main;
 v_mapTarget text;
 v_expression text;
 ctr int;
begin
  v_mapTarget:='';
  v_expression:='';
  ctr:=1;

  for resultItem in (select * from tbl_test) loop
     v_mapTarget:=resultItem.mapTarget;
     select into v_expression expression from ee;
     raise notice 'parameter used: %',v_mapTarget;
     raise notice 'current expression: %',v_expression;

     update ee set expression=replace(v_expression, new_exp::text, term) from (select new_exp::text, term from tbl_values offset ctr limit 1) b ; 
     ctr:=ctr+1;
     raise notice 'counter: %', ctr;
     v_expression:= (select expression from ee);
     resultItem.expression:= v_expression;
     raise notice 'current expression: %',v_expression;
return next resultItem;
 end loop;
 return;
 end;
$function$;

任何进一步的信息将不胜感激。
我的 Postgres 版本:

x86_64-unknown-linux-gnu 上的 PostgreSQL 9.3.6,由 gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2, 64 位

【问题讨论】:

  • 我没有 plpgsql 的经验,但我会做以下事情:创建一个临时表之类的东西,然后插入 tbl_main 的拆分值。比用你的 tbl_values 做一个简单的连接。如果您可以更改表格,我更愿意这样做。
  • 为什么conceptid386053000 是三次?我希望该专栏是独一无二的。另外,您的 Postgres 版本是什么?

标签: sql database postgresql function plpgsql


【解决方案1】:

带有动态 SQL 的 PL/pgSQL 函数

循环始终是最后的手段。即使在这种情况下,使用查询连接查询字符串并执行它一次会大大降低成本:

CREATE OR REPLACE FUNCTION f_make_expression(_expr text, OUT result text) AS
$func$
BEGIN
   EXECUTE (
      SELECT 'SELECT ' || string_agg('replace(', '') || '$1,'
           || string_agg(format('%L,%L)', conceptid::text, v.term), ','
                         ORDER BY conceptid DESC)
      FROM  (
         SELECT conceptid::bigint
         FROM   regexp_split_to_table($1, '\D+') conceptid
         WHERE  conceptid <> ''
         ) m
      JOIN   tbl_values v USING (conceptid)
      )
   USING _expr
   INTO result;
END
$func$ LANGUAGE plpgsql;

呼叫:

SELECT *, f_make_expression(expression) FROM tbl_main;

然而,如果不是所有的conceptid 都具有相同的位数,则操作可能不明确。首先将 conceptid 替换为更多数字以避免这种情况 - ORDER BY conceptid DESC 会这样做 - 并确保替换字符串不会引入歧义(可能会在下一步中替换的数字)。更多关于这些陷阱的相关答案:

令牌$1在这里有两种不同的使用方式,不要被误导:

  1. regexp_split_to_table(<b>$1</b>, '\D+')

    这个引用了第一个函数参数_expr。您也可以使用参数名称。

  2. || '<b>$1</b>,'

    这会将对通过USING 子句传递给EXECUTE 的第一个表达式的引用连接到SQL 字符串中。外部函数的参数在EXECUTE 内部是不可见的,您必须显式传递它们。

外部函数的$1 (_expr) 作为$1 传递给EXECUTE 纯属巧合。还不如将$7 作为USING 子句中的第三个表达式($3)移交...

我在小提琴中添加了一个调试功能。稍作修改,您就可以输出生成的 SQL 字符串来检查它:

SQL 函数

这是一个纯 SQL 替代方案。可能也更快:

CREATE OR REPLACE FUNCTION f_make_expression_sql(_expr text)
  RETURNS text AS
$func$
SELECT string_agg(CASE WHEN $1 ~ '^\d'
                       THEN txt || COALESCE(v.term, t.conceptid) 
                       ELSE COALESCE(v.term, t.conceptid) || txt END
                , '' ORDER BY rn) AS result
FROM  (
   SELECT *, row_number() OVER () AS rn
   FROM  (
      SELECT regexp_split_to_table($1, '\D+') conceptid
           , regexp_split_to_table($1, '\d+') txt
      ) sub
   ) t
LEFT  JOIN tbl_values v ON v.conceptid = NULLIF(t.conceptid, '')::int
$func$  LANGUAGE sql STABLE;

在 Postgres 9.4 中,这可以通过两个新功能更加优雅:

CREATE OR REPLACE FUNCTION f_make_expression_sql(_expr text)
  RETURNS text AS
$func$
SELECT string_agg(CASE WHEN $1 ~ '^\d'
                       THEN txt || COALESCE(v.term, t.conceptid) 
                       ELSE COALESCE(v.term, t.conceptid) || txt END
                , '' ORDER BY rn) AS result
FROM   ROWS FROM (
          regexp_split_to_table($1, '\D+')
        , regexp_split_to_table($1, '\d+')
       ) WITH ORDINALITY AS t(conceptid, txt, rn)
LEFT   JOIN tbl_values v ON v.conceptid = NULLIF(t.conceptid, '')::int
$func$  LANGUAGE sql STABLE;

SQL Fiddle 为 Postgres 9.3 演示所有内容。

【讨论】:

  • 谢谢。但是当我使用它时它会抛出一个错误。 ERROR: function f_make_expression(text) does not exist LINE 1: SELECT *, f_make_expression(expression) FROM tbl_main... 虽然我确实创建了函数... \df f_make_expresson List of functions Schema | Name | Result data type | Argument data types | Type --------+------------------+------------------+-----------------------------+-------- public | f_make_expresson | text | _expr text, OUT result text | normal
  • @Erwin: f_make_expresson f_make_expression。我有一个错字是你抄的。
  • 糟糕!我的错。我已经修复了该部分,但现在我遇到了函数抛出的参数问题。当我运行该函数时,它总是给我这个错误,但是当我手动运行查询时,它可以工作。 ERROR: query string argument of EXECUTE is null
  • @Erwin:这不应该。字符串连接中的一个操作数必须为 NULL,这会使整个查询字符串无效。您可以改用concat() function 进行调试,但最终您必须找到并消除NULL 值。我对该功能进行了改进(无法解释您遇到的错误)。无论哪种方式,请查看小提琴:它适用于 Postgres 9.3。
  • @Erwin:另外,我还有一个想法,添加了一个纯 SQL 解决方案。
【解决方案2】:

还有另一种方法,无需创建函数...使用“WITH RECURSIVE”。将它与数千行的查找表一起使用。

您需要将以下表名和列更改为您的名称:

tbl_main, strsourcetext, strreplacedtext;

lookuptable、strreplacefrom、strreplaceto。

WITH RECURSIVE replaced AS (
(SELECT
strsourcetext,
strreplacedtext,
array_agg(strreplacefrom ORDER BY length(strreplacefrom) DESC, strreplacefrom, strreplaceto) AS arrreplacefrom,
array_agg(strreplaceto ORDER BY length(strreplacefrom) DESC, strreplacefrom, strreplaceto) AS arrreplaceto,
count(1) AS intcount,
1 AS intindex
FROM tbl_main, lookuptable WHERE tbl_main.strsourcetext LIKE '%' || strreplacefrom || '%'
GROUP BY strsourcetext)
UNION ALL 
SELECT
strsourcetext,
replace(strreplacedtext, arrreplacefrom[intindex], arrreplaceto[intindex]) AS strreplacedtext,
arrreplacefrom,
arrreplaceto,
intcount,
intindex+1 AS intindex
FROM replaced WHERE intindex<=intcount
)
SELECT strsourcetext,
(array_agg(strreplacedtext ORDER BY intindex DESC))[1] AS strreplacedtext
FROM replaced 
GROUP BY strsourcetext

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-25
    • 2021-12-02
    • 1970-01-01
    • 2021-02-20
    • 1970-01-01
    • 1970-01-01
    • 2020-05-29
    • 2023-01-30
    相关资源
    最近更新 更多