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其他数据库则需要留意个别函数差异比如某些数据库中没有内置LPADSAP会通过运行时转换模拟性能会有损耗。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_WSWith SeparatorSELECT concat_ws( -, matnr, maktx, TEST ) AS combinedCONCAT_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中stroffset(len)的0基计数不同。新手最容易在这个地方踩坑。2.3 替换与补齐REPLACE、LPAD、RPADREPLACE的用法非常直观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和LOWERSELECT 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_valueHANA数据库还支持TO_DECFLOAT、TO_INTEGER等。这里建议优先使用CASTCASE 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 BYHAVING条件部分函数取决于数据库但不能在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_textSAP表大多没有配置为允许NULL但接口表、增强字段、视图字段就不好说了。保险起见在字符串拼接和截取前对可控字段统一COALESCE或IFNULL处理可以避免大多数“查出来不对劲”的问题。3.3 数据库方言与HANA的兼容性Open SQL之所以叫“Open”就是因为它需要兼容不同数据库。S4HANA系统通常绑定HANA数据库但很多ECC系统还在用Oracle、DB2、MaxDB。写字符串函数时必须了解哪些是“SAP Open SQL标准”哪些是“底层数据库方言”。SAP官方给出的方向是标准Open SQL字符串函数可以在任何受支持的数据库上执行但不同数据库生成的SQL会有差异。如果你在AMDP中直接写Native SQLHANA 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 场景一物料主数据描述拼接与去空格业务需求报表需要显示“物料号-物料描述-单位”且物料号要去掉前导零描述里的连续空格压缩为单空格。常规做法要三步现在用一条SQLSELECT 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, - ) 0instr 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层用传统方式处理的结果做比较。发现不一致时优先怀疑三个点是NULL和空字符串差异。是字符计数方式差异比如INSTR是否区分大小写字符。是数据库排序规则影响了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。
返回列表