【问题标题】:Replacing Placeholder values with another table's data用另一个表的数据替换占位符值
【发布时间】:2019-06-10 14:44:46
【问题描述】:

我有 2 个表。第一个表包含带有占位符的行,第二个表包含这些占位符值。

我想要一个从第一个表中获取数据并将占位符替换为存储在第二个表中的实际值的查询。

例如: 表1数据

id                                      value
608CB424-90BF-4B08-8CF8-241C7635434F    jdbc:postgresql://{POSTGRESIP}:{POSTGRESPORT}/{TESTDB}
CDA4C3D4-72B5-4422-8071-A29D32BD14E0    https://{SERVICEIP}/svc/{TESTSERVICE}/

表2数据

id                                      placeolder      value
201FEBFE-DF92-4474-A945-A592D046CA02    POSTGRESIP      1.2.3.4
20D9DE14-643F-4CE3-B7BF-4B7E01963366    POSTGRESPORT    5432
45611605-F2D9-40C8-8C0C-251E300E183C    TESTDB          mytest
FA8E2E4E-014C-4C1C-907E-64BAE6854D72    SERVICEIP       10.90.30.40
45B76C68-8A0F-4FD3-882F-CA579EC799A6    TESTSERVICE     mytest-service

需要的输出是

id                                      value
608CB424-90BF-4B08-8CF8-241C7635434F    jdbc:postgresql://1.2.3.4:5432/mytest
CDA4C3D4-72B5-4422-8071-A29D32BD14E0    https://10.90.30.40/svc/mytest-service/

