拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ABAP SQL字符串函数详解:用法、踩坑与性能优化

ABAP SQL字符串函数详解:用法、踩坑与性能优化

我最早对ABAP SQL字符串函数“真香”有感觉,是在一次做物料主数据清洗的时候。当时需要把一批物料的旧描述全部替换关键词,还要按长度截取前缀做汇总。按老思路得先把数据全部取回内表,然后循环用CONDENSE、REPLACE、SUBSTRING去处理,少则几十行ABAP,多则上百行,跑起来还慢。后来换用Open SQL自带的字符串函数,几条SELECT语句直接在数据库层完成清洗,代码量直接砍掉一大半,执行效率也明显提升。

如果你还在用ABAP传统方式处理字符串,或者刚接触S/4HANA的新语法,这篇文章应该能帮到你。我会把常用字符串函数的用法、参数细节、踩坑经验、性能注意事项一次说透,并提供可以直接套用的实战案例。内容以SAP NetWeaver 7.40及以上版本(尤其是S/4HANA环境)的Open SQL语法为主,部分写法在不同数据库版本下有差异,我也会特别标注。

1. 为什么ABAP SQL要单独学字符串函数

1.1 传统ABAP内表处理与SQL层处理的本质区别

很多刚接触ABAP的开发者会问:ABAP本身就有丰富的字符串操作语句,比如CONCATENATE、REPLACE、CONDENSE、FIND,为什么还要在SQL里再学一套字符串函数?

核心原因在于“处理发生在哪一层”。

传统做法是:SELECT先把原始数据从数据库“搬运”到应用服务器,再写ABAP语句逐行处理。比如要截取物料号前四位,通常是:

DATA: lt_mara TYPE TABLE OF mara. SELECT matnr INTO TABLE lt_mara FROM mara UP TO 100 ROWS. DATA(lv_prefix) = lt_mara[ 1 ]-matnr(4).

这里有两个问题:一是所有原始数据都要传输到应用层,数据量大时网络和内存开销很高;二是想在SQL里直接限定条件(比如只取物料号前四位为'1000'的物料)做不到,必须取回全量再在ABAP里用IF判断。

而在Open SQL中使用字符串函数,可以把substr(matnr,1,4) = '1000'直接写进WHERE条件,数据库在扫描时已经完成过滤,返回的结果集本身就小。数据过滤越靠近数据源,执行效率越高,这就是工程上常说的“下推”。

1.2 SAP版本演进带来的函数支持变化

SAP的Open SQL语法一直在演进。在旧版本(NetWeaver 7.40之前)里,SQL查询中的字符串操作能力非常有限,大多需要依赖LIKE、CS、CP这些简单的模式匹配,或者直接用ABAP层兜底。

从NetWeaver 7.40开始,SAP引入了新的Open SQL表达式能力,可以在SELECT语句中直接使用CONCAT、SUBSTRING、REPLACE、LEFT、RIGHT、LENGTH、INSTR、LPAD、RPAD等函数。到S/4HANA时代,这些函数已经非常成熟,并且被大量应用于CDS View和AMDP中。

不过有个前提:这些函数最终要转换成底层数据库的SQL,所以实际支持程度取决于你使用的数据库。HANA作为S/4HANA的默认数据库,函数覆盖最全,执行性能也最好;如果是AnyDB(其他数据库),则需要留意个别函数差异,比如某些数据库中没有内置LPAD,SAP会通过运行时转换模拟,性能会有损耗。

2. 核心字符串函数逐一拆解

2.1 拼接类:CONCAT、CONCAT_WS与&&的差异

拼接是日常用得最多的字符串操作。在ABAP SQL里,拼接有三种写法,各有适用场景。

先说最常见的CONCAT函数,它只能接收两个参数:

SELECT matnr, concat( matnr, ' ' ) AS matnr_with_space FROM mara INTO TABLE @DATA(lt_mara) UP TO 10 ROWS.

如果拼接三个字段,就会报参数数量错误。所以要连续拼,得做嵌套:

SELECT matnr, concat( concat( matnr, '-' ), maktx ) AS combined FROM mara AS m INNER JOIN makt AS t ON t-matnr = m-matnr WHERE t-spras = @sy-langu INTO TABLE @DATA(lt_makt).

这种方式写起来略显啰嗦,但兼容性最好。另一种是使用&&运算符,这也是ABAP SQL和CDS中推荐的可读性更高的写法:

SELECT matnr && '-' && maktx AS combined ...

