常量字段可以用来为内表中的部分字段赋初始值,字段类型和长度依据输入常量的值决定
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’ 一次性删除
通常在查询语句中,程序声明(非数据库层级)的变量前需要使用转义符 @ 进行标识,这些宿主变量通常被用作 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 ).
聚合表达式用于对一组值执行计算并返回单一的值,可以使用在SELECT或HAVING子句中,不能用在WHERE子句
WHERE 子句的搜索条件在进行分组操作之前应用;而 HAVING 的搜索条件则在进行分组操作之后应用
常见的聚合表达式如下,表达式内部可选用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.
在 SELECT LIST 使用内嵌表达式来处理数值,使用时需注意传入参数的类型
常见的数值表达式如下:
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).
在 SELECT LIST 使用内嵌表达式来处理字符串, 通常情况下字符串返回结果不能超过255个字符,如果字符长度异常,语法检查时会有错误提示
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.
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.
在 SELECT LIST 使用内嵌表达式来处理日期/时间
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.
在 SELECT LIST 使用内嵌表达式来处理时间戳
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.
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.
在 SELECT LIST 使用内嵌表达式来转换日期/时间/时间戳
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.
在 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.
在使用 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.
参照下表进行类型转换
摘自谷歌翻译:
使用“x”的组合没有其他需要注意的限制。 以下先决条件适用于其他组合:
在不兼容类型的情况下,操作数的内容将转换为目标类型(如果值不合适,则会引发异常)。 如果表达式的操作数具有空值,则表达式的结果也为空值。
看一个实际转换的案例
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 ).
在 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(). READ TABLE lt_makt INTO DATA(ls_makt) WITH KEY matnr = -matnr BINARY SEARCH. IF sy-subrc = 0. -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.
在特定的应用场景中,需要使用字符长度不一致的两个字段进行关联时,可以使用相应的表达式处理,但要注意表达式的位置,一般需要放在等式左边,如下例
使用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).
常用的条件语句,整理如下:
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).