【问题标题】:How to split a string value based on a delimiter in DB2如何在 DB2 中根据分隔符拆分字符串值
【发布时间】:2013-09-23 14:32:16
【问题描述】:

如何在 DB2 中拆分字符串值?

例如,给定值:

CHG-FFH.

我想在破折号 (-) 上进行拆分,这将产生两个值:

CHG 
FFH. 

我尝试使用 split 函数,但它不是 DB2 中的函数。

我们将不胜感激。

【问题讨论】:

标签: split db2


【解决方案1】:

简答:

你需要找到 定界符,然后以它为起点的子串,并计算出长度。

SELECT 
    SUBSTR('CHG-FFH', 1, LOCATE('-','CHG-FFH')-1) as FIRST_PART
  , SUBSTR('CHG-FFH', LOCATE('-','CHG-FFH')+1)   as SECOND_PART
FROM SYSIBM.SYSDUMMY1;

奖励!如果您经常这样做,请创建一个用户定义的函数来动态执行此操作。这是example in a DB fiddle.

CREATE FUNCTION SPLITTER (input VARCHAR(4000), delimiter CHAR, part_number INTEGER)
      RETURNS VARCHAR(4000)
      LANGUAGE SQL
      READS SQL DATA
      NO EXTERNAL ACTION
      DETERMINISTIC
      RETURN 

with pos_info (first_pos, length) as (
select 
  case when part_number = 1 then 0
       else LOCATE_IN_STRING(input, delimiter,1, part_number-1, OCTETS)  
  end as first_pos, 

  case when part_number = 1 then LOCATE_IN_STRING(input, delimiter,1, part_number, OCTETS) - 1 
       when LOCATE_IN_STRING(input, delimiter,1, part_number, OCTETS) = 0
        and  LOCATE_IN_STRING(input, delimiter,1, part_number-1, OCTETS) = 0
       then 0
       when LOCATE_IN_STRING(input, delimiter,1, part_number, OCTETS) = 0
       then length(input) -  LOCATE_IN_STRING(input, '-',1, part_number - 1,    OCTETS)
       else LOCATE_IN_STRING(input, delimiter,1, part_number, OCTETS) -    LOCATE_IN_STRING(input, delimiter,1, part_number-1, OCTETS) - 1
  end as length
from sysibm.sysdummy1
)

select    
    substr(input, first_pos+1,length) as part
from pos_info;

或者,您可以看到不同的方法here at this answer: Split a VARCHAR in DB2 to retrieve a value inside


长答案:

DB2 以及其他关系数据库不提供单一功能来完成此任务。

原因很可能是它不是一个隐式标量函数。如果你的字符串中有多个破折号,你想把它分成三部分吗?四?所以第一步是注意你的数据是否是确定的——如果它有你想要拆分的特定数量的组件。在您的示例中,您有两个,因此我将从该假设开始,然后评论您将如何处理其他情况。

场景:由分隔符分隔的两个组件的字符串值

只有两部分,你需要找到分隔符的位置,然后在一个子字符串函数中使用它之前和之后的位置,在它之前和之后子字符串。

  1. LOCATE 分隔符的索引。
LOCATE('-','CHG-FFH')

注意: DB2 提供了两个可用于此目的的函数:POSITION(或 POSSTR)和 LOCATE(或 LOCATE_IN_STRING)。 LOCATE 功能更强大,因为它允许您指定起始位置,如果您有多个分隔符,这将很有帮助。

  1. SUBSTR 使用分隔符索引。

对于第一部分,从位置 1 开始您的子字符串,直到分隔符之前的字符(分隔符位置 - 1):

SUBSTR('CHG-FFH', 1,LOCATE('-','CHG-FFH')-1) as FIRST_PART

对于第二部分,在分隔符索引之后的位置(分隔符位置 + 1)开始您的子字符串,并获取字符串的其余部分:

 SUBSTR('CHG-FFH', LOCATE('-','CHG-FFH')+1) as SECOND_PART

最终结果:

SELECT 
    SUBSTR('CHG-FFH', 1,LOCATE('-','CHG-FFH')-1) as FIRST_PART
  , SUBSTR('CHG-FFH', LOCATE('-','CHG-FFH')+1) as SECOND_PART
FROM SYSIBM.SYSDUMMY1;

场景:由分隔符分隔的三个组件的字符串值

使用与第一个场景相同的概念,但您必须确定第二个分隔符的索引。使用第一个分隔符的索引来指定起始点:注意 LOCATE 允许指定起始位置:

