【问题标题】:Convert string with groups of numbers to a comma separated string with single numbers in PL/SQL在PL / SQL中将带有数字组的字符串转换为带有单个数字的逗号分隔字符串
【发布时间】:2020-11-21 01:25:58
【问题描述】:

我有一个像“1-3,4,9,11-15”这样的字符串,我正在寻找一种简单的方法来将它转换为一个逗号分隔的字符串,其中包含单个数字,比如“1,2,3,4 ,9,11,12,13,14,15' 带有 PL/SQL 中的函数。

感谢您的帮助!

【问题讨论】:

  • 使用一些应用程序代码似乎比编写函数更容易
  • 数字排序序列中是否有 NULL 元素或数字必须在最后正确地进行数字排序?

标签: string plsql


【解决方案1】:

我试了一下,可以达到类似的效果,

CREATE OR REPLACE FUNCTION get_series_of_numbers(p_input_string IN VARCHAR2) 
RETURN VARCHAR2 IS
   lo_start        NUMBER;
   lo_end          NUMBER;
   lo_final_string VARCHAR2(4000);
   --a convinient method to get the series for a strig like '1-3' or 9-15'
   FUNCTION get_series(p_series_string VARCHAR2) RETURN VARCHAR2 IS
      lo_string_to_return VARCHAR2(4000);
   BEGIN
      IF instr(p_series_string
              ,'-') > 0
      THEN
         lo_start := to_number(substr(p_series_string
                                     ,1
                                     ,instr(p_series_string
                                           ,'-') - 1));
         lo_end   := to_number(substr(p_series_string
                                     ,instr(p_series_string
                                           ,'-') + 1));
         --query to generate a series of numbers between a start and end point and then concatenate all with ','
         SELECT listagg(actual_numbers
                       ,',') within GROUP(ORDER BY actual_numbers)
         INTO   lo_string_to_return
         FROM   (SELECT LEVEL actual_numbers FROM dual WHERE LEVEL >= lo_start CONNECT BY LEVEL <= lo_end);
      ELSE
         lo_string_to_return := p_series_string;
      END IF;
      RETURN lo_string_to_return;
   END;
BEGIN
   --this loop is to get all the elements in the string separated by ',' as column 
   --so that we can loop over all   
   FOR i IN (SELECT regexp_substr(str
                                 ,'[^,]+'
                                 ,1
                                 ,rownum) split
             FROM   (SELECT p_input_string str FROM dual)
             CONNECT BY LEVEL <= length(regexp_replace(str
                                                      ,'[^,]+')) + 1)
   LOOP
      IF lo_final_string IS NOT NULL
      THEN
         lo_final_string := lo_final_string || ',' || get_series(i.split);
      ELSE
         lo_final_string := get_series(i.split);
      END IF;
   END LOOP;
   RETURN lo_final_string;
END get_series_of_numbers;

一些测试结果:

DECLARE
  input_string VARCHAR2(4000) := '1,2,8-3,4';
  result_string VARCHAR2(4000);
begin
  dbms_output.put_line('Input string is: '||input_string);
  result_string := get_series_of_numbers(p_input_string => input_string);
  dbms_output.put_line('Output string is: '||result_string);
end;
/*
Input string is: 1-3,4,9,11-15
Output string is: 1,2,3,4,9,11,12,13,14,15

Input string is: 1-3,4-6,9-10,11-15
Output string is: 1,2,3,4,5,6,9,10,11,12,13,14,15

Input string is: 1,2,3,4
Output string is: 1,2,3,4

Input string is: 1,2,3-8,4
Output string is: 1,2,3,4,5,6,7,8,4

--a negative case
Input string is: 1,2,8-3,4
Output string is: 1,2,,4
*/

希望它能提供有关可以进一步优化以处理极端情况或尚未涵盖的任何内容的要求的想法。干杯!!

【讨论】:

    【解决方案2】:

    考虑这种使用 CTE 分解步骤的不同方法。见里面的 cmets。可以将其中的一些结合起来,但保持步骤分开会使其更简单。它也可以并且应该被制成一个可重用的过程或函数。

    这也处理 NULL 列表元素,并且数字排序之外的数字将在输出中按数字排序。尝试使用'1-3,17-20,4,9,,11-15' 之类的数据。总是期待意外!

    -- tbl_orig only creates a source for the original data
    WITH tbl_orig(orig_str) AS (
      SELECT '1-3,4,9,11-15' FROM dual
    ),
    -- tbl_rows then contains that data split into rows on
    -- the comma
    tbl_rows(str_element) AS (
      SELECT REGEXP_SUBSTR(orig_str, '(.*?)(,|$)', 1, LEVEL, NULL, 1)
      FROM tbl_orig
      CONNECT BY LEVEL <= REGEXP_COUNT(orig_str, ',')+1
    ),
    -- Next look at those rows and if does not contain a hyphen just keep it,
    -- else use an inline view to expand the range using listagg and regex's to
    -- get the start and end of the range
    tbl_expanded(str_expanded) AS (
    SELECT  
      CASE INSTR(str_element, '-', 1)
        WHEN 0 THEN str_element
        ELSE (SELECT LISTAGG(n, ',') WITHIN GROUP (ORDER BY n)
              FROM (SELECT ROWNUM n FROM dual CONNECT BY LEVEL <= REGEXP_SUBSTR(str_element, '\d+-(\d+)', 1, 1, NULL, 1))
              WHERE n >= REGEXP_SUBSTR(str_element, '(\d+)-\d+', 1, 1, NULL, 1)
             )
      END AS str_expanded
    FROM tbl_rows
    )
    -- Lastly put it all back together, but order by the numeric value of the first part of
    -- the character strings.  
    SELECT LISTAGG(str_expanded, ',') WITHIN GROUP (ORDER BY TO_NUMBER(REGEXP_REPLACE(str_expanded, '(\d+).*', '\1')))
      as fullrange
    FROM tbl_expanded;
    
    
    FULLRANGE                                                                       
    --------------------------------------------------------------------------------
    1,2,3,4,9,11,12,13,14,15                                                        
    

    【讨论】:

    • @Sujitmohanty30 谢谢。正则表达式可能较慢,因此您必须考虑数据量和复杂性。选择和探索不同的做事方式总是好的。当我搜索如何做某事时,我通常会使用部分答案的组合。
    • 非常好,我更喜欢这个解决方案。谢谢!
    • @Weltraumfahrer :- 我不知道你这边的行为是对还是错。您最初要求 PL/SQL 中的解决方案,后来如果有新的解决方案,我们应该像我一样对它进行投票。现在,您不仅更改了已接受的答案,而且甚至没想过要投票给我的答案。我想说的是,这是非常令人沮丧的行为。很抱歉这么说。
    • 我确实付出了一些努力来想出一个解决方案。
    • 对不起,我不想惹你生气,我不知道复选标记的详细用途。我喜欢这两种解决方案,最后还是选择了第一个。谢谢你和最好的问候
    猜你喜欢
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    相关资源
    最近更新 更多