&&可以直接串联多个字段,中间不用嵌套,本质上等价于串联的CONCAT调用。需要特别注意,&&在ABAP中既有字符串拼接的含义,在布尔表达式中又有“逻辑且”的含义,但在SELECT语句的表达式列表里只表示字符串拼接,不会产生歧义。

对于需要加分隔符拼接多个字段的场景,更有用的是CONCAT_WS(With Separator):

SELECT concat_ws( '-', matnr, maktx, 'TEST' ) AS combined

CONCAT_WS第一个参数是分隔符,后面可以跟多个参数。和CONCAT不一样,如果你要拼的字段中有空字符串,CONCAT_WS会自动忽略空值,不会出现“字段A--字段B”这种两个分隔符连续出现的尴尬。

2.2 截取与定位:SUBSTRING、INSTR、LEFT、RIGHT

截取是另一个刚需。ABAP SQL中主要用四类函数:

  • LEFT(str, n):从左边截取n个字符
  • RIGHT(str, n):从右边截取n个字符
  • SUBSTRING(str, pos, len):从位置pos开始截取len个字符
  • INSTR(str, needle):查找needle在str中第一次出现的位置,找不到返回0

举个典型例子:SAP的物料号在很多表中是18位,带有前导零,报表展示时常需要去掉前导零,用SUBSTRING结合REPLACE处理不如直接用REPLACE(ltrim(matnr), '0', '')那么危险(会把中间的0也去掉)。这时更安全的做法是用abap_sql的CAST和SUBSTRING配合,或者用LEFT和RIGHT组合:

SELECT matnr, left( matnr, 4 ) AS head4, right( matnr, 4 ) AS tail4, substring( matnr, 5, 10 ) AS middle FROM mara INTO TABLE @DATA(lt_tmp).

INSTR最大的价值在于,可以写进WHERE条件判断某个标记字符是否存在于字段中。比如查所有含有“_”的物料描述:

SELECT matnr, maktx FROM makt WHERE instr( maktx, '_' ) > 0 INTO TABLE @DATA(lt_has_underscore).

这里有个容易忽视的位置概念:SUBSTRING和INSTR的起始位置都是从1开始计数,不是0,这和我们ABAP中str+offset(len)的0基计数不同。新手最容易在这个地方踩坑。

2.3 替换与补齐:REPLACE、LPAD、RPAD

REPLACE的用法非常直观:

SELECT replace( maktx, 'OLD', 'NEW' ) AS new_text

它会把字段中所有出现的匹配项全部替换,不是只替换第一个。这一点和ABAP语句REPLACE ALL OCCURRENCES一样,要注意语义差异。如果想只替换第一个,Open SQL原生不支持,一般只能先拆再拼,或者干脆在ABAP层处理。

LPAD和RPAD是补齐函数,用于在字符串左侧或右侧填充字符到指定长度。这在生成流水号、条码、批次号时很常用。比如把数字型批次号补足10位,前面补零:

SELECT charx, lpad( charx, 10, '0' ) AS padded FROM t001w

注意:LPAD和RPAD的第三个参数是填充字符,只能指定一个字符,多写会报错。如果不指定,默认是空格。RPAD在实际业务中用得相对少,但在定长文本导出里很有用。

这类函数有几个限制项:

  • 结果长度如果超过原字段的定义长度,在某些数据库上会直接截断或用CAST到目标类型,处理不一致。
  • LPAD在HANA上的原生支持很好,在部分低版本HANA或非HANA数据库下,SAP会转换成其他表达式,可能影响查询计划。

2.4 大小写处理与数字转换:UPPER、LOWER、TO_NUMBER等

大小写转换的标准函数是UPPER和LOWER:

SELECT upper( maktx ) AS upper_text, lower( maktx ) AS lower_text

这在去掉大小写敏感匹配时很有用。比如WHERE upper(maktx) = @p_matnr_upper。

另外经常被忽略的是字符串与数字互转。Open SQL提供CAST表达式,可以把字符串转为数值:

SELECT CAST( charfield AS NUMC( 10 ) ) AS numc_value

但是CAST要求目标类型与源类型兼容,如果字符串里有非数字字符,转换会直接报异常。后来S/4HANA中引入了NUMCDEC、DEC等转换函数,其中TO_NUMBER可以直接把合法数字字符串转为数值:

SELECT to_number( charfield ) AS numeric_value