>>-LOCATE(search-string,source-string-+--------+-+--------------------+-)-><
                                      '-,start-' '-,--+-CODEUNITS16-+-'     
                                                      +-CODEUNITS32-+       
                                                      '-OCTETS------' 

找到第二个分隔符:

使用第一个分隔符的位置作为寻找第二个分隔符的起点。

LOCATE('-','CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE')+1)

将其用作第二个和第三个值的 SUBSTR 点,一切就绪。注意:对于第二个值,您必须使用两个分隔符位置来对值进行子字符串化。

最终结果:

SELECT 
    SUBSTR('CHG-FFH-EEE', 1,LOCATE('-','CHG-FFH-EEE')-1) as FIRST_PART
  , SUBSTR('CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE')+1, LOCATE('-','CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE'))-1) as SECOND_PART
  , SUBSTR('CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE')+1)+1) as THIRD_PART
FROM SYSIBM.SYSDUMMY1;

你可以看到这个策略会因为你的字符串中有更多的分隔符而失控。

场景:定界符数量不定

这是一个棘手的问题,最好使用存储过程来解决。仔细考虑以下事项:您希望如何从算法中得出解析数据,您将如何访问数据?数组不是 SQL 中的原生类型,但它们存在于存储过程中,那么当您从 String 中解析出所有片段后,您将如何处理数组?

这里回答了处理这种情况的一种方法:

Split a VARCHAR in DB2 to retrieve a value inside

【讨论】:

  • 第二部分可以是:SUBSTR('CHG-FFH-EEE', LOCATE('-','CHG-FFH-EEE')+1, LOCATE('-','CHG- FFH-EEE', LOCATE('-','CHG-FFH-EEE'))-1) 作为 SECOND_PART
  • 是的!没错,@ user3606336。我之前没有注意到我的示例无法正常工作。我会用你的更正来更新我的答案。谢谢!
  • 很容易实现一个函数scan('CHG-FFH-EEE',2,'-'),它返回第二个单词,单词之间用'-'分隔
  • @jms 感谢您的建议。我在答案中添加了一个用户定义函数(UDF),并链接到一个演示它的小提琴。好主意!
【解决方案2】:

这是我尝试过的,它给我带来了有效的结果。因此与大家分享。

select column_name, substr(column_name,1,locate('-',column_name)-1), 
substr(column_name,locate('-',column_name)+1,
length(substr(column_name,locate('-',column_name)+1))) from 
table_name where column_name is not null and column_name!='' 
and column_name like '%-%'

【讨论】:

  • 你好,我需要一个 for 循环,我的 String 是可变的。怎么写?谢谢你
【解决方案3】:

这么晚回答的原因是为了展示更简单和通用的方法来实现目标。它基于使用正则表达式的函数,即使在提出问题时也可用。

获取第 N 个令牌(示例中为第 2 个):

SELECT 
  COL
-- since 9.7 (2009)
, xmlcast(xmlquery('fn:tokenize($s, "-")[2]' passing COL as "s") as varchar(20)) as one
-- since 11.1
, REGEXP_SUBSTR(COL, '([^-]*)-?', 1, 2, '', 1) as two
FROM (VALUES 'CHG-FFH.', 'ABC-DEF-GH') TAB (COL);

结果是:

|COL       |ONE                 |TWO       |
|----------|--------------------|----------|
|CHG-FFH.  |FFH.                |FFH.      |
|ABC-DEF-GH|DEF                 |DEF       |

标记一个字符串:

-- since 9.7 (2009)
SELECT TAB.COL, TOK.SEQ, TOK.TOKEN
FROM 
  (VALUES 'CHG-FFH.', 'ABC-DEF-GH') TAB (COL)
, XMLTABLE
(
  'for $id in tokenize($s, "-") return <i>{string($id)}</i>' PASSING TAB.COL AS "s"
  COLUMNS
    SEQ   FOR ORDINALITY
  , TOKEN VARCHAR(20) PATH '.'
) TOK
ORDER BY TAB.COL, TOK.SEQ;

结果是:

|COL       |SEQ                 |TOKEN               |
|----------|--------------------|--------------------|
|ABC-DEF-GH|1                   |ABC                 |
|ABC-DEF-GH|2                   |DEF                 |
|ABC-DEF-GH|3                   |GH                  |
|CHG-FFH.  |1                   |CHG                 |
|CHG-FFH.  |2                   |FFH.                |

