【问题标题】:format string in oracle SQL to replace placeholders with variable names given at the end of the string在 oracle SQL 中格式化字符串以用字符串末尾给出的变量名替换占位符
【发布时间】:2021-06-20 23:15:48
【问题描述】:

各位开发者大家好,

我正在开发一个 Oracle DB,并且有一些 XML 数据以 blob 格式存储, XML 有:

a) 公式(例如 %1 - %2%2||'-'||%1

b) 适合公式的变量的分隔列表(例如 SALE_Q1| SALE_Q2YEAR| MONTH

我已经设法将这些数据提取到 2 个不同的列中(如果需要也可以合并到 1 个列中),我需要做的是将输出列作为变量叠加到占位符上。

(例如 SALE_Q1 - SALE_Q2 或 MONTH||'-'||YEAR

还有一些注意事项,例如:

  1. 我不知道每个公式会有多少个变量,
  2. 变量在公式中的使用顺序与分隔列表的顺序不同(参见示例 2)

我们可以考虑来自如下查询的数据:SELECT formula || ', ' || columns_used FROM data_table; 用于输入字符串 我目前得到的输出是这样的:

%1||'-'||%2||%3, SITE_NO| SITE_NAME| COUNTRY
0.1 * %1, WIND_RES
%1, TOTAL
CASE WHEN LENGTH(%1) < 8 THEN NULL ELSE TO_DATE(%1,'yyyymmdd')END, MIN_DATE
%1(+)=%3 and %2(+)=%4, ABC| LMN| PQR| XYZ

我对 SQL 很陌生,这个要求超出了我的想象,任何帮助将不胜感激。我需要一个 SQL 解决方案,因为 PL/SQL 解决方案在我的要求中不可行(此脚本将从一个数据库中提取数据并定期将其提供给另一个存储库)

我看过一些关于 XML 表、模型或递归正则表达式的文章,但我不确定如何使用它们。我还在 StackOverflow 上看到了this question,但我的要求与此有点不同,而且也有点棘手。将公式字符串和变量放入同一个字符串中以便像该问题一样进行处理也是一种可行的方法。任何可以写在 SQL 查询中的解决方案都会很有帮助。

更多示例供您参考:

"%1||'-'||%2||%3, SITE_NO| SITE_NAME| COUNTRY" => "SITE_NO||'-'||SITE_NAME||COUNTRY"

"0.1 * %1, WIND_RES" => "0.1 * WIND_RES"

"%1, TOTAL" => "TOTAL"

"CASE WHEN LENGTH(%1) < 8 THEN NULL ELSE TO_DATE(%1,'yyyymmdd')END, MIN_DATE" => "CASE WHEN LENGTH(MIN_DATE) < 8 THEN NULL ELSE TO_DATE(MIN_DATE,'yyyymmdd')END"

"%1(+)=%3 and %2(+)=%4, ABC| LMN| PQR| XYZ" => "ABC(+)=PQR and LMN(+)=XYZ"

【问题讨论】:

  • 您似乎正在构建将构建动态 SQL 的东西;这看起来像是使您的代码易受 SQL 注入攻击的秘诀,而且几乎可以肯定这不是您想要放入生产系统的东西。此外,您的最后一个示例看起来非常像您使用 Oracle 的旧逗号连接语法;请迁移到使用 ANSI 连接。
  • 另外,您似乎使用|| 作为字符串连接运算符,| 作为您的分隔符。有什么逻辑可以确保| 字符保留其适当的含义?是不是逗号前的| 是连接运算符,而逗号后的| 总是分隔符?你能确保永远不会有两个逗号吗?
  • @MT0 脚本不会暴露给任何客户端并且数据输入不是来自事务数据库,这是一个脚本是系统的一部分,用于获取大量聚合数据到数据库进行分析。公式来自报告工具,因此 sntax 看起来很旧(我无法更改)。但是我可以更改分隔符,所有建议都可用“、”或“|” “/”或您可以建议的任何符号
  • @MT0,输入字符串和变量列表也可以用作 2 个不同的列,如果这样可以更轻松地完成任务

标签: sql oracle replace oracle11g


【解决方案1】:

我假设最后一个逗号是模板字符串和分隔术语之间的分隔符。您可以使用递归子查询因式分解子句和简单的字符串函数:

WITH terms ( value, terms, num_terms ) AS (
  SELECT SUBSTR( value, 1, INSTR( value, ', ', -1 ) - 1 ),
         SUBSTR( value, INSTR( value, ', ', -1 ) + 2 ),
         REGEXP_COUNT(
           SUBSTR( value, INSTR( value, ', ', -1 ) + 2 ),
           '.+?(\| |$)'
         )
  FROM   table_name
),
term_bounds ( rn, value, terms, start_pos, lvl ) AS (
  SELECT ROWNUM,
         REPLACE(
           value,
           '%' || num_terms,
           CASE num_terms
           WHEN 1
           THEN terms
           ELSE SUBSTR( terms, INSTR( terms, '| ', 1, num_terms - 1 ) + 2 )
           END
         ),
         terms,
         CASE
         WHEN num_terms > 1
         THEN INSTR( terms, '| ', 1, num_terms - 1 )
         ELSE 1
         END,
         num_terms
  FROM   terms
UNION ALL
  SELECT rn,
         REPLACE(
           value,
           '%' || (lvl - 1),
           CASE lvl - 1
           WHEN 1
           THEN SUBSTR( terms, 1, start_pos - 1 )
           ELSE SUBSTR(
                  terms,
                  INSTR( terms, '| ', 1, lvl - 2 ) + 2,
                  start_pos - INSTR( terms, '| ', 1, lvl - 2 ) - 2
                )
           END
         ),
         terms,
         CASE
         WHEN lvl > 2
         THEN INSTR( terms, '| ', 1, lvl - 2 )
         ELSE 1
         END,
         lvl - 1
  FROM   term_bounds
  WHERE  lvl > 1
)
SEARCH DEPTH FIRST BY rn SET rn_order
SELECT value
FROM   term_bounds
WHERE  lvl = 1;

其中,对于样本数据:

CREATE TABLE table_name ( value ) AS
SELECT '%1||'-'||%2||%3, SITE_NO| SITE_NAME| COUNTRY' FROM DUAL UNION ALL
SELECT '0.1 * %1, WIND_RES' FROM DUAL UNION ALL
SELECT '%1, TOTAL' FROM DUAL UNION ALL
SELECT 'CASE WHEN LENGTH(%1) < 8 THEN NULL ELSE TO_DATE(%1,'yyyymmdd')END, MIN_DATE' FROM DUAL UNION ALL
SELECT '%1(+)=%3 and %2(+)=%4, ABC| LMN| PQR| XYZ' FROM DUAL UNION ALL
SELECT '%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, %11, ONE| TWO| THREE| FOUR| FIVE| SIX| SEVEN| EIGHT| NINE| TEN| ELEVEN' FROM DUAL UNION ALL
SELECT '%%%%%%%7, HELLO| 1| 2| 3| 4| 5| 6' FROM DUAL

输出:

|价值 | | :------------------------------------------------ ---------------------------------------------------- | | SITE_NO||"-"||SITE_NAME||国家 | | 0.1 * WIND_RES | |总计 | | CASE WHEN LENGTH(MIN_DATE)

db小提琴here


或作为 PL/SQL 函数:

CREATE FUNCTION substitute_values(
  i_value          IN VARCHAR2,
  i_terms          IN VARCHAR2,
  i_term_delimiter IN VARCHAR2 DEFAULT '| '
) RETURN VARCHAR2 DETERMINISTIC
IS
  TYPE term_list_type IS TABLE OF VARCHAR2(200);
  v_start  PLS_INTEGER := 1;
  v_end    PLS_INTEGER;   
  v_index  PLS_INTEGER;
  v_terms  term_list_type := term_list_type();
  v_output VARCHAR2(4000) := i_value;
BEGIN
  LOOP
    v_end := INSTR(i_terms, i_term_delimiter, v_start);
    v_terms.EXTEND;
    IF v_end > 0 THEN
      v_terms(v_terms.COUNT) := SUBSTR(i_terms, v_start, v_end - v_start);
    ELSE
      v_terms(v_terms.COUNT) := SUBSTR(i_terms, v_start);
      EXIT;
    END IF;
    v_start := v_end + LENGTH(i_term_delimiter);
  END LOOP;
  
  LOOP
    v_index  := TO_NUMBER(REGEXP_SUBSTR(v_output, '^(.*?)%(\d+)', 1, 1, NULL, 2));
    IF v_index IS NULL THEN
      RETURN v_output;
    ELSIF v_index > v_terms.COUNT THEN
      RETURN NULL;
    END IF;
    v_output := REGEXP_REPLACE(v_output, '^(.*?)%(\d+)', '\1' || v_terms(v_index));
  END LOOP;
END;
/

然后:

SELECT SUBSTITUTE_VALUES(
         SUBSTR(value, 1, INSTR(value, ', ', -1) - 1),
         SUBSTR(value, INSTR(value, ', ', -1) + 2)
       ) AS value
FROM   table_name

输出:

VALUE
SITE_NO||'-'||SITE_NAME||COUNTRY
0.1 * WIND_RES
TOTAL
CASE WHEN LENGTH(MIN_DATE) < 8 THEN NULL ELSE TO_DATE(MIN_DATE,'yyyymmdd')END
ABC(+)=PQR and LMN(+)=XYZ
ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN
HELLO

db小提琴here

【讨论】:

  • 非常感谢您提供这个解决方案,它就像一个魅力!
  • 我运行了您的查询,它运行起来就像一个魅力,但我有一个角落案例,我想问一下 我如何为 %15 或 % 之类的占位符合并两位数21
  • @kunalkavthekar 更新为从末尾开始,从最高数字倒推到最低数字。
  • 非常感谢,成功了,抱歉回复晚了
  • 不,您需要反复应用替换,而这只能通过反复迭代字符串来完成。您可以使用 SQL、PL/SQL(问题中的示例)或其他可以从数据库访问的语言(例如 Java)来执行此操作。我可以建议的唯一其他改进是在插入/更新值时计算替换文本(使用触发器填充另一个列/表或在物化视图中),这样您就不必使用每个 @ 重新生成文本987654328@.
猜你喜欢
  • 2012-05-11
  • 1970-01-01
  • 2020-08-12
  • 2013-08-13
  • 2020-06-09
  • 2012-02-03
  • 2016-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多