HANA数据库还支持TO_DECFLOAT、TO_INTEGER等。这里建议优先使用CAST+CASE WHEN做安全保护,这样即使转换失败,也能用默认值兜底。比如:

SELECT CAST( charfield AS NUMC( 5 ) ) AS numeric_value FROM table INTO TABLE @DATA(lt_data) WHERE charfield CO '1234567890'.

注意CO只能判断字符集合,如果有正负号或小数点,可能需要case配合REGEX正则表达式来校验,这是个更稳妥的思路。

3. 在Open SQL中写字符串处理的关键规则与限制

3.1 字符串函数的可用位置与字段类型限制

Open SQL的字符串表达式并不是所有位置都能用。最常用的位置是:

  • SELECT字段列表
  • WHERE条件
  • GROUP BY/ORDER BY
  • HAVING条件(部分函数,取决于数据库)

但不能在GROUP BY中对一个使用了SUBSTRING的字段再直接聚合,比如:

SELECT substring( matnr, 1, 2 ) AS prefix, COUNT(*) AS cnt FROM mara GROUP BY substring( matnr, 1, 2 )

这是允许的,但要求GROUP BY表达式必须和SELECT中的表达式完全一致,包括大小写和空格,否则ABAP语法检查会报“Only SQL expressions are allowed here”之类的错误。

字段类型上需要注意:

  • SUBSTRING、LEFT、RIGHT等截取函数的结果默认是字符串类型,长度派生自源字段,因此后续再拼接时可能遇到长度超出限制的问题。
  • LENGTH函数返回的结果是INT4类型,可以直接参与算术运算。
  • 中文等多字节字符在HANA上按Unicode字符数计算长度,LENGTH返回的是字符个数,不是字节数,和STRLEN在ABAP层的语义一致。

3.2 NULL值传播与空字符串的微妙差异

字符串函数遇到NULL参数时,结果通常是NULL,不是空字符串。这在SAP业务数据中是个大坑,因为SAP很多字段默认是空字符串,而不是数据库层面真正的NULL。

比如说:

SELECT concat( field1, field2 ) FROM table

如果field1是空字符串'',field2是'ABC',结果是'ABC'。没问题。但如果你用LEFT结合CASE WHEN去取一个可能为NULL的字段,结果可能变成NULL,后续往报告里一放,就显示出空白。

所以稳妥写法是提前用COALESCE处理:

SELECT concat( coalesce( field1, '' ), coalesce( field2, '' ) ) AS full_text

SAP表大多没有配置为允许NULL,但接口表、增强字段、视图字段就不好说了。保险起见,在字符串拼接和截取前,对可控字段统一COALESCE或IFNULL处理,可以避免大多数“查出来不对劲”的问题。

3.3 数据库方言与HANA的兼容性

Open SQL之所以叫“Open”,就是因为它需要兼容不同数据库。S4HANA系统通常绑定HANA数据库,但很多ECC系统还在用Oracle、DB2、MaxDB。写字符串函数时必须了解哪些是“SAP Open SQL标准”,哪些是“底层数据库方言”。

SAP官方给出的方向是:标准Open SQL字符串函数可以在任何受支持的数据库上执行,但不同数据库生成的SQL会有差异。如果你在AMDP中直接写Native SQL(HANA SQL)那就完全不受ABAP语法约束,可以用HANA的全部字符串函数,例如LOCATE、REPLACE_REGEXPR等。但AMDP只能用于S4HANA,普通ABAP程序无法跨数据库。

如果是在标准Open SQL中,强烈建议不要尝试写原生数据库函数,比如Oracle的SUBSTR、HANA的SUBSTRING都可以,但REGEXP_REPLACE这种函数在ABAP字典中并不被支持,除非使用CDS View的REPLACE_REGEXPR(这属于另一个体系)。简单说:能用标准函数就用标准函数,别炫技,否则换库后程序直接报数据库不兼容。

4. 实战场景:从取数到报表的字符串清洗

4.1 场景一:物料主数据描述拼接与去空格

业务需求:报表需要显示“物料号-物料描述-单位”,且物料号要去掉前导零,描述里的连续空格压缩为单空格。常规做法要三步,现在用一条SQL:

SELECT t-matnr, concat_ws( '-', replace( ltrim( t-matnr ), ' ', '' ), t-maktx, m-meins ) AS display_text FROM makt AS t INNER JOIN mara AS m ON m-matnr = t-matnr WHERE t-spras = @sy-langu INTO TABLE @DATA(lt_export).