【讨论】:

    【解决方案4】:

    试试这个说法:

    select substr(your_value, 1,3), substr(your_value, 4, 3) from your_table
    

    【讨论】:

    • @彼得:感谢您的回复。您提供的解决方案仅适用于上述值,即 CHG-FFH。但是如果“-”前后的字符不固定,那么可以暗示什么解决方案呢?
    • 试试 josh hull 的解决方案
    【解决方案5】:

    这个答案并没有完全不同,但它以更灵活的方式实现 LOCATE_IN_STRING,这很有用,例如,如果您有不使用函数的限制。

    如您的示例中那样获取由字符“-”分隔的第 n 个字符串:

    SUBSTR(THESTRING, LOCATE_IN_STRING(THESTRING, '-', 1,  n - 1) + 1, 
    LOCATE_IN_STRING(THESTRING||'-', '-', 1,  n) - LOCATE_IN_STRING(THESTRING, '-', 1,  n - 1) - 1) 
                            
    

    如果将每次出现的 n 替换为所需的列号,此构造将返回第 nth 列的内容。


    一些解释:

    SUBSTR要提取的字符串的长度是根据nth(n + 1)th分隔符的出现的差值计算出来的(-在这种情况下)。请注意,(n + 1)th 分隔符位置是根据 THESTRING 加上附加到其末尾的额外分隔符来计算的,以说明当我们查找最后一列并且 THESTRING 不以分隔符结尾的情况.

    【讨论】:

      【解决方案6】:

      我知道这是旧帖子.. 但认为以下可能对其他人有所帮助。

      我使用以下方法来拆分给定的字符串。

      SELECT TRIM(ITEM) AS ITEM FROM TABLE(<LIB1>.SF_SPLIT(I_INPUTLIST=>'AA|BB|CC|DD|EE|EE|FF', I_DELIMITER=>'|')) AS T;
      
      SF_SPLIT is the User defined SQL function and below is definition:
      
      CREATE OR REPLACE FUNCTION <LIB1>.SF_SPLIT(
      
          I_INPUTLIST VARCHAR(8000) 
        , I_DELIMITER VARCHAR(3)    
      
      ) 
      RETURNS TABLE (ITEM VARCHAR(8000))
      
      LANGUAGE SQL
      
      RETURN
      
      WITH R1 (ITEM, REMINDER) AS 
      
      (SELECT SUBSTR(I_INPUTLIST, 1, LOCATE(I_DELIMITER, I_INPUTLIST)-1) AS ITEM, 
      
      SUBSTR(I_INPUTLIST, LOCATE(I_DELIMITER, I_INPUTLIST)+1, LENGTH(I_INPUTLIST)) REMINDER
      
      FROM SYSIBM.SYSDUMMY1
      
      UNION ALL
      
      SELECT SUBSTR(REMINDER, 1, LOCATE(I_DELIMITER, REMINDER)-1) AS ITEM, 
      SUBSTR(REMINDER, LOCATE(I_DELIMITER, REMINDER)+1, LENGTH(REMINDER)) REMINDER 
      
      FROM R1 WHERE LOCATE(I_DELIMITER, REMINDER) > 0
      
      UNION ALL
      
      SELECT SUBSTR(REMINDER, LOCATE(I_DELIMITER, REMINDER)+1, LENGTH(REMINDER)) AS ITEM,
      
      '' AS REMINDER FROM R1 WHERE REMINDER <> '' AND LOCATE(I_DELIMITER, REMINDER) = 0
      
      )
      
      SELECT ITEM FROM R1;
      

      【讨论】:

      • 这对我来说真的很接近工作,但我不得不改变长度部分。如果您的答案适用于其他人,我将单独回答。这可能是我们环境之间的 db2 差异。
      【解决方案7】:

      在 DB2 中

      SELECT
      '11,222,33,444' AS THE_F_STRING
      , SUBSTR('11,222,33,444', 1, LOCATE_IN_STRING('11,222,33,444',',',1,1)-1) AS AA
      , SUBSTR('11,222,33,444', LOCATE_IN_STRING('11,222,33,444',',',1,1)+1, LOCATE_IN_STRING('11,222,33,444',',',1,2)-LOCATE_IN_STRING('11,222,33,444',',',1,1)-1) AS BB 
      , SUBSTR('11,222,33,444', LOCATE_IN_STRING('11,222,33,444',',',1,2)+1, LOCATE_IN_STRING('11,222,33,444',',',1,3)-LOCATE_IN_STRING('11,222,33,444',',',1,2)-1) AS CC
      , SUBSTR('11,222,33,444', LOCATE_IN_STRING('11,222,33,444',',',1,3)+1, LENGTH('11,222,33,444')-LOCATE_IN_STRING('11,222,33,444',',',1,3)) AS DD
      FROM SYSIBM.SYSDUMMY1;
      

      继续推断...享受...

      【讨论】:

        【解决方案8】:

        如果您确定每个子字符串的长度为 3 个字符,则可以尝试此代码,前提是 TABLE1 是一个至少有 X 行的表(在本例中 X = 10):

        select rc, substr(string_to_split, (rc-1)*3+rc, 3) as result from
            (select row_number() over() as rc from TABLE1 fetch first 10 rows only) TB_rowcount
            cross join
            (select 'CHG-FFH' as string_to_split from sysibm.sysdummy1) T2
            where substr(string_to_split, (rc-1)*3+rc, 3) <> '   '
        

        如果子字符串的长度不一样,你必须应用LOCATE函数来查找分隔符

        【讨论】:

          【解决方案9】:

          我还需要使用 instr、substr、trim 和 messed with locate。但是 instr 和 substr 都受支持。你可以找到一个模式。我必须通过 ' - ' 进行 varchar 拆分,并且需要找到结尾并从那里返回。

                     select  itn, 
                     substr(Message, 1 , locate(' - ', Message)) FIRST_SSR,  
                     SUBSTR(Message , instr( message, ' - ', octets)+1, (instr( 
                      message, '(Ref', octets)+1)) SECOND_STR ,
                     Message
                        from
                   (
             select p.itn itn, 
                    substr(p.msg,  instr( p.msg, ' - ' , octets)+21) Message
              from itnpad p
              where p.msg like '%MR - Multiple Requests%'
          
                 ) A 
          

          【讨论】:

            【解决方案10】:

            Suraj 的回答非常适合我,但我需要稍微更改长度逻辑。此时我也有冒号,以便在存储过程之外运行它。

            WITH R1 (ITEM, REMAINDER) AS 
            
            (SELECT SUBSTR(:I_INPUTLIST, 1, LOCATE(:I_DELIMITER, :I_INPUTLIST)-1) AS ITEM, 
            
            SUBSTR(:I_INPUTLIST, LOCATE(:I_DELIMITER, :I_INPUTLIST)+1, LENGTH(:I_INPUTLIST) - LOCATE(:I_DELIMITER, :I_INPUTLIST)) REMAINDER
            
            FROM SYSIBM.SYSDUMMY1
            UNION ALL
            
            SELECT SUBSTR(REMAINDER, 1, LOCATE(:I_DELIMITER, REMAINDER)-1) AS ITEM, 
            SUBSTR(REMAINDER, LOCATE(:I_DELIMITER, REMAINDER)+1, LENGTH(:I_INPUTLIST) - LOCATE(:I_DELIMITER, :I_INPUTLIST)) REMAINDER 
            
            FROM R1 WHERE LOCATE(:I_DELIMITER, REMAINDER) > 0
            
            UNION ALL
            
            SELECT SUBSTR(REMAINDER, LOCATE(:I_DELIMITER, REMAINDER)+1, LENGTH(:I_INPUTLIST) - LOCATE(:I_DELIMITER, :I_INPUTLIST)) AS ITEM,
            
            '' AS REMAINDER FROM R1 WHERE REMAINDER <> '' AND LOCATE(:I_DELIMITER, REMAINDER) = 0
            
            )
            
            SELECT ITEM FROM R1;
            

            【讨论】:

              【解决方案11】:
              CREATE TYPE CUSTOMSTRINGARRAY AS VARCHAR(1000) ARRAY[VARCHAR(1000)];
              create or replace function SPLIT_STRING(inputString varchar(1000),splitor varchar(10), pos int)
              returns VARCHAR(1000)
              ARRAYDEMO: BEGIN
              DECLARE arraySymbols CUSTOMSTRINGARRAY;
              DECLARE out_str,item,str VARCHAR(1000);
              DECLARE i,occ INT;
              SET i = 1;
              set item = '';
              set str = inputString;
              set occ = LENGTH(inputString) - LENGTH(REPLACE(inputString,splitor,''));
              WHILE i <= occ DO
              set item = substr(str,1,LOCATE_IN_STRING(str,splitor,1));
              set str = replace(str,item,'');
              SET arraySymbols[i] = TRIM(replace(item,splitor,''));
              SET i = i + 1;
              END WHILE;
              set arraySymbols[i] = str;
              return(arraySymbols[pos]);
              end;
              

              【讨论】:

                猜你喜欢
                • 2019-09-19
                • 1970-01-01
                • 1970-01-01
                • 2020-04-18
                • 2020-04-11
                • 2011-08-04
                • 2013-10-19
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多