在 oracle SQL 中格式化字符串,用字符串末尾给出的变量名替换占位符
format string in oracle SQL to replace placeholders with variable names given at the end of the string
各位开发人员大家好,
我正在处理 Oracle 数据库,并且有一些 XML 数据以 blob 格式存储,
XML 有:
a) 公式(例如 %1 - %2
或 %2||'-'||%1
)
b) 适合公式的分隔变量列表(例如 SALE_Q1| SALE_Q2
或 YEAR| MONTH
)
我已经设法将这些数据提取到 2 个不同的列中(如果需要也可以合并到 1 个列中),我需要做的是将输出列作为变量叠加到占位符上。
(例如 SALE_Q1 - SALE_Q2 或 MONTH||'-'||YEAR
)
还有一些注意事项,例如:
- 我不知道每个公式会有多少个变量,
- 公式中使用变量的顺序并不总是与定界列表相同(参见示例 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 table、模型或递归正则表达式的文章,但我不确定如何使用这些文章。我也在 Whosebug 上看到了 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"
我假设最后一个逗号是模板字符串和分隔项之间的分隔符。您可以使用递归子查询分解子句和简单的字符串函数:
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
输出:
| 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<>fiddle 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+)', '' || 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<>fiddle here
各位开发人员大家好,
我正在处理 Oracle 数据库,并且有一些 XML 数据以 blob 格式存储, XML 有:
a) 公式(例如 %1 - %2
或 %2||'-'||%1
)
b) 适合公式的分隔变量列表(例如 SALE_Q1| SALE_Q2
或 YEAR| MONTH
)
我已经设法将这些数据提取到 2 个不同的列中(如果需要也可以合并到 1 个列中),我需要做的是将输出列作为变量叠加到占位符上。
(例如 SALE_Q1 - SALE_Q2 或 MONTH||'-'||YEAR
)
还有一些注意事项,例如:
- 我不知道每个公式会有多少个变量,
- 公式中使用变量的顺序并不总是与定界列表相同(参见示例 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 table、模型或递归正则表达式的文章,但我不确定如何使用这些文章。我也在 Whosebug 上看到了 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"
我假设最后一个逗号是模板字符串和分隔项之间的分隔符。您可以使用递归子查询分解子句和简单的字符串函数:
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
输出:
| 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<>fiddle 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+)', '' || 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<>fiddle here