这个写法有几个细节:

  • ltrim用于去掉左侧空格,但物料号前导零还在,所以我用replace( ltrim( matnr ), ' ', '' ),这里空格替换不是针对前导零,而是保险去除字符串内可能的空格。如果只想去除前导零,建议用replace( ltrim( matnr, '0' ), ' ', '' )会把前导零去掉,但ltrim第二参数的写法在Open SQL中并非所有数据库都支持。更通用的做法是:
cast( replace( ltrim( matnr ), ' ', '' ) AS NUMC( 18 ) )

这样数据库会把字符串转成数值,自动去掉前导零,再隐式转回字符串。但要注意,物料号超过18位的场景(比如有些扩展物料号)就无效了。

4.2 场景二:用SQL字符串函数完成内部代码过滤

另一个常见需求:根据外部系统传入的一组“物料号前缀”从标准表中取数。如果前缀列表是ABAP内表,传统的做法是循环拼接WHERE条件,或者用FOR ALL ENTRIES。但使用LEFT在SQL中直接过滤可以更简洁:

SELECT matnr, maktx FROM makt WHERE matnr LIKE @lv_prefix && '%' INTO TABLE @DATA(lt_selected).

这算不上字符串函数,但很有用。更有意思的是结合SUBSTRING做区间匹配。比如取所有物料号中第三位到第五位属于“100”到“199”的物料:

SELECT matnr FROM mara WHERE substring( matnr, 3, 3 ) BETWEEN '100' AND '199' INTO TABLE @DATA(lt_range).

注意:BETWEEN对字符串是按字典序比较的,这一写法适合纯数字字符串。如果是混合字符串,结果可能和预期不同,最好先把数字字符串转成数值再比较:

WHERE cast( substring( matnr, 3, 3 ) AS INT4 ) BETWEEN 100 AND 199

这里CAST到INT4需要确保字符内容都是数字,否则会异常。

4.3 场景三:批次号、序列号智能化解析

批次号在SAP里通常是一个打包了多种信息的字段,比如“装柜号+生产日期+序列号”。假设批次号规则是:前4位是工厂代码,5到8位是年份,9到10位是月份,后面是流水号。想统计2023年以后生产的批次数量,可以写:

SELECT substring( charg, 5, 4 ) AS prod_year, COUNT(*) AS cnt FROM mchb WHERE substring( charg, 5, 4 ) >= '2023' GROUP BY substring( charg, 5, 4 )

这里的GROUP BY用了和SELECT完全相同的表达式,语法没问题。如果要进一步查具体月份对应的数量,把月份也放进分组即可。

序列号解析更复杂,SAP序列号存储在SER01等表中,有时序列号会包含字母段和生产信息。更常用的是用INSTR判断序列号中是否存在分隔符“-”,再结合LEFT/RIGHT拆出前后段:

SELECT sc-serpp AS serial, left( sc-serpp, instr( sc-serpp, '-' ) - 1 ) AS left_part, right( sc-serpp, length( sc-serpp ) - instr( sc-serpp, '-' ) ) AS right_part FROM ser01 AS sc WHERE instr( sc-serpp, '-' ) > 0

instr = 0表示没有分隔符,这时候left_part会计算0 - 1 = -1,可能返回错误或空值,所以务必在WHERE中先排除。这一整套写法在没有REGEXP的情况下,是最容易跨数据库兼容的方案。

5. 常见问题与排查技巧实录

5.1 语法报错:Only SQL expressions are allowed here

这是写ABAP SQL字符串函数时最常见的报错。常常出现在把SUBSTRING或REPLACE结果用作内表工作区赋值,或与普通ABAP变量混用时。

比如有人写:

DATA(lv_prefix) = substring( matnr, 1, 2 ).

这一句放在SELECT外面,语法检查会报错。因为SUBSTRING是SQL表达式,不能在普通ABAP赋值语句中直接调用。正确做法是让它作为SELECT的字段列表的一部分:

SELECT substring( matnr, 1, 2 ) INTO @DATA(lv_prefix) FROM mara ...

另一个常见原因是函数名写错。ABAP SQL的函数名不区分大小写,但参数数量必须一致。比如CONCAT只能有两个参数,写三个参数会报“Wrong number of arguments”。

5.2 Unicode环境下长度计算异常

在SAP Unicode系统(现在基本都是)中,length( field )按字符数返回长度。看起来简单,但和旧非Unicode系统里的“字节长度”概念完全不一样。以前一个中文占2个字节,用strlen可能返回6,现在中文1个字符,返回3。如果报表宽度按字节计算,可能在切分时出现中文乱码。