【问题讨论】:

    标签: sql postgresql postgresql-9.6


    【解决方案1】:

    如果你想使用类似 Python 的命名占位符,那么你需要写在 plpythonu 上的辅助函数:

    create extension plpythonu;
    
    create or replace function formatpystring( str text, a json ) returns text immutable language plpythonu as $$
    import json
    d = json.loads(a)
    return str.format(**d)
    $$;
    

    然后简单测试:

    select formatpystring('{foo}.{bar}', '{"foo": "win", "bar": "amp"}');
     formatpystring 
    ----------------
     win.amp
    

    最后,您需要从表格中组合这些参数。很简单:

    select t1.id, formatpystring(t1.value, json_object_agg(t2.placeholder, t2.value)) as value
    from table1 as t1, table2 as t2
    group by t1.id, t1.value;
    

    (查询未测试,但您有方向)

    【讨论】:

      【解决方案2】:

      (笨拙的)动态 SQL 实现,具有外连接,但生成递归函数调用:

      这个函数效率不会很高,但是翻译表可能比较小。


      CREATE TABLE xlat_table (aa text ,bb text);
      INSERT INTO xlat_table (aa ,bb ) VALUES( 'BBB', '/1.2.3.4/')
          ,( 'ccc', 'OMG') ,( 'ddd', '/4.3.2.1/') ;
      
      CREATE FUNCTION dothe_replacements(_arg1 text) RETURNS text
      AS
      $func$
      DECLARE
              script  text;
              braced text;
              res text;
              found record; -- (aa text, bb text, xx text);
      BEGIN
      
      script := '';
      res := format('%L', _arg1);
      
      for found IN SELECT xy.aa,xy.bb
              , regexp_matches(_arg1, '{\w+}','g' ) AS xx
              FROM xlat_table xy
              LOOP
              -- RAISE NOTICE '#xx=%', found.xx[1];
              -- RAISE NOTICE 'aa=%', found.aa;
              -- RAISE NOTICE 'bb=%', found.bb;
      
              braced := '{'|| found.aa || '}';
              IF (found.xx[1] = braced  ) THEN
                      -- RAISE NOTICE 'Res=%', res;
      
                      script := format ('replace(%s, %L, %L)'
                              ,res,braced,found.bb);
                      res := format('%s', script);
              END IF;
              END LOOP;
      
      if(length(script) =0) THEN return res; END IF;
      
      script :='Select '|| script;
      -- RAISE NOTICE 'script=%', script;
      
      EXECUTE script INTO res;
      
      return res;
      END;
      
      $func$
      LANGUAGE plpgsql;
      
      SELECT dothe_replacements( 'aaa{BBB}ccc{ddd}eee' );
      SELECT dothe_replacements( '{AAA}bbb{CCC}DDD}{EEE}' );
      

      结果:


      CREATE TABLE
      INSERT 0 3
      CREATE FUNCTION
           dothe_replacements      
      -----------------------------
       aaa/1.2.3.4/ccc/4.3.2.1/eee
      (1 row)
      
          dothe_replacements    
      --------------------------
       '{AAA}bbb{CCC}DDD}{EEE}'
      (1 row)
      

      上述方法具有二次行为(wrt xlat-entries 的数量);这是可怕的

      但是,我们可以动态创建一个函数(一次)并多次调用它 (穷人的发电机)

      应该只从xlat 表中选择相关条目。

      而且,您当然应该在每次更改 xlat 表时重新创建函数。


      CREATE FUNCTION create_replacement_function(_name text) RETURNS void
      AS
      $func$
      DECLARE
              argname text;
              res text;
              script text;
              braced text;
              found record; -- (aa text, bb text, xx text);
      BEGIN
      
      script := '';
      argname := '_arg1';
      res :=format('%I', argname);
      
      for found IN SELECT xy.aa,xy.bb
              FROM xlat_table xy
              LOOP
              -- RAISE NOTICE 'aa=%', found.aa;
              -- RAISE NOTICE 'bb=%', found.bb;
              -- RAISE NOTICE 'Res=%', res;
              braced := '{'|| found.aa || '}';
              script := format ('replace(%s, %L, %L)'
                              ,res,braced,found.bb);
                      res := format('%s', script);
              END LOOP;
      
      script :=FORMAT('CREATE FUNCTION %I (_arg1 text) RETURNS text AS
              $omg$
              BEGIN
              RETURN %s;
              END;
              $omg$ LANGUAGE plpgsql;', _name, script);
      
      RAISE NOTICE 'script=%', script;
      
      EXECUTE script ;
      
      return ;
      END;
      
      $func$
      LANGUAGE plpgsql;
      
      SELECT create_replacement_function( 'my_function');
      
      SELECT my_function('aaa{BBB}ccc{ddd}eee' );
      SELECT my_function( '{AAA}bbb{CCC}DDD}{EEE}' );
      

      结果:


      CREATE FUNCTION
      NOTICE:  script=CREATE FUNCTION my_function (_arg1 text) RETURNS text AS
          $omg$
          BEGIN
          RETURN replace(replace(replace(_arg1, '{BBB}', '/1.2.3.4/'), '{ccc}', 'OMG'), '{ddd}', '/4.3.2.1/');
          END;
          $omg$ LANGUAGE plpgsql;
       create_replacement_function 
      -----------------------------
      
      (1 row)
      
               my_function         
      -----------------------------
       aaa/1.2.3.4/ccc/4.3.2.1/eee
      (1 row)
      
            my_function       
      ------------------------
       {AAA}bbb{CCC}DDD}{EEE}
      (1 row)
      

      【讨论】:

        【解决方案3】:

        以下提供了一个具有单个功能的 plpgsql 解决方案。 您会注意到我已经“重命名”了值列。使用保留/关键字作为对象名称是不好的做法。 soq 也是我用于所有 SO 代码的架构。
        该过程首先从 table2 获取持有者值并生成一组键值对(在本例中为 hstore,但 jsonb 也可以)。然后它从值列(我的列名:val_string)构建一个数组,其中包含值中的 place_holder 名称。最后,它使用数组值作为查找键迭代该数组,用键值中的值替换实际的持有者名称。
        任一表中的较大卷的性能都不会很好。如果您需要一次处理大量数据到单行临时表,可能会产生更好的性能。

        create or replace function soq.replace_holders( place_holder_line_in text)
         returns text
        language plpgsql
        as $$
        declare 
            l_holder_values hstore;
            l_holder_line   text; 
            l_holder_array  text[];
            l_indx          integer;
        
        begin
            -- transform cloumns to key-value pairs of holder-value
            select string_agg(place,',')::hstore
              into l_holder_values
              from (
                     select concat( '"',place_holder,'"=>"',place_value,'"')  place 
                       from soq.table2 
                   ) p; 
           -- raise notice 'holder_array_in==%',l_holder_values;
        
            -- extract the text line and build array of place_holder names
            select phv, string_to_array (string_agg(v,','),',')  
              into l_holder_line,l_holder_array
              from (
                    select replace(replace(place_holder_line_in,'{',''),'}','') phv
                         , replace(replace(replace(regexp_matches(place_holder_line_in,'({[^}]+})','g')::text ,'{',''),'}',''),'"','') v 
                   ) s 
               group by phv;
        
            -- raise notice 'Array==%',l_holder_array::text;
            -- replace each key from text line with the corresponding value
            for l_indx in 1 .. array_length(l_holder_array,1)
            loop
                l_holder_line = replace(l_holder_line,l_holder_array[l_indx],l_holder_values -> l_holder_array[l_indx]);
            end loop;
        
            -- done
            return l_holder_line;       
        end;
        $$;
        

        -- 测试驱动

        select id, soq.replace_holders(val_string) result_value from soq.table1;
        

        【讨论】:

          【解决方案4】:

          我为此解决方案创建了一个简单的查询,它按要求工作。

          WITH RECURSIVE cte(id, value, level) AS (
                  SELECT id,value, 0 as level
                  FROM Table1 
                UNION 
                  SELECT ts.id,replace(ts.value,'{'||tp.placeholder||'}',tp.value) as value, level+1
              FROM cte ts, Table2 tp WHERE ts.value LIKE CONCAT('%',tp.placeholder, '%')
          )
          SELECT id, value FROM cte c
           where  level =
          (
            select Max(level)
            from cte c2  where c.id=c2.id
          )  
          

          输出是

          id                                      value
          CDA4C3D4-72B5-4422-8071-A29D32BD14E0    https://10.90.30.40/svc/mytest-service/
          608CB424-90BF-4B08-8CF8-241C7635434F    jdbc:postgresql://1.2.3.4:5432/mytest
          

          【讨论】:

            猜你喜欢
            • 2020-04-17
            • 2018-04-23
            • 1970-01-01
            • 2012-04-23
            • 2016-12-16
            • 1970-01-01
            • 2017-09-11
            • 2013-01-24
            • 1970-01-01
            相关资源
            最近更新 更多