1. 常量
1.1 常量赋值
常量字段可以用来为内表中的部分字段赋初始值,字段类型和长度依据输入常量的值决定
SELECT
mara~matnr, " 物料号
mara~matkl, " 物料组
mara~mtart, " 物料类型
' ' AS lkenz, " 删除标识,常量空字符串
123 AS flag " 常量数字
FROM mara
INTO TABLE @DATA(lt_mara)
UP TO 100 ROWS.
cl_demo_output=>display( lt_mara ).
像上面的删除标识字段就蛮有用的,循环内表有时候需要根据复杂的条件决定该行目是否需要,此时若不需要就打上删除标记’X’,最后再用 delete {table} where lkenz = ‘X’ 一次性删除
2. 宿主变量/表达式
2.1 作为SQL操作数
通常在查询语句中,程序声明(非数据库层级)的变量前需要使用转义符 @ 进行标识,这些宿主变量通常被用作 Open SQL 语句中的操作数
在查询语句内部声明结构/内表时,应该在 DATA 前使用转义符
此外,在使用宿主表达式(在 Open SQL 中作为操作数使用的一些表达式) 时,也需要添加转义字符,如下例所示
注:表达式内部的变量不需要再使用转义符,且不能使用表达式外部的数据库对象
DATA: lv_maktx TYPE makt-maktx VALUE '物料描述'.
SELECT
mara~matnr, " 物料号
mara~matkl, " 物料组
mara~mtart, " 物料类型
@lv_maktx AS maktx, " 物料描述 宿主变量
@( COND char10( WHEN sy-langu = '1' THEN '中文'
WHEN sy-langu = 'E' THEN 'English'
ELSE '其他语言' ) ) AS langu " 物料语言 宿主表达式
FROM mara
INTO TABLE @DATA(lt_mara)
UP TO 100 ROWS.
cl_demo_output=>display( lt_mara ).
3. 聚合表达式
3.1 对一组数据计算
聚合表达式用于对一组值执行计算并返回单一的值,可以使用在SELECT或HAVING子句中,不能用在WHERE子句
WHERE 子句的搜索条件在进行分组操作之前应用;而 HAVING 的搜索条件则在进行分组操作之后应用
常见的聚合表达式如下,表达式内部可选用DISTINCT对数据去重后再进行处理:
- AVG:返回结果集的平均值,返回类型默认为浮点型,可通过AS语句返回指定类型,如DEC,CURR,QUAN或FLTP
- MAX:返回结果集的最大值
- MIN:返回结果集的最小值
- SUM:返回结果集的汇总值
- COUNT:返回结果集的条目数,通常情况下使用COUNT( * ) / COUNT(*),需要使用DISTINCT时则要指定字段名
SELECT
vbeln, " 交货单号
MAX( lfimg ) AS max_lfimg, " 最大交货数量
MIN( lfimg ) AS min_lfimg, " 最小交货数量
AVG( lfimg AS DEC( 14,3 ) ) AS avg_lfimg, " 平均交货数量
SUM( lfimg ) AS sum_lfimg, " 总交货数量
COUNT(*) AS count_lfimg, " 总行目数
AVG( DISTINCT lfimg AS DEC( 14,3 ) ) AS avg_dstct_lfimg, " 去重交货数量
SUM( DISTINCT lfimg ) AS sum_dstct_lfimg, " 去重总交货数量
COUNT( DISTINCT lfimg ) AS count_dstct_lfimg " 去重总行目数
FROM lips ##DB_FEATURE_MODE[TABLE_LEN_MAX1]
WHERE
vbeln BETWEEN '8000000061' AND '8000000069'
GROUP BY vbeln
INTO TABLE @DATA(lt_data).
SORT lt_data BY vbeln.
4. 嵌入表达式
4.1 数值
在 SELECT LIST 使用内嵌表达式来处理数值,使用时需注意传入参数的类型
常见的数值表达式如下:
- ABS:获取绝对值
- CEIL:向上取整
- FLOOR:向下取整
- DIV:除法计算,取整数位
- DIVISION:除法计算,保留 N 位小数
- MOD:除法计算,取余数
- ROUND:计算舍入值
DATA(lv_num) = CONV lfimg( '-123.456' ).
" 简化测试,全部用宿主变量
SELECT SINGLE
@lv_num AS num , " 原始值
ABS( @lv_num ) AS abs_num , " 绝对值
CEIL( @lv_num ) AS ceil_num , " 向上取整
FLOOR( @lv_num ) AS floor_num , " 向下取整
DIV( 5 , 2 ) AS div_num , " 除法,整数
DIVISION( 5 , 2 , 2 ) AS divison_num , " 除法,保留n位小数
MOD( 5 , 2 ) AS mod_num , " 取余
ROUND( @lv_num , 2 ) AS rount_num_po , " 四舍五入,N为正数时,保留小数点后N位
ROUND( @lv_num , -2 ) AS rount_num_ne " 四舍五入,N为负数时,保留小数点前N位
FROM lips ##DB_FEATURE_MODE[TABLE_LEN_MAX1]
INTO @DATA(ls_data).
4.2 字符串
在 SELECT LIST 使用内嵌表达式来处理字符串, 通常情况下字符串返回结果不能超过255个字符,如果字符长度异常,语法检查时会有错误提示
- CONCAT:连接字符串,参数固定为2个,各个表达式之间可以嵌套使用,CONCAT内部也可以使用 &&
- &&:连接字符串,参数没有个数限制,但不能将其他内嵌表达式当作参数使用,仅作为操作符使用,在非SELECT语句中也可以被使用
- CONCAT_WITH_SPACE:连接字符串,并用 N 个空格分隔,该表达式结果不能超过1333个字符
- INSTR:遍历字符串,查找指定字符 s1 并返回第一次出现的位置,没有查到则返回0
- LEFT/RIGHT:从字符串左/右侧开始取出 N 位字符,忽略前导/尾部的空格
- LENGTH:计算字符串长度
SELECT concat( carrid , currcode ) AS concat ,
carrid && currcode AS concat_sign ,
concat_with_space( carrid , currcode , 1 ) AS with_space ,
instr( carrid , 'BA' ) AS instr ,
left( carrname , 4 ) AS left ,
right( carrname , 4 ) AS right ,
length( carrname ) AS length
FROM scarr
INTO TABLE @DATA(lt_data)
UP TO 5 ROWS.
- LOWER/UPPER:将字符串转换成小写/大写
- LPAD/RPAD:在字符串左侧/右侧填充指定字符 s1,直到字符串长度为 N,如果初始值长度>=N,则不会填充,但是超过 N 位的字符会被截断
- LTRIM/RTRIM:从字符串左侧/右侧开始逐个删除指定字符 s1,直到出现其他字符为止,默认会删除尾部的空格
- REPLACE:将字符串中所有的指定字符 s1 用其他字符 s2 代替
- SUBSTRING:从字符串第 N 位开始截取长度为 M 的字符,系统会默认检查截取范围是否超出该字段最大长度,以避免造成DUMP
SELECT lower( carrid ) AS lower,
upper( carrid ) AS upper,
lpad( carrid , 5 , 'B' ) AS lpad,
rpad( carrid , 5 , 'B' ) AS rpad,
ltrim( carrid , 'A' ) AS ltrim,
rtrim( carrid , 'A' ) AS rtrim,
replace( carrid , 'A' , '@' ) AS replace,
substring( carrname , 5 , 10 ) AS substring
FROM scarr
INTO TABLE @DATA(lt_data)
UP TO 3 ROWS.
4.3 日期/时间
在 SELECT LIST 使用内嵌表达式来处理日期/时间
- DATS_IS_VALID/TIMS_IS_VALID:校验日期/时间有效性,有效时返回 1,否则返回 0(时间有效性的函数有报错,可能版本不支持)
- DATS_DAYS_BETWEEN:计算日期d1和d2相隔的天数
- DATS_ADD_DAYS:为指定日期加上N天
- DATS_ADD_MONTHS:为指定日期加上N月
DATA(lv_date) = CONV datum( '20181022' ).
SELECT
fldate AS original_date,
dats_is_valid( fldate ) AS valid_date,
* tims_is_valid( @sy-uzeit ) AS valid_time,
dats_days_between( fldate , @lv_date ) AS between,
dats_add_days( fldate , 10 ) AS add_days,
dats_add_months( fldate , 3 ) AS add_months
FROM sflight
INTO TABLE @DATA(lt_data)
UP TO 3 ROWS.
4.4 时间戳
在 SELECT LIST 使用内嵌表达式来处理时间戳
- TSTMP_IS_VALID:校验时间戳有效性,有效时返回 1,否则返回 0
- TSTMP_CURRENT_UTCTIMESTAMP:返回当前时间戳
- TSTMP_SECONDS_BETWEEN:计算时间戳 t1 和 t2 相隔的秒数,需要用赋值语句进行传参,可以添加相应的错误处理
- TSTMP_ADD_SECONDS:为指定时间戳加上 N 秒,N 必须为 timestamp 类型
DATA(lv_stamp_now) = CONV timestamp( '20190603133559' ).
DATA(lv_stamp_past) = CONV timestamp( '20190602161408' ).
SELECT tstmp_is_valid( @lv_stamp_now ) AS valid_stamp,
tstmp_current_utctimestamp( ) AS current_stamp,
tstmp_seconds_between( tstmp1 = @lv_stamp_past,
tstmp2 = @lv_stamp_now,
on_error = @sql_tstmp_seconds_between=>set_to_null ) AS between,
tstmp_add_seconds( tstmp = @lv_stamp_now,
seconds = @( CONV timestamp( 999 ) ),
on_error = @sql_tstmp_add_seconds=>set_to_null ) AS add_second
FROM sflight
INTO TABLE @DATA(lt_data)
UP TO 1 ROWS.
4.5 时区
ABAP_USER_TIMEZONE:获取用户时区,不传参时默认获取当前用户当前 Client 的时区
ABAP_SYSTEM_TIMEZONE:获取系统时区,不传参时默认获取当前 Client 的时区
SELECT abap_user_timezone( user = @( CONV uname( 'JIANGRE' ) ),
client = '130',
on_error = @sql_abap_user_timezone=>set_to_null ) AS user_zone,
abap_system_timezone( client = '130',
on_error = @sql_abap_system_timezone=>set_to_null ) AS sys_zone
FROM sflight
INTO TABLE @DATA(lt_data)
UP TO 1 ROWS.
4.6 日期/时间转换
在 SELECT LIST 使用内嵌表达式来转换日期/时间/时间戳
- TSTMP_TO_DATS:将时间戳转换成对应时区的日期
- TSTMP_TO_TIMS:将时间戳转换成对应时区的时间
- TSTMP_TO_DST:根据时间戳获取对应时区的夏令时标识
- DATS_TIMS_TO_TSTMP:将日期和时间根据时区转换成时间戳
DATA(lv_stamp) = CONV timestamp( '20190603133559' ).
SELECT tstmp_to_dats( tstmp = @lv_stamp,
tzone = @( CONV tznzone( 'CET' ) ) ) AS dats,
tstmp_to_tims( tstmp = @lv_stamp,
tzone = @( CONV tznzone( 'CET' ) ) ) AS tims,
tstmp_to_dst( tstmp = @lv_stamp,
tzone = @( CONV tznzone( 'CET' ) ) ) AS dst,
dats_tims_to_tstmp( date = @sy-datum,
time = @sy-uzeit,
tzone = @( CONV tznzone( 'CET' ) ) ) AS tstmp
FROM sflight
INTO TABLE @DATA(lt_data)
UP TO 1 ROWS.
5. Case
5.1 条件语句
在 SELECT 语句中使用 CASE 作为条件语句,与一般条件判断使用的 CASE 类似,但有所区别
该语句不仅可以用于单值判断,也可以根据复杂条件进行判断;
此外,WHEN OTHERS 不再适用,需要使用 ELSE 代替,语句结束时使用 END,而不是 ENDCASE,且需要定义别名
SELECT CASE currcode
WHEN 'EUR' THEN carrname
ELSE url
END AS case_simple, " 单值判断
CASE
WHEN currcode = 'EUR' THEN url
WHEN carrname <> ' ' THEN carrname
ELSE carrid && '@' && currcode
END AS case_complex " 复杂条件判断
FROM scarr
INTO TABLE @DATA(lt_data)
UP TO 5 ROWS.
6. NULL Value
在使用 LEFT / RIGHT OUTER JOIN 关联外表时,如果主表中存在记录,但在外表中没有关联到数据,则外表的这部分字段的值在取数过程中始终为 NULL,在取数完成后传入数据对象时,NULL 会再转换成系统兼容的值,通常为初始值;
NULL 值用于数值计算或是字符串处理时返回结果仍为NULL值,可以在条件语句中用 IS [ NOT ] NULL 判断以及处理
DATA: lr_carrid TYPE RANGE OF s_carr_id.
lr_carrid = VALUE #( sign = 'I' option = 'EQ' ( low = 'AA' )
( low = 'CO' ) ).
SELECT DISTINCT
r~carrid,
CASE
WHEN t~seatsocc IS NULL THEN 'IS NULL'
WHEN t~seatsocc IS NOT NULL THEN 'IS NOT NULL'
END AS field_status
FROM scarr AS r
LEFT OUTER JOIN sflight AS t ON t~carrid = r~carrid
INTO TABLE @DATA(lt_data)
WHERE r~carrid IN @lr_carrid.
7. CAST
7.1 类型转换
参照下表进行类型转换
摘自谷歌翻译:
使用“x”的组合没有其他需要注意的限制。 以下先决条件适用于其他组合:
- 对于带“y”的组合,初始长度不能小于目标长度。
- 对于带有“z”的组合,目标长度必须足以表示源中的值。
- 对于带有“c”的组合,初始长度和目标长度必须相等。
在不兼容类型的情况下,操作数的内容将转换为目标类型(如果值不合适,则会引发异常)。 如果表达式的操作数具有空值,则表达式的结果也为空值。
看一个实际转换的案例
DATA(lv_date) = CONV d('20101019').
" 数据库中是存在有效期自字段小于20101219的汇率的
" 但是该表中日期不是正常的d类型,所以需要数据转换后才可查询
" 下述测试无法正常查询出结果
SELECT
gdatu,
ukurs
FROM tcurr
WHERE
gdatu < @lv_date AND
kurst = 'M' AND
fcurr = 'JPY' AND
tcurr = 'EUR'
INTO TABLE @DATA(lt_data).
* cl_demo_output=>display( lt_data ).
" 需要将数据库有效期自字段转换为yyyyMMdd的格式再计算
" 有点怪,为啥不能直接把cast的结果作为where条件查询
SELECT
CAST(
CAST( ( 99999999 -
CAST( (
CAST( gdatu AS NUMC ) ) " char不能直接转int计算,只能转numc
AS INT4 ) ) " 通过numc转int,参与9999999 - datum的运算
AS CHAR ) " int不能直接转dats,先将int转为char
AS DATS ) " char转dats
AS datum,
ukurs
FROM tcurr
WHERE
* datum < @lv_date AND
kurst = 'M' AND
fcurr = 'JPY' AND
tcurr = 'EUR'
INTO TABLE @DATA(lt_data1).
" 没办法将cast的结果用于where,所以最后delete不符合条件数据
" 这个日期字段为啥要搞这么复杂,来个sap大佬解释一下啊
DELETE lt_data1 WHERE datum < lv_date.
cl_demo_output=>display( lt_data1 ).
8. 内表
8.1 作为数据源
在 ABAP 7.52 后,支持将内表作为数据源使用
内表作为数据源使用时,需要定义别名并使用转义符@,该用法可以用来代替 FOR ALL ENTRIES IN,但FROM 语句中最多使用一个内表
内表作为数据源参与join,和for all entries in后loop赋值,两种情况做了简单的对照,从结果来看第一种方式效率更低,即使多次测试结果偶尔会有所接近,但效率始终低于第二种方式
虽然说select内表能替代for all entries in,但是测试结果始终比for all 慢一丢丢
(PS. 用起来真的很方便,后面被技术leader提了说效率不高,让别在项目里面使用之后就没用过了)
测试代码如下所示:
DATA: lv_start TYPE timestampl,
lv_end TYPE timestampl,
lv_res TYPE timestampl.
TYPES:BEGIN OF ty_data,
matnr TYPE mara-matnr,
spras TYPE makt-spras,
mtart TYPE mara-mtart,
matkl TYPE mara-matkl,
maktx TYPE makt-maktx,
lkenz TYPE lkenz,
END OF ty_data.
DATA: lt_data TYPE TABLE OF ty_data,
lt_data1 TYPE TABLE OF ty_data.
" 取mara主表数据,关联makt取语言码以保证join后续内容一致
SELECT
makt~matnr,
mtart,
matkl,
spras
FROM mara
LEFT JOIN makt ON mara~matnr = makt~matnr
INTO CORRESPONDING FIELDS OF TABLE @lt_data.
SORT lt_data BY matnr.
" 第一种方式,For All Entries In
GET TIME STAMP FIELD lv_start.
SELECT
matnr,
spras,
maktx
FROM makt
FOR ALL ENTRIES IN @lt_data
WHERE
matnr = @lt_data-matnr AND
spras = @lt_data-spras
INTO TABLE @DATA(lt_makt).
SORT lt_makt BY matnr.
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<fs_data>).
READ TABLE lt_makt INTO DATA(ls_makt) WITH KEY matnr = <fs_data>-matnr BINARY SEARCH.
IF sy-subrc = 0.
<fs_data>-maktx = ls_makt-maktx.
ENDIF.
ENDLOOP.
GET TIME STAMP FIELD lv_end.
lv_res = lv_end - lv_start.
WRITE / 'FOR ALL ENTRIES IN 方式:' && lv_res.
" 新建内表分组对照
lt_data1 = CORRESPONDING #( lt_data
EXCEPT
maktx ).
" 第二种方式,SELECT 内表
GET TIME STAMP FIELD lv_start.
SELECT
it~*,
makt~maktx
FROM makt
LEFT JOIN @lt_data1 AS it ON it~matnr = makt~matnr AND it~spras = makt~spras ##ITAB_KEY_IN_SELECT ##DB_FEATURE_MODE[ITABS_IN_FROM_CLAUSE]
INTO CORRESPONDING FIELDS OF TABLE @lt_data1.
GET TIME STAMP FIELD lv_end.
lv_res = lv_end - lv_start.
WRITE / 'JOIN内表方式:' && lv_res.
9. JOIN 表达式
在特定的应用场景中,需要使用字符长度不一致的两个字段进行关联时,可以使用相应的表达式处理,但要注意表达式的位置,一般需要放在等式左边,如下例
使用for all entries in之后就无法使用该表达式了,有点鸡肋,因为不是所有时候条件都放在主表里面关联查询的
例:(NAST-OBJKY类型为CHAR30,EKKO-EBELN类型为CHAR10)
SELECT k~ebeln,
t~kschl
FROM nast AS t
INNER JOIN ekko AS k ON left( t~objky, 10 ) = k~ebeln
INTO TABLE @DATA(lt_data).
10. WHERE 条件
常用的条件语句,整理如下:
- [ NOT ] IN:除了 SELECT-OPTION,也支持多个自定义或者通过子查询获取的值
- ANY/SOME/ALL:允许将子查询获取到的结果集作为限制条件使用
- [ NOT ] BETWEEN … AND …:使用范围条件
- [ NOT ] LIKE … [ ESCAPE ]:使用模糊查询
- IS [ NOT ] NULL:判断是否有关联到相应的记录
- IS [ NOT ] INITIAL:判断是否为空值
- [ NOT ] EXISTS:根据指定条件到数据库表查询数据,判断查询结果是否存在
SELECT i~*
FROM scarr AS r
LEFT OUTER JOIN spfli AS i ON i~carrid = r~carrid
WHERE i~carrid IN ( 'AA' , 'CO' )
AND i~carrid IN ( SELECT DISTINCT carrid FROM sflight )
AND i~carrid = ANY ( SELECT DISTINCT carrid FROM sflight )
AND i~carrid NOT BETWEEN 'BA' AND 'CA'
AND ( i~carrid LIKE 'A%' OR i~carrid LIKE 'C%' )
AND r~carrid IS NOT NULL
AND i~carrid IS NOT INITIAL
AND EXISTS ( SELECT carrid FROM sflight WHERE carrid = i~carrid )
INTO TABLE @DATA(lt_data).