更隐蔽的情况是SUBSTRING截取时,位置是按字符算的,不会把一个多字节字符截成两半。这实际上是好消息,但要注意的是如果直接把length()结果用于循环计数的边界,就要在ABAP侧做一次字符计数和字节计数的换算,必要时使用cl_abap_conv_out_ce之类的工具。

5.3 性能问题:字符串函数在查询中的效率

字符串函数在WHERE中对字段做处理,会导致该字段上的索引基本失效。比如:

WHERE substring( matnr, 1, 4 ) = '1000'

这种情况下数据库无法直接使用matnr的索引,需要全表扫描后逐行截取再比较。数据量大时,性能就是灾难。更好的做法是保持字段原生形态,使用LIKE '1000%'或范围条件:

WHERE matnr LIKE '1000%'

这样数据库就能走matnr索引的前缀匹配。如果不得不处理中段字符,可以考虑引入冗余字段、CDS View,或者用HANA的全文索引。很多SAP性能顾问在代码审查时看到字符串函数出现在WHERE里,第一反应就是让你改掉,不是没有道理。

如果需要在SELECT列表中对大量行做CONCAT或SUBSTRING,虽然无法用索引加速,但数据量仍然可控时表现并不差。因为函数下推到了数据库层,省去了ABAP侧逐行函数调用的ABAP虚拟机开销。HANA数据库在字符串处理上性能很猛,多数场景下不是瓶颈。

5.4 排查技巧:如何对比ABAP侧与SQL侧结果

我在实际开发中养成了一个习惯:先用一条SQL把字符串函数处理后的结果select到一个临时内表,再和ABAP层用传统方式处理的结果做比较。发现不一致时,优先怀疑三个点:

  1. 是NULL和空字符串差异。
  2. 是字符计数方式差异(比如INSTR是否区分大小写字符)。
  3. 是数据库排序规则影响了BETWEEN和REPLACE的大小写敏感匹配。

比如INSTR在HANA默认区分大小写,但Oracle默认不区分。同一个instr(maktx, 'ABC'),在Oracle中可能把含有“abc”的记录也查出来,在HANA中则不会。如果程序需要跨库,尽量先统一处理大小写,比如把字段和目标字符都转成upper再定位。

这也是我推荐的通用排查流程:先SELECT一小部分数据,分别打印SQL处理结果和ABAP处理结果,字段值一个一个人眼比对,这类问题基本都是几分钟内就能定位。

5.5 常用函数速查参考

函数含义示例注意事项
CONCAT(a, b)两个参数拼接CONCAT(matnr, maktx)只能两个参数,需要多拼用嵌套或&&
CONCAT_WS(sep, a, b, ...)带分隔符拼接CONCAT_WS('-', a, b)自动忽略空字符串
SUBSTRING(str, pos, len)截取子串SUBSTRING(matnr, 1, 3)位置从1开始
INSTR(str, needle)查找位置INSTR(maktx, '_')返回首次出现位置,0为未找到
LEFT(str, n)/RIGHT(str, n)左右截取LEFT(matnr, 4)n为负数或超长行为依数据库而定
REPLACE(str, from, to)全部替换REPLACE(maktx, ' ', '')替换所有出现项,不是第一个
LPAD(str, len, ch)/RPAD填充LPAD(charx, 10, '0')填充字符只能1个
LENGTH(str)长度(字符数)LENGTH(maktx)在Unicode系统按字符数计算
UPPER/LOWER大小写UPPER(maktx)对于非字母字符无影响

把这张表存下来,写SQL时翻一下,能省很多查文档的时间。

最后再分享一个小技巧:如果你要把字符串函数的结果传给后续ABAP逻辑,最好在SELECT里就给字段起一个清晰的别名,并且在INTO后面用内表而不是单字段。比如:

SELECT matnr, upper( maktx ) AS upper_text, length( maktx ) AS text_length FROM makt INTO TABLE @DATA(lt_result).

然后访问lt_result[ 1 ]-upper_text。这样做的好处是,后续调试时可以用ST05或SAT工具直接看SQL缓存里的语句,明确知道数据库真正执行的字符串处理长什么样。调试的次数多了,你会发现很多“数据库行为诡异”的问题,都是对字符串语义理解偏差造成的,并不是SAP本身的bug。

返回列表