C、N、D、T、I、F、P、X、string、Xstring
P:默认为8字节,最大允许16字节。最大整数位:16*2 = 32 - 1 = 31 -14(允许最大小数位数) = 17位整数位
类型 |
最大长度(字符数) |
默认长度 |
说明 |
C |
1~262143个字符 |
1 字符 |
|
N |
1~262143个字符 |
1 字符 |
0到9之间字符组成的数字字符串 |
D |
8 个字符 |
|
日期格式必须为 YYYYMMDD |
T |
6 个字符 |
|
格式为 24-hour的 HHMMSS |
I |
4 bytes |
|
-2.147.483.648 to +2.147.483.647 |
F |
8 bytes |
|
小数位最大可以到17位,即可精确到小数点后17位 |
P |
1 to 16 bytes |
8 bytes |
两个数字位压缩后才占一个字节,由于0-9的数字只需要4Bit位,所以一个字节实质上允许存储二位数字,这就是P数据类型为压缩数据类型的由来。并借用半个字节来存储小数点位置、正号、负号相关信息 |
X |
1~524,287 bytes |
1 byte |
十六进制字符 0-9, A-F具体的范围为:00~FF 类型X是十六进制类型,可表示内存字节实际内容,使用两个十六制字符表示一个字节中所存储的内容。但直接打印输出时,输出的还是赋值时字面意义上的值,而不是Unicode解码后的字符 如果未在 DATA 语句中指定参数 注:如果值是字母,则一定要大写 |
是一种压缩的定点数,其数据对象占据内存字节数和数值范围取定义时指定的整个数据大小和小数点后位数,如果不指定小数位,则将视为I类型。其有效数字位大小可以是从1~31位数字(小数点与正负号占用一个位置,半个字节),小数点后最多允许14个数字。
P类型的数据,可用于精确运算(这里的精确指的是存储中所存储的数据与定义时字面上所看到的大小相同,而不存在精度丢失问题——看到的就是内存中实实在在的大小)。在使用P类型时,要先选择程序属性中的选项 Fixed point arithmetic(即定点算法,一般默认选中),否则系统将P类型看用整型。其效率低于I或F类型。
"16 * 2 = 32表示了整个字面意义上允许的最大字面个数,而14表示的是字面上小数点后面允许的最大小数位,而不是指14个字节,只有这里定义时的16才表示16个字节
DATA: p(16) TYPE p DECIMALS 14 VALUE '12345678901234567.89012345678901'.
"正负符号与小数点固定要占用半个字节,一个字面上位置,并包括在这16个字节里面。
"16 * 2 = 32位包括了小数点与在正负号在内
"在定义时字面上允许最长可以达到32位,除去小数点与符号需占半个字节以后
"有效数字位可允许31位,这31位中包括了整数位与小数位,再除去定义时小
"数位为14位外,整数位最多还可达到17位,所以下面最多只能是17个9
DATA: p1(16) TYPE p DECIMALS 14 VALUE '-99999999999999999'.
"P类型是以字符串来表示一个数的,与字符串不一样的是,P类型中的每个数字位只会占用4Bit位,所以两个数字位才会占用一个字节。另外,如果定义时没有指定小数位,表示是整型,但小数点固定要占用半个字节,所以不带小数位与符号的最大与最小整数如下(最多允许31个9,而不是32个)
DATA: p1(16) TYPE p VALUE '+9999999999999999999999999999999'.
DATA: p2(16) TYPE p VALUE '-9999999999999999999999999999999'.
其实P类型是以字符串形式来表示一个小数,这样才可以作到精确,就像Java中要表示一个精确的小数要使用BigDecimal一样,否则会丢失精度。
DATA: p(9) TYPE p DECIMALS 2 VALUE '-123456789012345.12'.
WRITE: / p."123456789012345.12-
DATA: f1 TYPE f VALUE '2.0',
f2 TYPE f VALUE '1.1',
f3 TYPE f.
f3 = f1 - f2."不能精确计算
"2.0000000000000000E+00 1.1000000000000001E+00 8.9999999999999991E-01
WRITE: / f1 , f2 , f3.
DATA: p1 TYPE p DECIMALS 1 VALUE '2.0',
p2 TYPE p DECIMALS 1 VALUE '1.1',
p3 TYPE p DECIMALS 1.
p3 = p1 - p2."能精确计算
WRITE: / p1 , p2 , p3. "2.0 1.1 0.9
Java中精确计算:
publicstaticvoid main(String[] args) {
System.out.println(2.0 - 1.1);// 0.8999999999999999
System.out.println(sub(2.0, 0.1));// 1.9
}
publicstaticdouble sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
透明表(还有其它数据词典中的类型,如结构)即可看作是一种类型,也可看作是对象,所以即可使用TYPE,也可以使用LIKE:
TYPES type6 TYPE mara-matnr.
TYPES type7 LIKE mara-matnr.
DATA obj6 TYPE mara-matnr.
DATA obj7 LIKE mara-matnr.
"SFLIGHT为表类型
DATA plane LIKE sflight-planetype.
DATA plane2 TYPE sflight-planetype.
DATA plane3 LIKE sflight.
DATA plane4 TYPE sflight.
"syst为结构类型
DATA sy1 TYPE syst.
DATA sy2 LIKE syst.
DATA sy3 TYPE syst-index.
DATA sy4 LIKE syst-index.
注:定义的变量名千万别与词典中的类型相同,否则表面上即可使用TYPE也可使用LIKE,就会出现这两个关键字(Type、Like)都可用的奇怪现像,下面是定义一个变量时与词典中的结构同名的后果(导致)
DATA : BEGIN OF address2,
street(20) TYPE c,
city(20) TYPE c,
END OF address2.
DATA obj4 TYPE STANDARD TABLE OF address2."这里使用的实质上是词典中的类型address2
DATA obj5 LIKE STANDARD TABLE OF address2."这里使用是的上面定义的变量address2
上面程序编译通过,按理obj4定义是通过不过的(只能使用LIKE来引用另一定义变量的类型,TYPE是不可以的),但由于address2是数字词典中定义的结构类型,所以obj4使用的是数字词典中的结构类型,而obj5使用的是LIKE,所以使用的是address2变量的类型
DESCRIBE FIELD dobj
[TYPE typ [COMPONENTS com]]
[LENGTH ilen IN {BYTE|CHARACTER} MODE]
[DECIMALS dec]
[OUTPUT-LENGTH olen]
[HELP-ID hlp]
[EDIT MASK mask].
DESCRIBE TABLE itab [KIND knd] [LINES lin] [OCCURS n].
可以使用&或&&将多个字符模板串链接起来,可以突破255个字符的限制,下面两个是等效的:
|...| & |...|
|...| && |...|
如果内容只有字面常量文本(没有变量表达式或控制字符\r \n \t),则不需要使用字符模板,可这样(如果包含了这些控制字符时,会原样输出,所以有这些控制字符时,请使用 |...|将字符包起来):
`...` && `...`
但是上面3个与下面3个是不一样的:
`...` & `...`
'...' & '...'
'...' && '...'
上面前两个还是会受255个字符长度限制,最后一个虽然不受255限制,但尾部空格会被忽略
字面常量文本(literal text)部分,使用 ||括起来,不能含有控制字符(如 \r \n \t这些控制字符),特殊字符 |{ } \需要使用 \进行转义:
txt = |Characters \|, \{, and \} have to be escaped by \\ in literal text.|.
字符串表达式:
str = |{ ( 1 + 1 ) * 2 }|."算术计算表达式
str = |{ |aa| && 'bb' }|."字符串表达式
str = |{ str }|."变量名
str = |{ strlen( str ) }|."内置函数
数据元素是构成结构、表的基本组件,域又定义了数据元素的技术属性。Data element主要附带Search Help、Parameter ID、以及标签描述,而类型是由Domain域来决定的。Domain主要从技术方面描述了Data element,如Data Type数据类型、Output Length输出长度、Convers. Routine转换规则、以及Value Range取值范围
将技术信息从Data element提取出来为Domain域的好处:技术信息形成的Domain可以共用,而每个表字段的业务含意不一样,会导致其描述标签、搜索帮助不一样,所以牵涉到业务部分的信息直接Data element中进行描述,而与业务无关的技术信息部分则分离出来形成Domain
当你在ABAP程序中引用了ABAPDictionary,则预置Dictionary类型则会转换为相应的ABAP类型,预置的Dictionary类型转换规则表如下:
Dictionarytype |
Meaning |
Maximumlengthn |
ABAPtype |
DEC |
Calculation/amountfield |
1-31, 1-17intables |
P((n+1)/2) |
INT1 |
Single-byte integer |
3 |
Internalonly |
INT2 |
Two-byteinteger |
5 |
Internalonly |
INT4 |
Four-byteinteger |
10 |
I |
CURR |
Currencyfield货币字段 |
1-17 |
P((n+1)/2) |
CUKY |
Currencykey货币代码 |
5 |
C(5) |
QUAN |
Amount金额 |
1-17 |
P((n+1)/2) |
UNIT |
Unit单位 |
2-3 |
C(n) |
PREC |
Accuracy |
2 |
X(2) |
FLTP |
Floating pointnumber |
16 |
F(8) |
NUMC |
Numeric text数字字符 |
1-255 |
N(n) |
CHAR |
Character字符 |
1-255 |
C(n) |
LCHR |
Long character |
256-max |
C(n) |
STRING |
Stringofvariable length |
1-max |
STRING. |
RAWSTRING |
Byte sequence of variable length |
1-max |
XSTRING |
DATS |
Date |
8 |
D |
ACCP |
Accounting period YYYYMM |
6 |
N(6) |
TIMS |
Time HHMMSS |
6 |
T |
RAW |
Byte sequence |
1-255 |
X(n) |
LRAW |
Long byte sequence |
256-max |
X(n) |
CLNT |
Client |
3 |
C(3) |
LANG |
Language |
internal 1, external 2 |
C(1) |
这里的“允许最大长度m”表示的是字面上允许的字符位数,而不是指底层所占内存字节数,如
int1的取值为0~255,所以是3位(不包括符号位)
int2的取值为-32768~32767,所以是5位
lLCHR and LRAW类型允许的最大值为INT2 最大值
lRAWSTRING and STRING 具有可变长度,最大值可以指定,但没有上限
lSSTRING 长度是可变的,其最大值必须指定且上限为255。与CHAR类型相比其优势是它与ABAP type string进行映射。
这些预置的Dictionary类型在创建Data element、Domain时可以引用
在Unicode系统中,一个字符占两个字节
SPLIT dobj AT sep INTO { {result1 result2 ...} | {TABLE result_tab} }必须指定足够目标字段。否则,用字段dobj的剩余部分填充最后目标字段并包含分界符;或者使用内表动态接收
SHIFT dobj {[{BY num PLACES}|{UP TO sub_string}][[LEFT|RIGHT][CIRCULAR]]}
| { {LEFT DELETING LEADING}|{RIGHT DELETING TRAILING} } pattern
对于固定长度字符串类型,shift产生的空位会使用空格或十六进制的0(如果为X类型串时)来填充
向右移动时前面会补空格,固定长度类型字符串与String结果是不一样:String类型右移后不会被截断,只是字串前面补相应数量的空格,但如果是C类型时,则会截断;左移后后面是否被空格要看是否是固定长度类型的字符串还是变长的String类型串,左移后C类型会补空格,String类型串不会(会缩短)
CIRCULAR:将移出的字符串放在左边或者左边
pattern:只要前导或尾部字符在指定的pattern字符集里就会被去掉,直到第一个不在模式pattern的字符止
CONDENSE
CONCATENATE {dobj1 dobj2 ...}|{LINES OF itab}[kənˈkatɪneɪt]
INTO result
[SEPARATED BY sep]
[RESPECTING BLANKS].
CDNT类型的前导空格会保留,尾部空格都会被去掉,但对String类型所有空格都会保留;对于c, d, n, t类型的字符串有一个RESPECTING BLANKS选项可使用,表示尾部空格也会保留。注:使用 `` 对String类型进行赋值时才会保留尾部空格 字符串连接可以使用 && 来操作,具体请参考这里
strlen(arg)、Xstrlen(arg)String类型的尾部空格会计入字符个数中,但C类型的变量尾部空格不会计算入
substring( val = TEXT [off = off] [len = len] )
count( val = TEXT {sub = substring}|{regex = regex} )匹配指定字符串substring或正则式regex出现的子串次数,返回的类型为i整型类型
contains( val = TEXT REGEX = REGEX)是否包含。返回布尔值,注:只能用在if、While等条件表达式中
matches( val = TEXT REGEX = REGEX)regex表达式要与text完全匹配,这与contains是不一样的。返回布尔值,也只能用在if、While等条件表达式中
match( val = TEXT REGEX = REGEX occ = occ)返回的为匹配到的字符串。注:每次只匹配一个。occ:表示需匹配到第几次出现的子串。如果为正,则从头往后开始计算,如果为负,则从尾部向前计算
find( val = TEXT {sub = substring}|{regex = regex}[occ = occ] )查找substring或者匹配regex的子串的位置。如果未找到,则返回-1,返回的为offset,所以从0开始
FIND ALL OCCURRENCES OF REGEX regex IN dobj
[MATCH COUNT mcnt] 成功匹配的次数
{ {[MATCH OFFSET moff][MATCH LENGTH mlen]}最后一次整体匹配到的串(整体串,最外层分组,而不是指正则式最内最后一个分组)起始位置与长度
| [RESULTS result_tab|result_wa] } result_tab接收所有匹配结果,result_wa只能接收最后一次匹配结果
[SUBMATCHES s1 s2 ...].通常与前面的MATCH OFFSET/ LENGTH一起使用。只会接收使用括号进行分组的子组。如果变量s1 s2 ...比分组的数量多,则多余的变量被initial;如果变量s1 s2 ...比分组的数量少,则多余的分组将被忽略;且只存储第一次或最后一次匹配到的结果
replace( val = TEXT REGEX = REGEX WITH = NEW)使用new替换指定的子符串,返回String类型
REPLACE ALL OCCURRENCES OF REGEX regex IN dobj WITH new
DATA: text TYPE string VALUE `Cathy's cat with the hat sat on Matt's mat.`,
regx TYPE string VALUE `\<.at\>`."\< 单词开头,\> 单词结尾
DATA: counts TYPE i,
index TYPE i,
substr TYPE string.
WRITE / text.
NEW-LINE.
counts = count( val = text regex = regx )."返回匹配次数
DO counts TIMES.
index = find( val = text regex = regx occ = sy-index )."返回匹配到的的起始位置索引
substr = match( val = text regex = regx occ = sy-index )."返回匹配到的串
index = index + 1.
WRITE AT index substr.
ENDDO.
DATA: moff TYPE i,
mlen TYPE i,
s1 TYPE string,
s2 TYPE string,
s3 TYPE string,
s4 TYPE string.
FIND ALL OCCURRENCES OF REGEX `((\w+)\W+\2\W+(\w+)\W+\3)`"\2 \3 表示反向引用前面匹配到的第二与第三个子串
IN `Hey hey, my my, Rock and roll can never die Hey hey, my my`"会匹配二次,但只会返回第二次匹配到的结果,第一次匹配到的子串不会存储到s1、s2、s3中去
IGNORING CASE
MATCH OFFSET moff
MATCH LENGTH mlen
SUBMATCHES s1 s2 s3 s4."根据从外到内,从左到右的括号顺序依次存储到s1 s2…中,注:只取出使用括号括起来的子串,如想取整体子串则也要括起来,这与Java不同
WRITE: / s1, / s2,/ s3 ,/ s4,/ moff ,/ mlen."s4会被忽略
DATA: result TYPE STANDARD TABLE OF string WITH HEADER LINE .
"与Java不同,只要是括号括起来的都称为子匹配(即使用整体也用括号括起来了),
"不管括号嵌套多少层,统称为子匹配,且匹配到的所有子串都会存储到,
"MATCH_RESULT-SUBMATCHES中,即使最外层的括号匹配到的子串也会存储到SUBMATCHES
"内表中。括号解析的顺序为:从外到内,从左到右的优先级顺序来解析匹配结构。
"Java中的group(0)存储的是整体匹配串,即使整体未(或使用)使用括号括起来
PERFORM get_match TABLES result
USING '2011092131221032' '(((\d{2})(\d{2}))(\d{2})(\d{2}))'.
LOOP AT result .
WRITE: / result.
ENDLOOP.
FORM get_match TABLES p_result"返回所有分组匹配(括号括起来的表达式)
USING p_str
p_reg.
DATA: result_tab TYPE match_result_tab WITH HEADER LINE.
DATA: subresult_tab TYPE submatch_result_tab WITH HEADER LINE.
"注意:带表头时 result_tab 后面一定要带上中括号,否则激活时出现奇怪的问题
FIND ALL OCCURRENCES OF REGEX p_reg IN p_str RESULTS result_tab[].
"result_tab中存储了匹配到的子串本身(与Regex整体匹配的串,存储在
"result_tab-offset、result_tab-length中)以及所子分组(括号部分,存储在
"result_tab-submatches中)
LOOP AT result_tab .
"如需取整体匹配到的子串(与Regex整体匹配的串),则使用括号将整体Regex括起来
"来即可,括起来后也会自动存储到result_tab-submatches,而不需要在这里像这样读取
* p_result = p_str+result_tab-offset(result_tab-length).
* APPEND p_result.
subresult_tab[] = result_tab-submatches.
LOOP AT subresult_tab.
p_result = p_str+subresult_tab-offset(subresult_tab-length).
APPEND p_result.
ENDLOOP.
ENDLOOP.
ENDFORM.
regex = Regular expression [ˈreɡjulə]
cl_abap_regex:与Java中的 java.util.regex.Pattern的类对应
cl_abap_matcher:与Java中的 java.util.regex.Matcher的类对应
是否完全匹配(正则式中不必使用 ^ 与 $);matches为静态方法,而match为实例方法,作用都是一样
DATA: matcher TYPE REF TO cl_abap_matcher,
match TYPE match_result,
match_line TYPE submatch_result.
"^$可以省略,因为matches方法本身就是完全匹配整个Regex
IF cl_abap_matcher=>matches( pattern = '^(db(ai).*)$' text = 'dbaiabd' ) = 'X'.
matcher = cl_abap_matcher=>get_object( )."获取最后一次匹配到的 Matcher 实例
match = matcher->get_match( ). "获取最近一次匹配的结果(注:是整体匹配的结果)
WRITE / matcher->text+match-offset(match-length).
LOOP AT match-submatches INTO match_line. "提取子分组(括号括起来的部分)
WRITE: /20 match_line-offset, match_line-length,matcher->text+match_line-offset(match_line-length).
ENDLOOP.
ENDIF.
DATA: matcher TYPE REF TO cl_abap_matcher,
match TYPE match_result,
match_line TYPE submatch_result.
"^$可以省略,因为matche方法本身就是完全匹配整个Regex
matcher = cl_abap_matcher=>create( pattern = '^(db(ai).*)$' text = 'dbaiabd' ).
IF matcher->match( ) = 'X'.
match = matcher->get_match( ). "获取最近一次匹配的结果
WRITE / matcher->text+match-offset(match-length).
LOOP AT match-submatches INTO match_line. "提取子分组(括号括起来的部分)
WRITE: /20 match_line-offset, match_line-length,matcher->text+match_line-offset(match_line-length).
ENDLOOP.
ENDIF.
是否包含(也可在正则式中使用 ^ 与 $ 用于完全匹配检查,或者使用 ^ 检查是否匹配开头,或者使用 $ 匹配结尾)
DATA: matcher TYPE REF TO cl_abap_matcher,
match TYPE match_result,
match_line TYPE submatch_result.
IF cl_abap_matcher=>contains( pattern = '(db(ai).{2}b)' text = 'dbaiabddbaiabb' ) = 'X'.
matcher = cl_abap_matcher=>get_object( ). "获取最后一次匹配到的 Matcher 实例
match = matcher->get_match( ). "获取最近一次匹配的结果
WRITE / matcher->text+match-offset(match-length).
LOOP AT match-submatches INTO match_line. "提取子分组(括号括起来的部分)
WRITE: /20 match_line-offset, match_line-length,matcher->text+match_line-offset(match_line-length).
ENDLOOP.
ENDIF.
一次性找出所有匹配的子串,包括子分组(括号括起的部分)
DATA: matcher TYPE REF TO cl_abap_matcher,
match_line TYPE submatch_result,
itab TYPE match_result_tab WITH HEADER LINE.
matcher = cl_abap_matcher=>create( pattern = '<[^<>]*(ml)>' text = 'hello' )."创建 matcher 实例
"注:子分组存储在itab-submatches字段里
itab[] = matcher->find_all( ).
LOOP AT itab .
WRITE: / matcher->text, itab-offset, itab-length,matcher->text+itab-offset(itab-length).
LOOP AT itab-submatches INTO match_line. "提取子分组(括号括起来的部分)
WRITE: /20 match_line-offset, match_line-length,matcher->text+match_line-offset(match_line-length).
ENDLOOP.
ENDLOOP.
逐个找出匹配的子串,包括子分组(括号括起的部分)
DATA: matcher TYPE REF TO cl_abap_matcher,
match TYPE match_result, match_line TYPE submatch_result,
itab TYPE match_result_tab WITH HEADER LINE.
matcher = cl_abap_matcher=>create( pattern = '<[^<>]*(ml)>' text = 'hello' ).
WHILE matcher->find_next( ) = 'X'.
match = matcher->get_match( )."获取最近一次匹配的结果
WRITE: / matcher->text, match-offset, match-length,matcher->text+match-offset(match-length).
LOOP AT match-submatches INTO match_line. "提取子分组(括号括起来的部分)
WRITE: /20 match_line-offset, match_line-length,matcher->text+match_line-offset(match_line-length).
ENDLOOP.
ENDWHILE.
DATA: matcher TYPE REF TO cl_abap_matcher,
length TYPE i,offset TYPE i,
submatch TYPE string.
matcher = cl_abap_matcher=>create( pattern = '(<[^<>]*(ml)>)' text = 'hello' ).
WHILE matcher->find_next( ) = 'X'. "循环2次
"为0时,表示取整个Regex匹配到的子串,这与Java一样,但如果整个Regex使用括号括起来后,
"则分组索引为1,这又与Java不一样(Java不管是否使用括号将整个Regex括起来,分组索引号都为0)
"上面Regex中共有两个子分组,再加上整个Regex为隐含分组,所以一共为3组
DO 3 TIMES.
"在当前匹配到的串(整个Regex相匹配的串)中返回指定子分组的匹配到的字符串长度
length = matcher->get_length( sy-index - 1 ).
"在当前匹配到的串(整个Regex相匹配的串)中返回指定子分组的匹配到的字符串起始位置
offset = matcher->get_offset( sy-index - 1 ).
"在当前匹配到的串(整个Regex相匹配的串)中返回指定子分组的匹配到的字符串
submatch = matcher->get_submatch( sy-index - 1 ).
WRITE:/ length , offset,matcher->text+offset(length),submatch.
ENDDO.
SKIP.
ENDWHILE.
DATA: matcher TYPE REF TO cl_abap_matcher,
count TYPE i,
repstr TYPE string.
matcher = cl_abap_matcher=>create( pattern = '<[^<>]*>' text = 'hello' ).
count = matcher->replace_all( ``)."返回替换的次数
repstr = matcher->text. "获取被替换后的新串
WRITE: / count , repstr.
内表:如果使用有表头行的内表,CLEAR 仅清除表格工作区域。要重置整个内表而不清除表格工作区域,使用REFRESH语句或 CLEAR 语句CLEAR
以上都不会释放掉内表所占用的空间,如果想初始化内表的同时还要释放所占用的空间,请使用:FREE
报表程序中选择屏幕事件块(AT SELECTION-SCREEN)与逻辑数据库事件块、以及methods(类中的方法)、subroutines(FORM子过程)、function modules(Function函数)中声明的变量为局部的,即在这些块里声明的变量不能在其他块里使用,但这些局部变量可以覆盖同名的全局变量;除这些处理块外,其他块里声明的变量都属于全局的(如报表事件块、列表事件块、对话Module),效果与在程序最开头定义的变量效果是一样的,所以可以在其他处理块直接使用(但要注意的是,需遵守先定义后使用的原则,这种先后关系是从语句书写顺序来说的,与事件块的本身运行顺序没有关系);另外,局部变量声明时,不管在处理块的任何地方,其效果都是相当于处理块里的全局变量,而不像其他语言如Java那样:局部变量的作用域可以存在于任何花括号{}之间(这就意味着局部变量在处理过程范围内是全局的),如下面的i,在ABAP语言中还是会累加输出,而不会永远是1(在Java语言中会是1):
FORM aa.
DO 10 TIMES.
DATA: i TYPE i VALUE 0.
i = i + 1.
WRITE: / i.
ENDDO.
ENDFORM.
Form、Function中的TABLES参数,TYPE与LIKE后面只能接标准内表类型或标准内表对象,如果要使用排序内表或者哈希内表,则只能使用USING(Form)与CHANGING方式来代替。当把一个带表头的实参通过TABLES参数传递时,表头也会传递过去,如果实参不带表头或者只传递了表体(使用了[]时),系统会自动为内表参数变量创建一个局部空的表头
不管是以TABLES还是以USING(Form)非值、CHANGE非值方式传递时,都是以引用方式(即别名,不是指地址,注意与Java中的传引用区别:Java实为传值,但传递的值为地址的值,而ABAP中传递的是否为地址,则要看实参是否是通过Type ref to定义的)传递;但如果USING值传递,则对形参数的修改不会改变实参,因为此时不是引用传递;但如果CHANGE值传递,对形参数的修改还是会改变实参,只是修改的时机在Form执行或Function执行完后,才去修改
Form中通过引用传递时,USING与CHANGING完全一样;但CHANGING为值传递方式时,需要在Form执行完后,才去真正修改实参变量的内容,所以CHANGING传值与传引用其结果都是一样:结果都修改了实参内容,只是修改的时机不太一样而已
FORM subr [TABLES t1 [{TYPE itab_type}|{LIKE itab}|{STRUCTURE struc}]
t2 […]]
[USING { VALUE(p1)|p1 } [ { TYPE generic_type }
| { LIKE
| { TYPE {[LINE OF] complete_type}|{REF TO type} }
| { LIKE {[LINE OF] dobj} | {REF TO dobj} }
| STRUCTURE struc]
{ VALUE(p2)|p2 } […]]
[CHANGING{ VALUE(p1)|p1 } [ { TYPE generic_type }
| { LIKE
| { TYPE {[LINE OF] complete_type} | {REF TO type} }
| { LIKE {[LINE OF] dobj} | {REF TO dobj} }
| STRUCTURE struc]
{ VALUE(p2)|p2 } […]]
[RAISING {exc1|RESUMABLE(exc1)} {exc2|RESUMABLE(exc2)} ...].
generic_type:为通用类型
complete_type:为完全限制类型
generic_para:为另一个形式参数类型,如下面的 b 形式参数
DATA: d(10) VALUE'11'.
FIELD-SYMBOLS:
ASSIGN d TO
PERFORM aa USING
FORM aa USING fs like
WRITE:fs,/ a , / b.
ENDFORM.
如果没有给形式参数指定类,则为ANY类型
如果TABLES与USING、CHANGING一起使用时,则一定要按照TABLES、USING、CHANGING顺序声明
值传递中的VALUE关键字只是在FORM定义时出现,在调用时PERFORM语句中无需出现,也就是说,调用时值传递和引用传递不存在语法格式差别
DATA : i TYPE i VALUE 100.
WRITE: / 'frm_ref===='.
PERFORM frm_ref USING i .
WRITE: / i."200
WRITE: / 'frm_val===='.
i = 100.
PERFORM frm_val USING i .
WRITE: / i."100
WRITE: / 'frm_ref2===='.
"不能将下面的变量定义到frm_ref2过程中,如果这样,下面的dref指针在调用frm_ref2 后,指向的是Form中局部变量内存,为不安全发布,运行会抛异常,因为From结束后,它所拥有的所有变量内存空间会释放掉
DATA: i_frm_ref2 TYPE i VALUE 400.
i = 100.
DATA: dref TYPE REF TO i .
get REFERENCE OF i INTO dref.
PERFORM frm_ref2 USING dref ."传递的内容为地址,属于别名引用传递
WRITE: / i."4000
field-SYMBOLS :
ASSIGN dref->* to
WRITE: /
WRITE: / 'frm_val2===='.
i = 100.
DATA: dref2 TYPE REF TO i .
get REFERENCE OF i INTO dref2.
PERFORM frm_val2 USING dref2 .
WRITE: / i."4000
ASSIGN dref2->* to
WRITE: /
FORM frm_ref USING p_i TYPE i ."C++中的引用参数传递:p_i为实参i的别名
WRITE: / p_i."100
p_i = 200."p_i为参数i的别名,所以可以直接修改实参
ENDFORM.
FORM frm_val USING value(p_i)."传值:p_i为实参i的拷贝
WRITE: / p_i."100
p_i = 300."由于是传值,所以不会修改主调程序中的实参的值
ENDFORM.
FORM frm_ref2 USING p_i TYPE REF TO i ."p_i为实参dref的别名,类似C++中的引用参数传递(传递的内容为地址,并且属于别名引用传递)
field-SYMBOLS :
"现在
ASSIGN p_i->* to
WRITE: /
DATA: dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
"由于USING为C++的引用参数,所以这里修改的直接是实参所存储的地址内容,这里的p_i为传进来的dref的别名,是同一个变量,所以实参的指向也发生了改变(这与Java中传递引用是不一样的,Java中传递引用时为地址的拷贝,即Java中永远也只有传值,但C/C++/ABAP中可以传递真正引用——别名)
p_i = dref."此处会修改实参的指向
ENDFORM.
FORM frm_val2 USING VALUE(p_i) TYPE REF TO i ."p_i为实参dref2的拷贝,类似Java中的引用传递(虽然传递的内容为地址,但传递的方式属于地址拷贝——值传递)
field-SYMBOLS :
"现在
ASSIGN p_i->* to
WRITE: /
DATA: dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
"这里与过程 frm_ref2 不一样,该过程 frm_val2 参数的传递方式与java中的引用传递是原理是一样的:传递的是地址拷贝,所以下面不会修改主调程序中实参dref2的指向,它所改变的只是拷贝过来的Form中局部形式参数的指向
p_i = dref.
ENDFORM.
当使用Function Builder创建函数组时,系统会自动创建main program与相应的include程序:
l
lSAPL
lL
lL
lL
lL
当你调用一个function module时,系统加将整个function group(包括Function Module、Include文件等)加载到主调程序所在的internal session中,然后该Function Module得到执行,该Function Group一直保留在内存中,直到internal session结束。Function Group中的所定义的Include文件中的变量是全局,被所有Function Module共享,所以Function Group好比Java中的类,而Function Module则好比类中的方法,所以Function Group中的Include文件中定义的东西是全局型的,能被所有Function Module所共享使用
function fuc_ref .
*"-------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(I_I1) TYPE IREFERENCE(别名)为参数的默认传递类型
*" VALUE(I_I2) TYPE I 定义时勾选了Pass Value选项才会是 VALUE类型
*" REFERENCE(I_I3) TYPE REF TO I
*" VALUE(I_I4) TYPE REF TO I
*" EXPORTING
*" REFERENCE(E_I1) TYPE I
*" VALUE(E_I2) TYPE I
*" REFERENCE(E_I3) TYPE REF TO I
*" VALUE(E_I4) TYPE REF TO I
*" TABLES
*" T_1 TYPE ZJZJ_ITAB
*" CHANGING
*" REFERENCE(C_I1) TYPE I
*" VALUE(C_I2) TYPE I
*" REFERENCE(C_I3) TYPE REF TO I
*" VALUE(C_I4) TYPE REF TO I
*"-------------------------------------------------------------------
write: / i_i1."1
"由于i_i1为输入类型参数,且又是引用类型,实参不能被修改。这里i_i1是以C++中的引用(别名)参数方式传递参数,所以如果修改了i_i1就会修改实际参数,所以函数中不能修改REFERENCE 的 IMPORTING类型的参数,如果去掉下面注释则编译出错
"i_i1 = 10.
write: / i_i2."2
"虽然i_i2是输入类型的参数,但不是引用类型,所以可以修改,编译能通过但不会修改外面实参的值,只是修改了该函数局部变量的值
i_i2 = 20.
field-symbols:
assign i_i3->* to
"由于i_i3存储的是地址,所以先要解引用再能使用
write: /
"同上面,REFERENCE的IMPORTING类型的参数不能被修改:这里即不能修改实参的指向"GET REFERENCE OF 30 INTO i_i3."虽然不可以修改实参的指向,但可以修改实参所指向的实际内容
assign i_i4->* to
"i_i4存储也的是地址,所以先要解引用再能使用
write: /
"虽然i_i4是输入类型的参数,但不是引用类型,所以可以修改,只会修改函数中的局部参数i_i4的指向,但并不会修改实参的指向
get reference of 40 into i_i4.
"虽然不能修改实参的指向,但可以直接修改实参的所指向的实际内容
WRITE: / c_i1."111
"c_i1为实参的别名,修改形参就等于修改实参内容
c_i1 = 1110.
WRITE: / c_i2."222
"c_i2为实参的副本,所以不会影响实参的内容,但是,由于是CHANGING类型的参数,且为值传递,在函数正常执行完后,还是会将该副本再次拷贝给实参,所以最终实参还是会被修改
c_i2 = 2220.
ENDFUNCTION.
调用程序:
DATA: i_i1 TYPE i VALUE 1,
i_i2 TYPE i VALUE 2,
i_i3 TYPE REF TO i ,
i_i4 TYPE REF TO i ,
c_i1 TYPE i VALUE 111,
c_i2 TYPE i VALUE 222,
c_i3 TYPE REF TO i ,
c_i4 TYPE REF TO i ,
t_1 TYPE zjzj_itab WITH HEADER LINE.
DATA: i_i3_ TYPE i VALUE 3.
GET REFERENCE OF i_i3_ INTO i_i3.
DATA: i_i4_ TYPE i VALUE 4.
GET REFERENCE OF i_i4_ INTO i_i4.
DATA: c_i3_ TYPE i VALUE 333.
GET REFERENCE OF c_i3_ INTO c_i3.
DATA: c_i4_ TYPE i VALUE 444.
GET REFERENCE OF c_i4_ INTO c_i4.
CALL FUNCTION 'FUC_REF'
EXPORTING
i_i1 = i_i1
i_i2 = i_i2
i_i3 = i_i3
i_i4 = i_i4
TABLES
t_1 = t_1
CHANGING
c_i1 = c_i1
c_i2 = c_i2
c_i3 = c_i3
c_i4 = c_i4.
WRITE: / i_i2."2
WRITE: / i_i3_."30
WRITE: / i_i4_."400
WRITE: / c_i1."1110
WRITE: / c_i2."2220
字段符号可以看作仅是已经被解引用的指针(类似于C语言中带有解引用操作符 * 的指针),但更像是C++中的引用类型(int i ;&ii= i;),即某个变量的别名,它与真正的指针还是有很大的区别的,在ABAP中引用变量(通过TYPE REF TO定义的变量)才好比C语言中的指针
ASSIGN ... TO
TYPES: BEGIN OF t_date,
year(4) TYPE n,
month(2) TYPE n,
day(2) TYPE n,
END OF t_date.
FIELD-SYMBOLS
ASSIGN sy-datum TO
DATA txt(8) TYPE c VALUE '19980606'.
FIELD-SYMBOLS
ASSIGN txt TO
请参考动态语句à ASSIGN 动态分配
UNASSIGN:该语句是初始化
CLEAR:与UNASSIGN不同的是,只有一个作用就是初始化它所指向的内存区域,而不是解除分配
TYPE REF TO data 数据引用data references
TYPE REF TO object 对象引用object references
除了object,所有的通用类型都能直接用TYPE后面(如TYPE data,但没有TYPE object,object不能直接跟在TYPE后面,只能跟在TYPE REF TO后面)
TYPE REF TO 后面可接的通用类型只能是data(数据引用)或者是object(对象引用)通用类型,其他通用类型不行
DATA: dref TYPE REF TO i ."dref即为数据引用,即数据指针,指向某个变量或常量,存储变量地址
CREATE DATA dref.
dref->* = 2147483647."可直接解引用使用,不需要先通过分配给字段符号后再使用
DATA: BEGIN OF strct,
c,
END OF strct.
DATA: dref LIKE REF TO strct .
CREATE DATA dref .
dref->*-c = 'A'.
TYPES: tpy TYPE c.
DATA: c1 TYPE REF TO tpy.
DATA: c2 LIKE REF TO c1."二级指针
GET REFERENCE OF 'a' INTO c1.
GET REFERENCE OF c1 INTO c2.
WRITE: c2->*->*."a
CLASS cl DEFINITION.
PUBLIC SECTION.
DATA: i VALUE 1.
ENDCLASS.
CLASS cl IMPLEMENTATION.
ENDCLASS.
DATA: obj TYPE REF TO cl.
CREATE OBJECT obj. "创建对象
DATA: oref LIKE REF TO obj. "oref即为对象引用,即对象指针,指向某个对象,存储对象地址
GET REFERENCE OF obj INTO oref. "获取对象地址
WRITE: oref->*->i."1
DATA: e_i3 TYPE REF TO i .
GET REFERENCE OF 33 INTO e_i3.
WRITE: e_i3->*."33
"但不能修改常量的值
"e_i3->* = 44.
DATA: i TYPE i VALUE 33,
dref LIKE REF TO i."存储普通变量的地址
GET REFERENCE OF i INTO dref.
dref->* = 44.
WRITE: i. "44
SORT itab BY (comp1)...(compn)
READ TABLE itab WITH KEY(k1)=v1...(kn)=vn
READ TABLE itab...INTOwaCOMPARING(comp1)...(compn) TRANSPORTING(comp1)...
MODIFY [TABLE] itab TRANSPORTING(comp1)...(compn)
DELETE TABLEitabWITH TABLE KEY(comp1)...(compn)
DELETE ADJACENT DUPLICATES FROM itab COMPARING(comp1)...(compn)
AT NEW/END OF (comp)
CREATE DATA ... TYPE (type)...
DATA: a TYPE REF TO i.
CREATE DATA a TYPE ('I').
a->* = 1.
CREATE OBJECT ... TYPE (type)...请参考类对象反射章节
Select请参照后面的动态SQL
MODIFY/UPDATE(dbtab)...
CALL METHOD (meth_name)
| cref->(meth_name)
| iref->(meth_name)
| (class_name)=>(meth_name)
| class=>(meth_name)
| (class_name)=>meth
实例请参考类对象反射章节
FIELD-SYMBOLS:
DATA:str(20) TYPE c VALUE 'Output String',
name(20) TYPE c VALUE 'STR'.
"静态分配:编译时就知道要分配的对象名
ASSIGN name TO
"通过变量名动态访问变量
ASSIGN (name) TO
DATA: BEGIN OF line,
col1 TYPE i VALUE '11',
col2 TYPE i VALUE '22',
col3 TYPE i VALUE '33',
END OF line.
DATA comp(5) VALUE 'COL3'.
FIELD-SYMBOLS:
ASSIGN line TO
ASSIGN comp TO
"还可以直接使用以下的语法访问其他程序中的变量
ASSIGN ('(ZJDEMO)SBOOK-FLDATE') TO
"通过索引动态的访问结构成员
ASSIGN COMPONENT sy-index OF STRUCTURE
"通过字段名动态的访问结构成员
ASSIGN COMPONENT
"如果定义的内表没有组件名时,可以使用索引为0的组件来访问这个无名字段(注:不是1)
ASSIGN COMPONENT 0 OF STRUCTURE itab TO
ASSIGN oref->('attr') TO
ASSIGN oref->('static_attr') TO
ASSIGN ('C1')=>('static_attr') TO
ASSIGN c1=>('static_attr') TO
ASSIGN ('C1')=>static_attr TO
实例请参考类对象反射章节
CL_ABAP_TYPEDESCR
|--CL_ABAP_DATADESCR
| |--CL_ABAP_ELEMDESCR
| |--CL_ABAP_REFDESCR
| |--CL_ABAP_COMPLEXDESCR
| |--CL_ABAP_STRUCTDESCR
| |--CL_ABAP_TABLEDESCR
|--CL_ABAP_OBJECTDESCR
|--CL_ABAP_CLASSDESCR
|--CL_ABAP_INTFDESCR
DATA: structtype TYPE REF TO cl_abap_structdescr.
structtype ?= cl_abap_typedescr=>describe_by_name( 'spfli' ).
*COMPDESC-TYPE ?= CL_ABAP_DATADESCR=>DESCRIBE_BY_NAME( 'EKPO-MATNR' ).
DATA: datatype TYPE REF TO cl_abap_datadescr,
field(5) TYPE c.
datatype ?= cl_abap_typedescr=>describe_by_data( field ).
DATA: elemtype TYPE REF TO cl_abap_elemdescr.
elemtype = cl_abap_elemdescr=>get_i( ).
elemtype = cl_abap_elemdescr=>get_c( 20 ).
DATA: oref1 TYPE REF TO object.
DATA: descr_ref1 TYPE REF TO cl_abap_typedescr.
CREATE OBJECT oref1 TYPE ('C1'). "C1为类名
descr_ref1 = cl_abap_typedescr=>describe_by_object_ref( oref1 ).
还有一种:describe_by_data_ref
handle只能是CL_ABAP_DATADESCR或其子类的引用变量,注:只能用于Data类型,不能用于Object类型,即不能用于CL_ABAP_ OBJECTDESCR,所以没有:
CREATE OBJECT dref TYPE HANDLE objectDescr.
DATA: dref TYPE REF TO data,
c10type TYPE REF TO cl_abap_elemdescr.
c10type = cl_abap_elemdescr=>get_c( 10 ).
CREATE DATA dref TYPE HANDLE c10type.
DATA: x20type TYPE REF TO cl_abap_elemdescr.
x20type = cl_abap_elemdescr=>get_x( 20 ).
FIELD-SYMBOLS:
ASSIGN dref->* TO
TYPES: ty_i TYPE i.
DATA: dref TYPE REF TO ty_i .
CREATE DATA dref TYPE ('I')."根据基本类型名动态创建数据
dref->* = 1.
WRITE: / dref->*." 1
CREATE OBJECT oref TYPE ('C1')."根据类名动态创建实例对象
DATA: dref_str TYPE REF TO data,
dref_tab TYPE REF TO data,
dref_i TYPE REF TO data,
itab_type TYPE REF TO cl_abap_tabledescr,
struct_type TYPE REF TO cl_abap_structdescr,
elem_type TYPE REF TO cl_abap_elemdescr,
table_type TYPE REF TO cl_abap_tabledescr,
comp_tab TYPE cl_abap_structdescr=>component_table WITH HEADER LINE.
FIELD-SYMBOLS :
**=========动态创建基本类型
elem_type ?= cl_abap_elemdescr=>get_i( ).
CREATE DATA dref_i TYPE HANDLE elem_type ."动态的创建基本类型数据对象
**=========动态创建结构类型
struct_type ?= cl_abap_typedescr=>describe_by_name( 'SFLIGHT' )."结构类型
comp_tab[] = struct_type->get_components( )."组成结构体的各个字段组件
* 向结构中动态的新增一个成员
comp_tab-name = 'L_COUNT'."为结构新增一个成员
comp_tab-type = elem_type."新增成员的类型对象
INSERT comp_tab INTO comp_tab INDEX 1.
* 动态创建结构类型对象
struct_type = cl_abap_structdescr=>create( comp_tab[] ).
CREATE DATA dref_str TYPE HANDLE struct_type."使用结构类型对象来创建结构对象
**=========动态创建内表
* 基于结构类型对象创建内表类型对象
itab_type = cl_abap_tabledescr=>create( struct_type ).
CREATE DATA dref_tab TYPE HANDLE itab_type."使用内表类型对象来创建内表类型
ASSIGN dref_tab->* TO
"**========给现有的内表动态的加一列
table_type ?= cl_abap_tabledescr=>describe_by_data( itab ).
struct_type ?= table_type->get_table_line_type( ).
comp_tab[] = struct_type->get_components( ).
comp_tab-name = 'FIDESC'.
comp_tab-type = cl_abap_elemdescr=>get_c( 120 ).
INSERT comp_tab INTO comp_tab INDEX 2.
struct_type = cl_abap_structdescr=>create( comp_tab[] ).
itab_type = cl_abap_tabledescr=>create( struct_type ).
CREATE DATA dref_tab TYPE HANDLE itab_type.
CLASS c1 DEFINITION.
PUBLIC SECTION.
DATA: c VALUE 'C'.
METHODS: test.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD:test.
WRITE:/ 'test'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
TYPES: ty_c.
DATA: oref TYPE REF TO object .
DATA: oref_classdescr TYPE REF TO cl_abap_classdescr .
CREATE OBJECT oref TYPE ('C1')."根据类名动态创建实例对象
"相当于Java中的Class类对象
oref_classdescr ?= cl_abap_classdescr=>describe_by_object_ref( oref ).
DATA: t_attrdescr_tab TYPE abap_attrdescr_tab WITH HEADER LINE,"类中的属性列表
t_methdescr_tab TYPE abap_methdescr_tab WITH HEADER LINE."类中的方法列表
FIELD-SYMBOLS
t_attrdescr_tab[] = oref_classdescr->attributes.
t_methdescr_tab[] = oref_classdescr->methods.
LOOP AT t_attrdescr_tab."动态访问类中的属性
ASSIGN oref->(t_attrdescr_tab-name) TO
WRITE: /
ENDLOOP.
LOOP AT t_methdescr_tab."动态访问类中的方法
CALL METHOD oref->(t_methdescr_tab-name).
ENDLOOP.
CLASS class DEFINITION [ABSTRACT][FINAL].
[PUBLIC SECTION.
[components]]
[PROTECTED SECTION.
[components]]
[PRIVATE SECTION.
[components]]
ENDCLASS.
INTERFACE intf.
[components]
ENDINTERFACE.
²TYPES, DATA, CLASS-DATA, CONSTANTS for data types and data objects
²METHODS, CLASS-METHODS, EVENTS, CLASS-EVENTS for methods and events
² INTERFACES(如果在类中,表示需要实现哪个接口;如果是在接口中,表示继承哪个接口) for implementing interfaces
² ALIASESfor alias names for interface components给接口组件取别名
CLASS math DEFINITION.
PUBLIC SECTION.
METHODS divide_1_by
IMPORTING operand TYPE i
EXPORTING result TYPE f
RAISING cx_sy_arithmetic_error.
ENDCLASS.
CLASS math IMPLEMENTATION.
METHOD divide_1_by.
result = 1 / operand.
ENDMETHOD.
ENDCLASS.
INTERFACEint1.
ENDINTERFACE.
CLASSclass DEFINITION. [ˌdefiˈniʃən]
PUBLICSECTION.
INTERFACES: int1,int2."可实现多个接口
ENDCLASS.
CLASS class IMPLEMENTATION. [ˌɪmplɪmənˈteɪ-ʃən]
METHOD intf1~imeth1.
ENDMETHOD.
ENDCLASS.
CLASS
INTERFACE i0.
METHODS m0.
ENDINTERFACE.
INTERFACE i1.
INTERFACES i0.
"可以有相同的成员名,因为继承过来后,成员还是具有各自的命名空间,在实现时
"被继承过来的叫 i0~m0,在这里的名为i1~m0,所以是不同的两个方法
METHODS m0.
METHODS m1.
ENDINTERFACE.
CLASS person DEFINITION.
ENDCLASS.
CLASS stud DEFINITION INHERITING FROMperson.
ENDCLASS.
START-OF-SELECTION.
DATA p TYPE REF TO person.
DATA s TYPE REF TO stud.
CREATE OBJECT s.
p = s. "向上自动转型
"拿开注释运行时抛异常,因为P此时指向的对象不是Student,而是Person所以能强转的前提是P指向的是Student
"CREATE OBJECT p.
s ?= p."向下强转型
METHODS/CLASS-METHODS meth [ABSTRACT|FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
[EXPORTING parameters]
[CHANGING parameters]
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
应该还有一个Returning选项,且RETURNING不能与EXPORTING、CHANGING同时使用:
... { VALUE(p1) | REFERENCE(p1) | p1 }
{ TYPE generic_type }
|{TYPE{[LINE OF] complete_type}|{REF TO {data|object|complete_type |class|intf}}}
|{LIKE{[LINE OF] dobj}|{REF TO dobj} }
[OPTIONAL|{DEFAULT def1}]
{ VALUE(p2) | REFERENCE(p2) | p2 }...
²data、object:表示是通用数据类型data、object
²complete_type:为完全限定类型
²OPTIONAL与DEFAULT两个选项不能同时使用,且对于EXPORTING类型的输入参数不能使用
²如果参数名p1前没有使用VALUE、REFERENCE,则默认为还是REFERENCE,即引用传递
²方法中的输入输出参数是否能修改,请参考Form、Function参数的传值传址
设置多个IMPORTING类型参数中的某一个参数为首选参数。
首选参数的意义在于:当所有IMPORTING类型都为可选optional时,我们可以通过PREFERRED PARAMETER选项来指定某一个可选输入参数为首选参数,则在以下简单方式调用时:[CALL METHOD] meth( a ). 实参a的值就会传递给设置的首选参数,而其他不是首参数的可选输入参数则留空或使用DEFAULT设置的默认值
注:此选项只能用于IMPORTING类型的参数;如果有必选的IMPORTING输入参数,则没有意义了
[CALL METHOD] meth|me->meth|oref->meth|super->meth|class=>meth[(]
[EXPORTING p1 = a1 p2 = a2 ...]
{ {[IMPORTING p1=a1 p2=a2 ...][CHANGING p1 = a1 p2 = a2 ...]}
|[RECEIVING r = a ] } RECEIVING不能与EXPORTING、CHANGING同时使用
[EXCEPTIONS [exc1 = n1 exc2 = n2 ...]
[OTHERS = n_others] ] [)].
如果省略CALL METHOD,则一定要加上括号形式;如果通过CALL METHOD来调用,则括号可加可不加
RECEIVING:用来接收METHODS /CLASS-METHODS 中RETURNING选项返回的值
如果EXPORTING、IMPORTING、CHANGING、RECEIVING、EXCEPTIONS、OTHERS同时出现时,应该按此顺序来编写
注:使用此种方式调用(使用 EXPORTING、IMPORTING等这些选项)时,如果原方法声明时带了返回值RETURNING,只能使用RECEIVING来接受,而不能使用等号来接收返回值,下面用法是错误的:
num2 = o1->m1( EXPORTING p1 = num1 ).
此方式下输入的参数都只能是IMPORTING类型的参数,如果要传CHANGING、EXPORTING、RAISING、EXCEPTIONS类型的参数时,只能使用上面通用调用方式。
²meth( )
此种方式仅适用于没有输入参数(IMPORTING)、输入\输出参数(CHANGING)、或者有但都是可选的、或者不是可选时但有默认值也可
²meth( a )
此种方式仅适用于只有一个必选输入参数(IMPORTING)(如果还有其他输入参数,则其他都为可选,或者不是可选时但有默认值也可),或者是有多个可选输入参数(IMPORTING)(此时没有必选输入参数情况下)的情况下但方法声明时通过使用PREFERRED PARAMETER选项指定了其中某个可选参数为首选参数(首选参数即在使用meth( a )方式传递一个参数进行调用时,通过实参a传递给设置为首选的参数)
²meth( p1 = a1 p2 = a2 ... )
此种方式适用于有多个必选的输入参数(IMPORTING)方法的调用(其它如CHANGING、EXPORTING没有,或者有但可选),如果输入参数(IMPORTING)为可选,则也可以不必传
Return唯一返回值
METHODS meth
[IMPORTING parameters [PREFERRED PARAMETER p]]
RETURNINGVALUE(r) typing
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
RETURNING :用来替换EXPORTING、CHANGING,不能同时使用。定义了一个形式参数 r 来接收返回值,并且只能是值传递
具有唯一返回值的函数方法可以直接用在以下语句中:逻辑表达式(IF、ELSEIF、WHILE、CHECK、WAIT)、CASE、LOOP、算术表达式、赋值语句
函数方法可以采用上面简单调用方式来调用:meth( )、meth( a )、meth( p1 = a1 P2 = a2 ... )
ref->m( RECEIVING r = i ).
CALL METHOD ref->m( RECEIVING r = i ).
CALL METHOD ref->m RECEIVING r = i.
等效于Java中的 this、super
EVENTS|CLASS-EVENTS evt [EXPORTING VALUE(p1)
{ TYPE generic_type }
|{TYPE {[LINE OF] complete_type} |
{ REF TO {data|object|complete_type|class|intf}} }
| {LIKE{[LINE OF] dobj} | {REF TO dobj} }
[OPTIONAL|{DEFAULT def1}]
VALUE(p2) ...].
²data、object:表示是通用数据类型data、object
²complete_type:为完全限定类型
² OPTIONAL与DEFAULT两个选项不能同时使用
²EXPORTING:定义了事件的输出参数,并且事件定义时只能有输出参数,且只能是传值
非静态事件声明中除了明确使用EXPORTING定义的输出外,每个实例事件其实还有一个隐含的输出参数sender,它指向了事件源对象,当使用RAISE EVENT语句触发一个事件时,事件源的对象就会分配给这个sender引用,但是静态事件没有隐含参数sender
事件evt的定义也是在类或接口的定义部分进行定义的
非静态事件只能在非静态方法中触发,而不能在静态方法中触发;而静态事件即可在静态也可在非静态方法中进行触发,或者反过来说:实例方法既可触发静态事件,也可触发非静态事件,但静态方法就只能触发静态事件
RAISE EVENT evt [EXPORTING p1 = a1 p2 = a2 ...].
该语句只能在定义evt事件的同一类或子类,或接口实现方法中进行调用
当实例事件触发时,如果在event handler事件处理器声明语句中指定了形式参数sender,则会自动接收事件源,但不能在RAISE EVENT …EXPORTING语句中明确指定,它会自动传递(如果是静态事件,则不会传递sender参数)
CLASS c1 DEFINITION.
PUBLIC SECTION.
EVENTS e1 EXPORTING value(p1) TYPE string value(p2) TYPE i OPTIONAL. "定义
METHODS m1.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD m1.
RAISE EVENT e1 EXPORTING p1 = '...'."触发
ENDMETHOD.
ENDCLASS.
静态或非静态事件处理方法都可以处理静态或非静态事件,与事件的静态与否没有直接的关系
INTERFACE window. "窗口事件接口
EVENTS: minimize EXPORTINGVALUE(status) TYPE i."最小化事件
ENDINTERFACE.
CLASS dialog_window DEFINITION. "窗口事件实现
PUBLIC SECTION.
INTERFACES window.
ENDCLASS.
INTERFACE window_handler. "窗口事件处理器接口
METHODS: minimize_window FOR EVENT window~minimize OF dialog_window
IMPORTING status sender. "事件处理器方法参数要与事件接口定义中的一致
ENDINTERFACE.
实例事件处理器(方法)注册(注:被注册的方法只能是用来处理非静态事件的方法):
SET HANDLER handler1 handler2 ... FOR oref|{ALL INSTANCES}[ACTIVATION act].
静态事件处理器(方法)注册(注:被注册的方法只能是用来处理静态事件的方法):
SET HANDLER handler1 handler2 ... [ACTIVATION act].
oref:只将事件处理方法handler1 handler2注册到 oref 这一个事件源对象
ALL INSTANCES:将事件处理方法注册到所有的事件源实例中
ACTIVATION act:表示是注册还是注销
CLASS c1 DEFINITION."事件源
PUBLIC SECTION.
EVENTS: e1 EXPORTING value(p1) TYPE c,e2.
CLASS-EVENTS ce1 EXPORTING value(p2) TYPE i.
METHODS:trigger."事件触发方法
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD trigger.
RAISE EVENT: e1 EXPORTING p1 = 'A',e2,ce1 EXPORTING p2 = 1.
ENDMETHOD.
ENDCLASS.
静态(如下面的h1方法)或非静(如下面的h2方法)态事件处理方法都可以处理静态或非静态事件,事件的处理方法是否只能处理静态的还是非静态事件与事件的静态与否没有关系,但事件的触发方法与事件的静态与否有关系(实例方法既可触发静态事件,也可触发非静态事件,但静态方法就只能触发静态事件);但是,事件处理方法虽然能处理的事件与事件的静态与否没有关系,但如果处理的是静态事件,那此处理方法就成为了静态处理器,只能采用静态注册方式对此处理方法进行注册。如果处理的是非静态事件,那此处理方法就是非静态处理器,只能采用非静态注册方式对此处理方法进行注册
处理器的静态与否与处理方法本身是否静态没有关系,只与处理的事件是否静态有关
CLASS c2 DEFINITION."监听器:即事件处理器
PUBLIC SECTION.
"静态方法也可以处理非静态事件,此方法属于非静态处理器,只能采用非静态注册方式
CLASS-METHODS h1 FOR EVENT e1 OF c1 IMPORTING p1 sender.
"非静态方法处理非静态事件,此方法属于非静态处理器,只能采用非静态注册方式
METHODS: h2 FOR EVENT e2 OF c1 IMPORTING sender,
"非静态方法当然更可以处理静态事件,此方法属于静态处理器,只能采用静态注册方式
h3 FOR EVENT ce1 OF c1 IMPORTING p2.
ENDCLASS.
CLASS c2 IMPLEMENTATION.
METHOD h1 .
WRITE: 'c2=>h1'.
ENDMETHOD.
METHOD: h2.
WRITE: 'c2->h2'.
ENDMETHOD.
METHOD: h3.
WRITE: 'c2->h3'.
ENDMETHOD.
ENDCLASS.
DATA: trigger TYPE REF TO c1,
trigger2 TYPE REF TO c1,
handler TYPE REF TO c2.
START-OF-SELECTION.
CREATE OBJECT trigger.
CREATE OBJECT trigger2.
CREATE OBJECT handler.
"由于h1、h2两个处理方法分别是用来处理非静态事件e1、e2的,所以只能采用实例注册方式
SET HANDLER: c2=>h1 handler->h2 FOR trigger,
"h3处理方法是用来处理静态事件ce1的,属于静态处理器,所以只能采用静态注册方式
handler->h3.
trigger->trigger( ).
"虽然trigger( )方法会触发 e1,e2,ce1 三种事件,但h1、h2未向实例trigger2注册,而h3属于静态处理器,与实例无关,即好比向所有实例注册过了一样
trigger2->trigger( ).
LOOP AT itab {INTO wa}|{ASSIGNING
[[USING KEY key_name|(name)] [FROM idx1] [TO idx2] [WHERE log_exp|(cond_syntax)]].
ENDLOOP.
FROM … TO: 只适用于标准表与排序表 WHERE … : 适用于所有类型的内表
如果没有通过USING KEY选项的key_name,则循环读取的顺序与表的类型相关:
l标准表与排序表:会按照primary table index索引的顺序一条条的循环,且在循环里SY-TABIX为当前正在处理行的索引号
l 哈希表:由于表没有排序,所以按照插入的顺序来循环处理,注,此时SY-TABIX 总是0
可以在循环内表时增加与删除当前行:If you insert or delete lines in the statement block of a LOOP , this will have the following effects:
DATA : BEGIN OF gt_table OCCURS 0,
c,
END OF gt_table.
APPEND 'a' TO gt_table.
APPEND 'b' TO gt_table.
APPEND 'c' TO gt_table.
APPEND 'd' TO gt_table.
APPEND 'e' TO gt_table.
APPEND 'f' TO gt_table.
APPEND 'g' TO gt_table.
APPEND 'h' TO gt_table.
LOOP AT gt_table .
IF gt_table-c = 'b' OR gt_table-c = 'c' OR gt_table-c = 'e'.
WRITE : / sy-tabix COLOR = 6 INTENSIFIED ON INVERSE OFF ,
gt_table COLOR = 6 INTENSIFIED ON INVERSE OFF .
ELSE.
WRITE : / sy-tabix, gt_table.
ENDIF.
ENDLOOP.
SKIP 2.
DATA count TYPE i .
LOOP AT gt_table .
count = count + 1.
"当循环到第三次时删除,即循环到 C 时进行删除
IF count = 3.
DELETE gt_table WHERE c = 'b' OR c = 'c' OR c = 'e'.
ENDIF.
"删除之后sy-tabix会重新开始对内表现有的行进行编号
WRITE :/ sy-tabix, gt_table.
ENDLOOP.
SKIP 2.
LOOP AT gt_table .
WRITE : / sy-tabix, gt_table.
ENDLOOP.
如果在 AT - ENDAT 块中使用 SUM,则系统计算当前行组中所有行的数字字段之和并将其写入工作区域中相应的字段中
|
含义 |
FIRST |
内表的第一行时触发 |
LAST |
内表的最后一行时触发 |
NEW |
相邻数据行中相同 |
END Of |
相邻数据行中相同 |
在使用AT...... ENDAT之前,一这要先按照这些语句中的组件名进行排序,且排序的顺序要与在AT...... ENDAT语句中使用顺序一致,排序与声明的顺序决定了先按哪个分组,接着再按哪个进行分组,最后再按哪个进行分组,这与SQL中的Group By 相似
用在AT...... ENDAT语句中的中的组件名不一定要是结构中的关键字段,但这些字段一定要按照出现在AT关键字后面的使用顺序在结构最前面进行声明,且这些组件字段的声明之间不能插入其他组件的声明。如现在需要按照
LOOP AT
AT FIRST. ... ENDAT.
AT NEW
AT NEW
.......
.......
AT END OF
AT END OF
AT LAST. .... ENDAT.
ENDLOOP.
一旦进入到 AT...
DATA: BEGIN OF th_mseg OCCURS 10,
matnr TYPE mard-matnr, werks TYPE mard-werks,
lgort TYPE mard-lgort, shkzg TYPE mseg-shkzg,
menge TYPE mseg-menge, budat TYPE mkpf-budat,
LOOP AT th_mseg.
AT END OF shkzg."会根据shkzg及前面所有字段来进行分组
sum.
WRITE: / th_mseg-matnr, th_mseg-werks,th_mseg-lgort,
th_mseg-shkzg,th_mseg-menge,th_mseg-budat.
ENDAT.
ENDLOOP.
AS-101 2300 0001 S 10.000 ****.**.**
AS-100 2300 0002 S 10.000 ****.**.**
AS-100 2300 0001 S 20.000 ****.**.**
上面由于没有根据matnr + werks + lgort + shkzg 进行排序,所以结果中的第三行其实应该与第一行合并。其实这个统计与SQL里的分组(Group By)统计原理是一样的,Group By 后面需要明确指定分组的字段,如上面程序使用SQL分组写法应该为 Group Bymatnr werks lgort shkzg,但在ABAP里你只需要按照 matnr werks lgort shkzg按照先后顺序在结构定义的最前面进行声明就可表达了Group By那种意义,而且不一定要将matnr werks lgort shkzg这四个字段全部用在AT语句块中AT NEW、AT END OF shkzg 才正确,其实像上面程序一样,只写AT END OF shkzg这一个语句,前面三个字段matnr werks lgort都可以不用在AT语句中出现,因为ABAP默认会按照结构中声明的顺序将shkzg前面的字段也全都用在了分组中了
DATA: BEGIN OF line,
"C2、C3组件名声明的顺序一定要与在AT...... ENDAT块中使用的次序一致,即这里不能将C3声明在C2之前,且不能在C2与C3之间插入其他字段的声明
c2(5) TYPE c,
c3(5) TYPE c,
c4(5) TYPE c,
i1 TYPE i,
i2 TYPE i,
c1(5) TYPE c,
END OF line.
"使用在AT...... ENDAT语句中的字段不一定要是关键字段
DATA: itab LIKE TABLE OF line WITH HEADER LINE WITH NON-UNIQUE KEY i1.
PERFORM append USING 2 'b' 'bb' 'bbb' '2222' 22.PERFORM append USING 3 'c' 'aa' 'aaa' '3333' 33.
PERFORM append USING 4 'd' 'aa' 'bbb' '4444' 44.PERFORM append USING 5 'e' 'bb' 'aaa' '5555' 55.
PERFORM append USING 6 'f' 'bb' 'bbb' '6666' 66.PERFORM append USING 7 'g' 'aa' 'aaa' '7777' 77.
PERFORM append USING 8 'h' 'aa' 'bbb' '8888' 88.
SORT itab ASCENDING BY c2 c3.
LOOP AT itab.
WRITE: / itab-c2,itab-c3,itab-c1,itab-c4,itab-i1,itab-i2.
ENDLOOP.
SKIP.
LOOP AT itab.
AT FIRST.
WRITE:/ '>>>> AT FIRST'.
ENDAT.
AT NEW c2.
WRITE: / ' >>>> Start of' , itab-c2.
ENDAT.
AT NEW c3.
WRITE: / ' >>>> Start of' , itab-c2, itab-c3.
ENDAT.
"只要一出 AT 块,则表头的数据又会恢复成当前被遍历行的内容
WRITE: / itab-c2,itab-c3,itab-c1,itab-c4,itab-i1,itab-i2.
AT END OF c3.
SUM.
WRITE: / itab-c2,itab-c3,itab-c1,itab-c4,itab-i1,itab-i2.
WRITE: / ' <<<< End of' , itab-c2, itab-c3.
ENDAT.
AT END OF c2.
SUM.
WRITE: / itab-c2,itab-c3,itab-c1,itab-c4,itab-i1,itab-i2.
WRITE: / ' <<<< End of' , itab-c2.
ENDAT.
AT LAST.
SUM.
WRITE: / itab-c2,itab-c3,itab-c1,itab-c4,itab-i1,itab-i2.
WRITE:/ '<<<< AT LAST'.
ENDAT.
ENDLOOP.
TYPES: c5(5) TYPE c.
FORM append USING value(p_i1) TYPE Ivalue(p_c1) TYPE c5 value(p_c2) TYPE c5
value(p_c3) TYPE c5 value(p_c4) TYPE c5 value(p_i2) TYPE i.
itab-i1 = p_i1. itab-c1 = p_c1. itab-c2 = p_c2.
itab-c3 = p_c3. itab-c4 = p_c4. itab-i2 = p_i2.
APPEND itab.
ENDFORM.
aa aaa c 3333 3 33
aa aaa g 7777 7 77
aa bbb d 4444 4 44
aa bbb h 8888 8 88
bb aaa a 1111 1 11
bb aaa e 5555 5 55
bb bbb b 2222 2 22
bb bbb f 6666 6 66
>>>> AT FIRST
>>>> Start of aa
>>>> Start of aa aaa
aa aaa c 3333 3 33
aa aaa g 7777 7 77
aa aaa ***** ***** 10 110
<<<< End of aa aaa
>>>> Start of aa bbb
aa bbb d 4444 4 44
aa bbb h 8888 8 88
aa bbb ***** ***** 12 132
<<<< End of aa bbb
aa ***** ***** ***** 22 242
<<<< End of aa
>>>> Start of bb
>>>> Start of bb aaa
bb aaa a 1111 1 11
bb aaa e 5555 5 55
bb aaa ***** ***** 6 66
<<<< End of bb aaa
>>>> Start of bb bbb
bb bbb b 2222 2 22
bb bbb f 6666 6 66
bb bbb ***** ***** 8 88
<<<< End of bb bbb
bb ***** ***** ***** 14 154
<<<< End of bb
***** ***** ***** ***** 36 396
<<<< AT LAST
如果循环的内表不是自己定义的,有时无法将分组的字段按顺序声明在一起,所以需要自己实现这些功能,下面是自己实现AT NEW与AT END OF(另一好处是在循环内表时可以使用Where条件语句)(注:使用这种只需要按照分组的顺序排序即可,如要分成bukrs与bukrs anlkl两组时,需要按照BY bukrs anlkl排序,而不能是BYanlkl bukrs):
DATA: lp_bukrs TYPE bukrs, "上一行bukrs字段的值
lp_anlkl TYPE anlkl. "上一行anlkl字段的值
"下面假设按bukrs,bukrs anlkl分成两组
SORT itab_data BY bukrs anlkl.
DATA: i_indx TYPE i .
DATA: lwa_data Like itab_data
LOOP AT itab_data where flg = 'X'.
i_indx = sy-tabix.
"**********AT NEW 对当前分组首行进行处理
IF itab_data-bukrs <> lp_bukrs. "Bukrs组
".........
ENDIF.
IF itab_data-bukrs <> lp_bukrs OR itab_data-anlkl <> lp_anlkl. "bukrs anlkl 分组
".........
ENDIF.
IF itab_data-bukrs <> lp_bukrs OR itab_data-anlkl <> lp_anlkl OR itab_data-.. <> lp_.. . "bukrs anlkl .. 分组
".........
ENDIF.
"**********普通循环处理
".........
"**********AT END OF 对当前分组末行进行处理
DATA : l_nolast1,l_nolast12 . "不是分组中最末行
"这里还是要清一下,以防该代码直接写在报表程序的事件里,而不是Form里(直接放在Report程序事件里时,l_nolast1,l_nolast12将会成为全局变量)
CLEAR: l_nolast1,l_nolast12,l_nolast...
DO.
i_indx = i_indx + 1.
READ TABLE itab_data INTO lwa_data INDEX i_indx."尝试读取下一行
IF sy-subrc <> 0."当前行已是内表中最后一行
EXIT.
"如果第一分组字段都发生了变化,则意味着当前行为所有分组中的最后行
"注:即使有N 个分组,这里也只需要判断第一分组字段是否发生变化,不
"需要对其他分组进行判断,即这里不需要添加其他 ELSEIF 分支
ELSEIF lwa_data-bukrs <> itab_data-bukrs.
EXIT.
ENDIF.
********断定满足条件的下一行不是分组最的一行
"如果Loop循环中没有Where条件,则可以将下面条件 lwa_data-flg = 'X' 删除即可
IF sy-subrc = 0 AND lwa_data-flg = 'X' .
IF lwa_data-bukrs = itab_data-bukrs ."判断当前行是否是 bukrs 分组最后行
l_nolast1 = '1'.
IF lwa_data-nanlkl = itab_data-nanlkl ."判断当前行是否是 bukrs nanlkl 分组最后行
l_nolast2 = '1'.
IF lwa_data-.. = itab_data-..."判断当前行是否是 bukrs nanlkl ..分组最后行
l_nolast.. = '1'.
ENDIF.
ENDIF.
EXIT."只要进到此句所在外层If,表示找到了一条满Where条件的下一行数据,因此,只要找到这样的数据就可以判断当前分组是否已完,即一旦找到这样的数据就不用再往后面找了,则退出以防继续往下找
ENDIF.
ENDIF.
ENDDO.
IF l_nolast..IS INITIAL"处理 bukrs nanlkl ..分组
......
ENDIF.
IF l_nolast2 IS INITIAL ."处理 bukrs nanlkl 分组
......
ENDIF.
IF l_nolast1 IS INITIAL ."处理 bukrs 分组
......
ENDIF.
lp_bukrs = itab_data-bukrs.
lp_anlkl = itab_data-anlkl.
lp_.. = itab_data-.. .
ENDLOOP.
TYPES: BEGIN OF line ,
key ,
val TYPE i ,
END OF line .
DATA: itab1 TYPE line OCCURS 0 WITH HEADER LINE .
DATA: itab2 TYPE line OCCURS 0 WITH HEADER LINE .
itab1-key = 1.
itab1-val = 1.
APPEND itab1.
itab2 = itab1.
APPEND itab2.
itab1-key = 2.
itab1-val = 2.
APPEND itab1.
itab2 = itab1.
APPEND itab2.
LOOP AT itab1.
WRITE: / 'itab1 index: ' , sy-tabix.
READ TABLE itab2 INDEX 1 TRANSPORTING NO FIELDS."试着读取其他内表
"READ TABLE itab1 INDEX 1 TRANSPORTING NO FIELDS."读取本身也不会影响后面的 MODIFY 语句
WRITE: / 'itab2 index: ', sy-tabix.
itab1-val = itab1-val + 1.
"在循环中可以使用下面简洁方法来修改内表,修改的内表行为当前正被循环的行,即使循环中使用了
"READ TABLE语句读取了其他内表(读取本身也没有关系)而导致了sy-tabix 发生了改变,因为以下
"语句不是根据sy-tabix来修改的(如果在前面读取内表导致sy-tabix 发生了改变发生改变后,再使用
"MODIFY itab1 INDEX sy-tabix语句进行修改时,反而不正确。而且该语句还适用于Hash内表,需在
"MODIFY后面加上TABLE关键字后再适用于Hash表——请参见后面章节示例)
MODIFY itab1.
ENDLOOP.
LOOP AT itab1.
WRITE: / itab1-key,itab1-val.
ENDLOOP.
TYPES: BEGIN OF line ,
key ,
val TYPE i ,
END OF line .
DATA: itab1 TYPE HASHED TABLE OF line WITH HEADER LINE WITH UNIQUE KEY key.
DATA: itab2 TYPE line OCCURS 0 WITH HEADER LINE .
itab1-key = 1.
itab1-val = 1.
INSERT itab1 INTO TABLE itab1.
itab2 = itab1.
APPEND itab2.
itab1-key = 2.
itab1-val = 2.
INSERT itab1 INTO TABLE itab1.
itab2 = itab1.
APPEND itab2.
LOOP AT itab1.
WRITE: / 'itab1 index: ' , sy-tabix."循环哈希表时,sy-tabix永远是0
READ TABLE itab2 INDEX 1 TRANSPORTING NO FIELDS.
WRITE: / 'itab2 index: ', sy-tabix.
itab1-val = itab1-val + 1.
MODIFY TABLE itab1."注:该语句不一定在要放在循环里才能使用——循环外修改Hash也是一样的,这与上面的索引表循环修改是不一样的,并且修改的条件就是itab1表头工作区,itab1即是条件,也是待修改的值,修改时会根据内表设置的主键来修改,而不是索引号
ENDLOOP.
LOOP AT itab1.
WRITE: / itab1-key,itab1-val.
ENDLOOP.
三种类型第二索引:
²UNIQUE HASHED: 哈希算法第二索引
²UNIQUE SORTED: 唯一升序第二索引
²NON-UNIQUE SORTED:非唯一升序第二索引
TYPES sbook_tab TYPE STANDARD TABLEOF sbook
"主索引:如果要为主索引指定名称,则只能使用预置的 primary_key,但可以通过后面的 ALIAS 选项来修改(注:ALIAS选项只能用于排序与哈希表)
WITH NON-UNIQUE KEY primary_key "ALIAS my_primary_key
COMPONENTS carrid connid fldate bookid
"第一个第二索引:唯一哈希算法
WITH UNIQUE HASHED KEY hash_key
COMPONENTS carrid connid
"第二第二索引:唯一升序排序索引
WITH UNIQUE SORTED KEY sort_key1
COMPONENTS carrid bookid
"第三第二索引:非唯一升序排序索引
WITH NON-UNIQUE SORTED KEY sort_key2
COMPONENTS customid.
1、 可以在READ TABLE itab、MODIFY itab、DELETE itab、LOOP AT itab内表操作语句中通过WITH [TABLE] KEYkey_nameCOMPONENTSK1=V1 ...或者USING KEY key_name,语句中的key_name为第二索引名:
READ TABLE itab WITH TABLE KEY[key_nameCOMPONENTS] {K1|(K1)} = V1... INTO wa
READ TABLE itab WITH KEY key_nameCOMPONENTS {K1|(K1)} = V1... INTO wa
READ TABLE itab FROM wa [USING KEY key_name] INTO wa
READ TABLE itab INDEX idx [USING KEY key_name] INTO wa
MODIFY TABLE itab [USING KEY key_name] FROM wa
MODIFY itab [USINGKEY loop_key] FROM wa此语句只能用在LOOP AT内表循环语句中,并且此时 USING KEY loop_key 选项也可以省略(其实默认就是省略的),其中loop_key是预定义的,不能写成其他名称
MODIFY itab INDEX idx [USING KEY key_name] FROM wa
MODIFY itab FROM wa [USING KEY key_name] ...WHERE ...
DELETE TABLE itab FROM wa [USING KEY key_name]
DELETE TABLE itab WITH TABLE KEY [key_nameCOMPONENTS] {K1|(K1)} = V1...
DELETE itab INDEX idx [USING KEY key_name|(name)]
DELETE itab [USING KEY loop_key]
DELETE itab [USING KEY key_name ] ...WHERE ...
DELETE ADJACENT DUPLICATES FROM itab [USING KEY key_name] [COMPARING K1 K2...]
LOOP AT itab USING KEY key_name WHERE... .
ENDLOOP.
2、 可以在INSERTitab与APPEND语句中通过USING KEY选项来使用第二索引
INSERT wa [USING KEY key_name] INTO TABLE itab
APPEND wa [USING KEY key_name] TO itab
DATA itab TYPE HASHED TABLE OF dbtab WITH UNIQUE KEY col1 col2 ...
"向内表itab中添加大量的数据 ...
READ TABLE itab "使用非主键进行搜索,搜索速度将会很慢
WITH KEY col3 = ... col4 = ...
ASSIGNING ...
上面定义了一个哈希内表,在读取时未使用主键,在大数据量的情况下速度会慢,所以在搜索字段上创建第二索引:
DATA itab TYPE HASHED TABLE OF dbtab
WITH UNIQUE KEY col1 col2 ...
"为非主键创建第二索引
WITH NON-UNIQUE SORTED KEY second_key
COMPONENTS col3 col4 ...
"向内表itab中添加大量的数据 ...
READ TABLE itab "根据第二索引进行搜索,会比上面程序快
WITH TABLE KEY second_key
COMPONENTS col3 = ... col4 = ...
ASSIGNING ...
"在循环内表的Where条件中,如果内表不是排序内表,则不会使用二分搜索,如果使用SORTED KEY,则循环时,会用到二分搜索?
LOOP AT itab USING KEY second_key where col3 = ... col4 = ... .
ENDLOOP.
TYPES: BEGIN OF typ_tab,
mandt TYPE t001-mandt,
bukrs TYPE t001-bukrs,
END OF typ_tab.
DATA: lt_hash TYPE HASHED TABLE OF typ_tab WITH UNIQUE KEY mandt WITH HEADER LINE.
DATA: lt_sort TYPE SORTED TABLE OF typ_tab WITH UNIQUE KEY mandt WITH HEADER LINE.
lt_hash-mandt = '200'.lt_hash-bukrs = '200'.
INSERT lt_hash INTO TABLE lt_hash.
WRITE:/ 'sy-subrc:',sy-subrc.
lt_hash-mandt = '100'.lt_hash-bukrs = '300'.
INSERT lt_hash INTO TABLE lt_hash.
WRITE:/ 'sy-subrc:',sy-subrc.
lt_hash-mandt = '200'.lt_hash-bukrs = '400'.
"重复的无法插入,也不会覆盖,也不会抛异常
INSERT lt_hash INTO TABLE lt_hash.
WRITE:/ 'sy-subrc:',sy-subrc.
SKIP.
LOOP AT lt_hash.
WRITE: / lt_hash-mandt,lt_hash-bukrs.
ENDLOOP.
WRITE:/ '-----------------------'.
lt_sort-mandt = '200'.lt_sort-bukrs = '200'.
INSERT lt_sort INTO TABLE lt_sort.
WRITE:/ 'sy-subrc:',sy-subrc.
lt_sort-mandt = '100'.lt_sort-bukrs = '300'.
INSERT lt_sort INTO TABLE lt_sort.
WRITE:/ 'sy-subrc:',sy-subrc.
lt_sort-mandt = '200'.lt_sort-bukrs = '400'.
"与HASH一样,重复的无法插入,也不会覆盖,也不会抛异常
INSERT lt_sort INTO TABLE lt_sort.
WRITE:/ 'sy-subrc:',sy-subrc.
SKIP.
LOOP AT lt_sort.
WRITE: / lt_sort-mandt,lt_sort-bukrs.
ENDLOOP.
WRITE:/ '-----------------------'.
"不管是Sort,还是Hash 表,只要是 Unique Key,且从数据库查出来的数据存在重复,则会Dump掉
*SELECT mandt bukrs INTO TABLE lt_hash FROM t001 CLIENT SPECIFIED WHERE mandt BETWEEN '100' AND'999'.
*SELECT mandt bukrs INTO TABLE lt_sort FROM t001 CLIENT SPECIFIED WHERE mandt BETWEEN '100' AND'999'.
另:针对Unique的Sort、Hash内表,可以使用Collect语句进行汇总统计
另外:对于排序内表,不要使用APPEND 、APPEND LINES附加数据,要使用INSERT、INSERT LINES向排序内表中插入数据,因为如果附加的数据不按排序内表排序规则来的话,会Dump,但使用INSERT就不会了
COLLECT [
INSERT
INSERT LINES OF
向UNIQUE 的排序表或哈希表插入重复的数据时,不会抛异常,但数据不会被插入进去,这与APPEND是不一样的
Hash内表的KEY设置只能是开头前部分定义的连续的组件字段,不能只将中间或先后几个字段设置为KEY,否则在查找时会出问题,数据无法查到
"只要根据关键字或索引在内表中读取到相应数据,不管该数据行是否与COMPARING 指定的字段相符,都会存储到工作区
READ TABLE
INTO
[TRANSPORTING
| ASSIGNING
READ TABLE
COMPARING:系统根据
如果系统找根据指定
MODIFY TABLE
MODIFY
DELETE TABLE
DELETE TABLE
DELETE itab WHERE ( col2 > 1 ) AND ( col1 < 4 )"删除多行
DELETE ADJACENT DUPLICATESFROM
注,在未使用COMPARING 选项时,要删除重复数据之前,一定要按照内表关键字声明的顺序来进行排序,才能删除重复数据,否则不会删除掉;如果指定了COMPARING 选项,则需要根据指定的比较字段顺序进行排序(如COMPARING
APPEND
APPEND LINES OF
INSERT
INSERT LINES OF
APPEND/INSERT…INDEX 操作不能用于Hash表
APPEND/INSERT…INDEX用于排序表时条件:附加/插入时一定要按照Key的升序来附加;如果是Unique排序表,则不能附加/插入重附的数据,这与INSERT…INTO TABLE是不一样的
READ TABLE
INTO
[TRANSPORTING
| ASSIGNING
MODIFY
DELETE
DELETE
如果从数据库读出来的数据存在重复时,不能存储到Unique内表中去——如Unique的排序表与哈希表
SELECT SINGLE...INTO [CORRESPONDING FIELDS OF] wa WHERE...
SELECT SINGLE
SELECT ... FROM
SELECT...INTO|APPENDING CORRESPONDING FIELDS OF TABLE
单条插入:在插入时是按照数据库表结构来解析
INSERT INTO
INSERT
多条插入:itab内表的行结构也必须和数据库表的行结构一致;ACCEPTING DUPLICATE KEYS:如果现出关键字相同条目,系统将SY-SUBRC返回4,并跳过该条目,但其他数据会插入进去
INSERT
单条更新:会根据数据库表关键字来更新其他非关键字段。如果WA工作区是自己定义的且未参照数据库表,则WA的结构需要与数据库表相一致,且不能短于数据库表结构,但字段名可任意取
UPDATE dbtab FROM wa
多条更新:主键不会被更新,即使在SET后面指定后也不会被更改
UPDATEdbtab SETf1 = g1 … fi = gi WHERE
UPDATE dbtab FROMTABLE itab 与从WA工作区单条更新原理一样,根据数据表库关键字段来更新,且行结构要与数据库表结构一致,并且不能短于数据库表结构,一样内表行结构组件名可任意
单条删除:下面的WA与Itab原理与Update是一样的
DELETE dbtab FROM wa
多条删除:
DELETE dbtab FROMTABLE itab
DELETEFROM dbtab WHERE
插入或更新:下面的WA与Itab原理与Update是一样的
MODIFY dbtab FROM wa 单行
MODIFY dbtab FROMTABLE itab多行,有就修改,没有就插入
=、<>、<、<=、>、>=
[NOT] BETWEEN ...AND
[NOT] LIKE
[NOT] IN
IS [NOT] NULL
两种定义方式:
RANGES seltab FOR dobj [OCCURS n].其中dobj为自定义变量或者是参照某个表字段
SELECT-OPTIONSselcritFOR {dobj|(name)}
上面两个语句会生成如下结构的内表,该条件内表的每一行都代表一个逻辑条件:
DATA: BEGIN OF seltab OCCURS 0,
sign TYPE c LENGTH 1, 允许值为I和E,I表示包含 Include,E表示排除Exclude
option TYPE c LENGTH 2, OPTION表示选择运算符,
low LIKE dobj, 下界,相当于前面文本框中的值
high LIKE dobj, 上界,相当于后面文本框中的值
END OF rtab.
option: HIGH字段为空,则取值可以为:EQ(=)、NE(<>)、GT(>)、GE(>=)、LE(<=)、LT(<)、CP、NP,CP(集合之内的数据)和NP(集合之外数据)只有当在输入字段中使用了通配符(“*”或“+”)时它们才是有效的
SELECT ... WHERE ... field [NOT] IN seltab ...
如果RANG条件内表为空,则IN seltab逻辑表达试恒为真,XX NOT IN seltab恒为假
注:不会像FOR ALL ENTRIES那样,忽略其他的条件表达式,其他条件还是起作用
1、使用该选项后,对于最后得出的结果集系统会自动删除重复行。因此如果你要保留重复行记录时,记得在SELECT语句中添加足够字段
2、FOR ALL ENTRIES IN后面使用的内部表itab如果为空,将查出当前CLIENT端所有数据(即忽略整个WHERE语句,其他条件都会被忽略)
3、内表中的条件字段不能使用BETWEEN、LIKE、IN比较操作符
4、使用该语句时,ORDER BY语句和HAVING语句将不能使用
5、使用该语句时,除COUNT( * )(并且如果有了COUNT函数,则不能再选择其他字段,只能使用在Select ... ENDSelect语句中了)以外的所有合计函数(MAX,MIN,AVG,SUM)都不能使用
即使Where后面还有其它条件,所有的条件都会忽略:
SELECT vbeln posnr pstyv werks matnr arktx lgort waerk kwmeng
FROM vbap INTO TABLE gt_so FOR ALL ENTRIES IN lt_matnr
WHERE matnr = lt_matnr-matnr AND vbeln IN s_vbeln AND posnr IN s_posnr.
如果上面的lt_matnr为空,则“AND vbeln IN s_vbeln AND posnr IN s_posnr”条件也会忽略掉,即整个Where都会被忽略掉。
SELECT matnr FROM mara INTO CORRESPONDING FIELDS OF TABLE strc
FOR ALL ENTRIES IN strc WHERE matnr = strc-matnr .
生成的SQL语句:SELECT "MATNR" FROM "MARA" WHERE "MANDT" = '210' AND "MATNR" IN ( '000000000000000101' , '000000000000000103' , '000000000000000104' )
注:这里看上去FOR ALL ENTRIES使用 IN 表达式来代替了,这是只有使用到内表中一个条件是这样的,如果使用多个条件时,不会使用In表达式,而是使用OR连接,像这样:
另外,在使用FOR ALL ENTRIES时,不管使用了条件内表中的一个还是多个条件字段,都会以5个值为单位进行SQL发送
ON后面的条件与Where条件类似,但有以下不同:
² 必需有ON条件语句,且多个条件之间只能使用AND连接
²每个条件表达式中两个操作数之中必须有一个字段是来自于JOIN右表
²如果是LEFT OUTER JOIN,则至少有一个条件表达式的两个操作数一个是来自于左表,另一个来自右表
²不能使用NOT、LIKE、IN(但如果是 INNER JOIN,则>、<、BETWEEN …AND、<>都可用)
²如果是LEFT OUTER JOIN,则只能使用等号操作符:(=、EQ)
²如果是LEFT OUTER JOIN,同一右表不能多次出现在不同的LEFT OUTER JOIN的ON条件表达式中
²LEFT OUTER JOIN的右表所有字段不能出现在WHERE中
²如果是LEFT OUTER JOIN,则在同一个ON条件语句中只能与同一个左表进行关联
SELECT (column_syntax) FROM...
column:可以是内表,也可以是字符串
TYPES: line_type TYPE c LENGTH 72.
DATA: column_syntax TYPE TABLE OF line_type .
APPEND 'CARRID' TO column_syntax.
APPEND 'CITYFROM CITYTO' TO column_syntax.
SELECT ... FROM (dbtab_syntax)...
PARAMETERS: p_cityfr TYPE spfli-cityfrom,
p_cityto TYPE spfli-cityto.
DATA: BEGIN OF wa,
fldate TYPE sflight-fldate,
carrname TYPE scarr-carrname,
connid TYPE spfli-connid,
END OF wa.
DATA itab LIKE SORTED TABLE OF wa
WITH UNIQUE KEY fldate carrname connid.
DATA: column_syntax TYPE string,
dbtab_syntax TYPE string.
column_syntax = `c~carrname p~connid f~fldate`.
dbtab_syntax = `( ( scarr AS c `
& ` INNER JOIN spfli AS p ON p~carrid = c~carrid`
& ` AND p~cityfrom = p_cityfr`
& ` AND p~cityto = p_cityto )`
& ` INNER JOIN sflight AS f ON f~carrid = p~carrid `
& ` AND f~connid = p~connid )`.
SELECT (column_syntax) FROM (dbtab_syntax)
INTO CORRESPONDING FIELDS OF TABLE itab.
SELECT ... WHERE (cond_syntax) ...
SELECT ... WHERE
DATA: cond(72) TYPE c,
itab LIKE TABLE OF cond.
APPEND 'cityfrom = ''NEW YORK''' TO itab.
APPEND 'or cityfrom = ''SAN FRANCISCO''' TO itab.
SELECT * INTO TABLE itab_spfli FROM spfli WHERE (itab).
DATA:cond1(72) TYPE c VALUE 'cityfrom = ''NEW YORK''',
cond2(72) TYPE c VALUE 'cityfrom = ''SAN FRANCISCO'''.
SELECT * INTO TABLE itab_spfli FROM spfli WHERE (cond1) OR (cond2).
DATA: cond(72) TYPE c,
cond1(72) TYPE c VALUE 'cityfrom = ''NEW YORK''',
itab LIKE TABLE OF cond.
APPEND 'cityfrom = ''SAN FRANCISCO''' TO itab.
SELECT * INTO TABLE itab_spfli FROM spfli WHERE (itab) OR (cond1).
DATA: cond(72) TYPE c,
itab LIKE TABLE OF cond.
APPEND 'cityfrom = ''SAN FRANCISCO''' TO itab.
SELECT * INTO TABLE itab_spfli FROM spfli WHERE (itab)OR cityfrom ='NEW YORK'
colum operator[ALL|ANY|SOME]、[NOT] EXISTS、[NOT] IN连接至WHERE从句与HAVING从句中
子查询的SELECT中只有一个表字段或者是一个统计列,并且只能返回一条数据
SELECT * FROM sflight INTO wa_sflight
WHERE seatsocc = ( SELECT MAX( seatsocc ) FROM sflight ).
ENDSELECT.
操作符可以是:=、<>、<、<=、>、>=
如果子查询返回的是多条,则使用ALL、ANY、SOME来修饰
SELECT customid COUNT( * ) FROM sbook INTO (id, cnt) GROUP BY customid
HAVING COUNT( * ) >= ALL ( SELECT COUNT( * ) FROM sbook GROUP BY customid ).
ENDSELECT.
² ALL:主查询数据大于所有子查询返回的行数据时,才为真
² ANY|SOME:主查询数据只要大于任何一条子查询返回的行数据时,才为真
² = ANY|SOME:等效IN子查询
此类子查询SELECT中也只有单独的一列选择列,但查询出的结果可能有多条
SELECT SINGLE city latitude longitude INTO (city, lati, longi) FROM sgeocity
WHERE city IN ( SELECT cityfrom FROM spfli
WHERE carrid = carr_id AND connid = conn_id ).
这类子查询没有返回值,也不要求SELECT从句中只有一个选择列,选择列可以任意个数,WHERE 或者 HAVING从句根据该子查询的是否查询到数据来决定外层主查询语句来选择相应数据
SELECT carrname INTO TABLE name_tab FROM scarr
WHERE EXISTS ( SELECT * FROM spfli
WHERE carrid = scarr~carridAND cityfrom = 'NEW YORK' ).
上面的示例子查询即为相关子查询
如果某个子查的WHERE条件中引用了外层查询语句的列,则称此子查询为相关子查询。相关子查询对外层查询结果集中的每条记录都会执行一次,所以尽量少用相关子查询
MAX、MIN、AVG、SUM、COUNT,聚合函数都可以加上DISTINCT选项
如果将统计函数与GROUP BY子句一起使用,那么Select语句中未出现在统计函数的数据库字段都必须在GROUP BY子句中出现。如果使用INTO CORRESPONDING FIELDS项,则需要在Select语句中通过AS后面的别名将统计结果存放到与之相应同名的内表字段中:
SELECT MIN( price ) AS mINTO price FROM sflight GROUP BY carrid
HAVING MAX(price)>10. Having从句中比较统计结果时,需要将统计函数重写一遍,而不能使用Select中定义的别名
ENDSELECT.
DATA: c TYPE cursor.[ˈkɜ:sə]
DATA: wa TYPE spfli.
"1、打开游标
OPEN CURSOR: c FOR SELECT carrid connid FROM spfli WHERE carrid = 'LH'.
DO.
"2、读取数据
FETCH NEXT CURSOR c INTO CORRESPONDING FIELDS OF wa.
IF sy-subrc <> 0.
"3、关闭游标
CLOSE CURSOR c.
EXIT.
ELSE.
WRITE: / wa-carrid, wa-connid.
ENDIF.
ENDDO.
l 单记录缓存:从数据库中仅读取一条数据并存储到table buffer 中。此缓存只对SELECT SINGLE…语句起作用
l 部分缓存:需要在指定generic key(即关键字段组合,根据哪些关键字段来缓存,可以是部分或全部关键字段)。如果主键是由一个字段构成,则不能选择此类型缓存。当你使用generic key进行数据访问时,则属于此条件范围的整片数据都会被加载到table buffer中
1、查询时如果使用BYPASSING BUFFER 选项,除了绕过缓存直接到数据库查询外,查出的数据不会放入缓存
2、只要查询条件中出现了用作缓存区域的所有关键字段,则查询出所有满足条件全部数据进行缓存
3、如果查询条件中generic key只出现某个或者某部分,则不会进行缓存操作
4、如果主键是只由一个字段组成,则不能设定为此种缓存
5、如果有MANDT字段,则为generic key的第一个字段
l 全部缓存:在第一次读取表数据时,会将整个表的数据都会缓存下来,不管WHERE条件
DATA: BEGIN OF wa,
connid TYPE spfli-connid,
cityfrom TYPE spfli-cityfrom,
cityto TYPE spfli-cityto,
END OF wa.
DATA c1 TYPE spfli-carrid VALUE 'LH'.
"Native SQL语句不能以句点号结尾;
"不能在EXEC SQL…ENDEXEC间有注释,即不能有星号与双引号的出现;
"参数占位符使用冒号,而不是问号;
EXEC SQL PERFORMING loop_output.
SELECT connid, cityfrom, cityto
INTO :wa
"或使用:INTO :wa-connid ,:wa-cityfrom ,:wa-cityto
FROM spfli
WHERE carrid = :c1
ENDEXEC.
FORM loop_output.
WRITE: / wa-connid, wa-cityfrom, wa-cityto.
ENDFORM
EXEC SQL.
EXECUTE PROCEDURE proc1 ( IN:x,OUT:y,INOUT:z )
ENDEXEC.
DATA: arg1 TYPE string VALUE '800'.
TABLES: t001.
EXEC SQL.
OPEN c1 FOR SELECT MANDT, BUKRS FROM T001 "打开游标
WHERE MANDT = :arg1 AND BUKRS >= 'ZA01'
ENDEXEC.
DO.
EXEC SQL.
FETCH NEXT c1 INTO :t001-mandt, :t001-bukrs "读取游标
ENDEXEC.
IF sy-subrc <> 0.
EXIT.
ELSE.
WRITE: / t001-mandt, t001-bukrs.
ENDIF.
ENDDO.
EXEC SQL.
CLOSE c1 "关闭游标
ENDEXEC.
通用数据库表锁函数:ENQUEUE_E_TABLE、DEQUEUE_E_TABLE、DEQUEUE_ALL(解锁所有)[kju:]
特定数据库表锁函数:ENQUEUE_
自定义的锁对象都必须以EZ_ 或者EY_ 开头来命名
|
允许第二次加锁模式 |
||
第一次加锁模式 |
S |
E |
X |
S 共享锁 |
是(是) |
否(是) |
否(否) |
E 可重入的排他锁 |
否(是) |
否(是) |
否(否) |
X 排他锁 |
否(否) |
否(否) |
否(否) |
括号内为同一程序(即同一事务内)内,括号外为非同一程序内
CALL FUNCTION 'ENQUEUE_EZ_ZSPFLI'"加锁
EXPORTING
mode_zspfli = 'E'
mandt = sy-mandt
carrid = 'AA'
connid = '0011'
* X_CARRID = ' '"设置字段初始值(Initial Value),若为X,则当遇到与CARRID的初始值Initial Value相同值时才会设置锁对象。CARRID的初始值只需在数据库表字段中选择Initial Value选项(SE11中设置)。当没有设置X时,则会用该锁函数所设置的Default Value指定初始值
* X_CONNID = ' '
* _SCOPE = '2'"该选项只有在UPDATE函数(CALL FUNCTION FM IN UPDATE TASK)中才起作用,用来控制锁的传递。一般当调用解锁函数DEQUEUE或程序结束时(LEAVE PROGRAM或者LEAVE TO TRANSACTION)锁就会被自动解除,另外,遇到A、X消息时或用户在命令框中输入/n 时锁也会被解除,但是,当事务码正在执行UPDATE函数时就不一样了,函数结束时是否自动解除锁则要看该选项 _SCOPE 了:
1-表示程序内有效 2-表示update module 内有效 3-全部
* _WAIT = ' '"表示如果对象已经被锁定,是否等待后再尝试加锁
* _COLLECT = ' '"参数表示是否收集后进行统一提交
程序锁定:ENQUEUE_ES_PROG和DEQUEUE_ES_PROG
DB LUW(Logic Unit Work)是确保数据库更新一致性的机制,是数据库级别的,和底层DBMS有关,和SAP系统无关。如下图,从一致性状态A到B,中间有一系列的数据库操作,一个BD luw以数据库提交commit结束,这些操作要么全都执行,要么全都不执行。当全部执行成功,则数据库进入一致性状态B,如果在此DB luw中发生错误,则将从DB luw开始的所有操作进行回滚,数据库还是在A状态。
这是在数据库级别实现的,和SAP系统无关。
在SAP系统中,DB luw commit and rollback 可以被显式或隐式的触发。
不管是commit还是rollback,结束了一个DB luw,也即是开始了一个新的DB luw。
·Native SQL提交语句
·Calling the function module DB_COMMIT.
·COMMIT WORK语句
·对话屏幕结束时(跳转另一屏幕)
·同步或异步方式远程调用RFC时:
CALL FUNCTION func DESTINATION dest
CALL FUNCTION func STARTING NEW TASK DESTINATION dest taskname
但RFC事务调用不会触发隐式提交:CALL FUNCTION func IN BACKGROUND TASK DESTINATION dest
·取RFC异步执行结果回调Form中RECEIVE 语句:CALL FUNCTION rfm_name STARTING NEW TASK DESTINATION dest tasknamePERFORMING return_form ON END OF TASK中的RECEIVE RESULTS FROM FUNCTION rfm_name语句会触发
·WAIT UNTIL log_exp [UP TO sec SECONDS]语句
·Sending error messages(E), information messages(I), and warnings(W).
·使用Native-SQL进行回滚
·使用ROLLBACK WORK.进行回滚
·runtime error:在程序执行过程中未捕获的异常以及message type X的消息,因为runtime error会导致程序的termination
·因为发送的Message而导致程序的termination(除了A、X类型的消息会导致程序终止外,其他类型的消息在特定的情况下也会导致程序终止,具体请参见《User Dialogs.docx》中的消息显示及处理章节)
Runtime Error:系统不能继续运行,并且ABAP程序终止,以下情况会发生运行时错误:
²未处理的异常(当一个能够被处理的异常却未被处理时;抛出了一个不能够处理的异常)
²X类型消息
² ASSERT断言语句不成立时
sap系统中一个业务操作会有多个对话屏幕,只到save操作成功,才算完成了一个业务。那么仅使用DB luw是不能保证SAP 系统数据一致性的。如下图,如果只是最后屏幕300保存时的DB luw发生错误,那么在数据库级一致性机制作用下只能回滚这一个db luw使数据库处于g状态,而前几个db luw在屏幕结束时已经提交,进行的数据库更新会全部生效,从业务层面上讲,这是不合理的,因为这个业务并没有保存成功,我们需要回滚到A或a状态。SAP luw就是sap在DB luw基础上保证数据库一致性的一种机制。
上图(未使用SAP LUW),绿色表示数据库更新操作。每个dialog steps都会形成一个单独的DB LUW。默认情况下(未通过SAP LUW的绑定方式来实现延迟执行数据库更新操作),每个DB LUW都会提交事务
上图(将多个DB LUW绑定成一个SAP LUW),将每个dialog steps(即DB LUW)中的数据库操作“移”到最后一个dialog steps(即DB LUW)中执行,这里的“移”实质上是一种延迟执行的方式,即先将前面每个dialog steps中的数据库更新操作记录下来,待最后统一由COMMIT来提交。
SAP LUW可以跨多个DB LUW,是一个业务逻辑上的概念,可人为的去定义开始与结束,而DB LUW是不能人为控制的
SAP LUW是DB LUW的一个增强,受体系结构限制,SAP程序每次屏幕切换时(控制权从后台DIALOG进程转移到前台GUI的Session),都会触发一个隐式的数据库提交,一个程序在运行是会产生多个DB 的LUW,这样无法做到全部提交或全部回滚,在某些业务场景下,这种事务的提交机制不足以保证数据的一致性,为此有了SAP LUW机制。
SAP LUW是一种延迟执行的技术,它将本来需要执行的程序块,记录下来(通过特定的方式来调用来实现SAP LUW的绑定:perform XXX on commit、update Funciton module),待系统在执行COMMIT WORK的时候会查询记录,真正执行需要运行的代码。COMMIT WORK一般在最后一个屏幕执行,这样就实现了将跨屏幕的数据更新逻辑绑定到一个DBLUW中(在后台会自动将这一系列的数据操作绑定在同一个数据事务DBLUW里,这样当提交出问题后,数据库事务DBLUW会自动回滚,这样就保证了数据的一致性),实现复杂情况数据更新的一致性
可以使用CALL FUNCTION update_function IN UPDATE TASK将多个数据更新绑定到一个database LUW中
此类型的更新函数Update Function中主要实现对数据库数据进行更新操作
² Immediate start:表示V1方式,将此更新函数设置为高优先级V1并运行在同一个SAP LUW中。更新出错后,可以在SM13里重新执行
² Immediate start -no restrat possible:V1方式,将此更新函数设置为高优先级V1并运行在同一个SAP LUW中。出错后不可以在SM13里重新执行
² Start delayed:V2方式,将此更新函数设置为低优先级V2并run in their own update transactions。V1方式更新完成后触发。出错后更新函数可以重启
² Collective run:V2方式,将此更新函数设置为低优先级V2并run in their own update transactions。需使用Collective(RSM13005)程序手动或JOB方式执行
V1与V2区别:
² V1优先级高于V2,V2被设计为依赖于V1,适合执行需要在V1完成后进行的操作
² V1更新使用V1进程处理,V1进程名字一般为UPD,V1进程绑定独立的数据库进程。在V1进程中调度的更新函数如果更新失败,回滚,不再进行V2操作。成功则提交更改到数据库,同时删除所有的SAP锁
² V2更新使用V2进程处理,如果没有配置V2进程则共用V1进程,V2进程名字为UP2,V2更新在独立DB LUW中,V2更新回滚后不会影响到V1更新提交的数据,由于V1更新结束后会删除SAP的锁,所以V2更新是在没有逻辑锁的情况下进行的,V2更新出错后可以在SM13中重新执行
² V1 的执行模式可以为异步、同步或本地;V2只能为异步执行
CALL FUNCTION ... IN UPDATE TASK的Function并不会立即执行,这只是先将它记录到内存(本地方式)或数据库表中(非本地方式),等到COMMIT WORK语句执行时才真正执行。如果在程序执行过程中,没有在Update Function 后面执行COMMIT WORK,则function module不会执行
此种类型的输入参数只能是值传递方式,不允许为引用传递,所以输入参数对应的“传递值”要钩上,并且传递的内容还不能是地址,即不允许为TYP EREF TO方式,只能是TYPE方式
PERFORM subr ON { {COMMIT [LEVEL idx]} | ROLLBACK }.
子过程subr不会被马上执行,而是直到COMMIT WORK 或者是ROLLBACK WORK。可以使用LEVEL参数指定优先级,优先级按升序进行排列,较小的会优先执行。如果同样名字的subroutine被注册了多次,在COMMIT WORK时只执行一次,IN UPDATE TASK方式执行的Funciton没有这个限制
由于子过程绑定不需要写数据库表,所以比较更新函数绑定,性能上要高一些,缺点是你不能传递任何参数,它们所使用的必须是global data object,或可通过ABAP memory来共享参数
没有专门类似开启数据库事务的语句(Begin trasaction),SAP LUW会在以下情况自动开启:
²提交COMMIT或回滚ROLLBACK后会开启另一个新的SAP LUW事务
²事务调用会重新开启一个新的SAP LUW
上图中F2不会被执行,因为它处于另一个事务中执行,但没有使用COMMIT WORK,而F1、F3会被执行,因为主调程序中使用了COMMIT WORK。所以事务调用会重新开启一个新的SAP LUW。
dialog modules与上面调用事务(CALL transaction)、调用executable programs (reports)不同的是:dialog modules中不会开启新的SAP LUW,如果dialog modules中调用了update function modules(CALL FUNCTION ... IN UPDATE TASK),则要等到主调程序中的COMMIT WORK时才会真正执行,哪怕在dialog modules中调用了COMMIT WORK也是没有用(不会启动update task)。
另外,由于数据共享问题,尽量不要在dialog modules中使用PERFORM XXX ON COMMIT。
COMMIT WORK 异步更新,该语句执行后不会等待所有更新函数都执行完后才继续执行后面的代码
COMMIT WORK AND WAIT同步更新,该语句执行后会等待所有更新函数都执行完后才继续执行后面的代码,执行结果可以通过sy-subrc来判断事务提交是否成功
本地方式:如果在调用UPDATE FUNCTION之前,先调用SET UPDATE TASK LOCAL.语句,这样所有在该语句后使用CALL FUNCTION...IN UPDATE TASK注册的更新函数不会记录到数据库中,而是记录在内存中,在Commit work之后,会从内存取得待执行的函数。在默认情况下,local update不会被设置,在使用COMMIT WORK之后 SET UPDATE TASK LOCAL的效果会被清除掉
本地方式更新只能采用同步方式,即使没有在Commit work后指定了and wait参数,仍然是同步执行
非本地方式:未使用SET UPDATE TASK LOCAL.语句。此方式下,注册的Update Function的名字以及接口实参都会以日志的形式记录到特殊的表。非本地方式即可同步也可异步COMMIT
² 本地方式不将待执行的更新函数写到数据表中,减少了I/O操作,效率上较高,但由于采用的是同步方式,程序需等待更新结果,用户交互时的会感觉程序运行较慢
² 非本地方式会将更新结果记录到数据表中,可以通过SM13查看更新情况,同时由于可以进行异步更新,用户交互时感觉会比较快
SLDB
决定了数据从哪些数据库表、视图中提取数据,以及这些表、视图之间的层次关系(层次将决定数据读取的顺序)
数据库表(T类型节点)、词典类型(S类型节点),比如节点类型为S的节点:root_node,数据类型为INT4:
LDB的数据库程序的最TOP Include文件包括以下语句:
NODES root_node.
另外,在LDB数据库程序包括了以下过程:
FORM put_root_node.
DO 10 TIMES.
root_node = sy-index.
PUT root_node."会去调用报表程序中的 GET root_node. 事件块
ENDDO.
ENDFORM.
在与此LDB关连的可执行程序:
REPORT demo_nodes.
NODES root_node.
GET root_node.
WRITE root_node.
定义了LDB的选择屏幕,该选择屏幕的布局由LDB的结构决定,一旦将LDB链接到报表程序后,该选择屏幕会自动嵌入到默认选择屏幕1000中
第一次进入选择屏幕程序时,系统会为每个LDB生成一个名为DB<LDB_Name>SELInclude选择屏幕包含文件:
而且,所有表(T类型的节点)的主键都会出现在SELECT-OPTIONS语句中,成为屏幕选择字段(自动生成的需要去掉注释,并设置屏幕选择字段名):
除了上面自动生成的LDB屏幕字段外,还可以使用以下面语句来扩展LDB选择屏幕:
增加一个单值输入条件框(PARAMETERS语句一般在LDB中只用于除节点表外的非表字段屏幕参数),在PARAMETERS语句中必须使用选项FOR NODE XXX 或者 FOR TABLE XXX 来指定这些扩展参数属性哪个节点的:PARAMETERS CITYTO LIKE SPFLI-CITYTO FOR NODE SPFLI.
注:SELECT-OPTIONS没有FOR NODE这样的用法
具体请参数后面的LDB选择屏幕章节
使用SELECTION-SCREEN语句来格式化屏幕
具体请参数后面的LDB选择屏幕章节
SELECTION-SCREEN DYNAMIC SELECTIONS FOR NODE|TABLE
上面LDB数据库程序中的RSDS_WHERE条件内表来自RSDS类型组,相应源码如下:
另外,上面LDB数据库程序中要能从DYN_SEL-CLAUSES内表读取数据,则必须在LDB选择屏幕里开启相应节点的动态选择条件:
其中,DYN_SEL-CLAUSES内表行结构如下:
PUT_
TYPE-POOLS RSDS.
DATA DYN_SEL TYPE RSDS_TYPE.
你不必在程序中定义它就可以直接使用,但它只能在LDB数据库程序中使用,而不能用在报表程序中。RSDS_TYPE数据类型是在类型组RSDS中定义的:
TYPE-POOL RSDS .
TYPES: RSDS_WHERE_TAB LIKE RSDSWHERE OCCURS 5."RSDSWHERE 类型为C(72)
TYPES: BEGIN OF RSDS_WHERE,
TABLENAME LIKE RSDSTABS-PRIM_TAB,
WHERE_TAB TYPE RSDS_WHERE_TAB,
END OF RSDS_WHERE.
TYPES: RSDS_TWHERE TYPE RSDS_WHERE OCCURS 5.
TYPES: BEGIN OF RSDS_TYPE,
CLAUSES TYPE RSDS_TWHERE,
TEXPR TYPE RSDS_TEXPR,
TRANGE TYPE RSDS_TRANGE,
END OF RSDS_TYPE.
RSDS_TYPE是一个深层结构的结构体,里面三个字段都是内表类型,其中以下两个字段重要:
为Where从句部分,实则存储了可直接用在WHERE从句中的动态Where条件内表,可以在Where动态语句中直接使用,该组件为内表,存储了用户在选择屏幕上选择的LDB动态选择字段
每个被选择的LDB屏幕动态选择字段都会形成一个条件,并存储到RSDS_TYPE-CLAUSES-WHERE_TAB内表中,WHERE_TAB内表中存储的就是直接可以用在Where从句中的动态选择条件中
每个表(节点)都会有自己的CLAUSES-WHERE_TAB动态条件内表,这是通过CLAUSES-TABLENAME区别的
现假设有名为 ZHK 的LDB,SCARR为该LDB的根节点,且仅有SPFLI一个子节点。LDB选择屏幕 Include文件DBZHKSEL内容如下:
SELECT-OPTIONS S_CARRID FOR SCARR-CARRID.
SELECT-OPTIONS S_CONNID FOR SPFLI-CONNID.
"需要先开始动态选择条件功能
SELECTION-SCREEN DYNAMIC SELECTIONS FOR TABLE SCARR.
LDB数据库程序SAPDBZHK中,PUT_SCARR过程中使用dynamic selection的过程如下:
FORM PUT_SCARR.
STATICS: DYNAMIC_SELECTIONS TYPE RSDS_WHERE,FLAG_READ. "定义成静态类型的是防止再次进入此Form时,再次初始化DYNAMIC_SELECTIONS结构,即只执行一次初始化代码
IF FLAG_READ = SPACE.
DYNAMIC_SELECTIONS-TABLENAME = 'SCARR'.
READ TABLE DYN_SEL-CLAUSES WITH KEY DYNAMIC_SELECTIONS-TABLENAME INTO DYNAMIC_SELECTIONS.
FLAG_READ = 'X'.
ENDIF.
SELECT * FROM SCARR WHERE CARRID IN S_CARRID AND (DYNAMIC_SELECTIONS-WHERE_TAB). "使用动态Where条件
PUT SCARR.
ENDSELECT.
ENDFORM.
该字段是一个内表,存储了CLAUSES的原数据,CLAUSES内表里的数据实质就是来源于TRANGE内表,只是CLAUSES已经将每个表字段的条件拼接成了一个或多个条件串了(形如:“XXX字段 = XXX条件”),但是TRANGE内表与RANGES tables相同,存储的是字段最原始的条件值,使用时,在WHERE从句中使用 IN 关键字来使用这些条件值(这与SELECT-OPTIONS类型的屏幕参数用户是完全一样的)。
但是,使用TRANGE没有直接使用CLAUSES灵活,因为使用TRANGE时,WHERE从句里的条件表字段需要事先写好,这实质上不是动态条件了,可以参考以下实例,与上面CLAUSES用法相比就更清楚了:现修改上面的示例:
SELECT-OPTIONS S_CARRID FOR SCARR-CARRID.
SELECT-OPTIONS S_CONNID FOR SPFLI-CONNID.
"需要先开始动态选择条件功能
SELECTION-SCREEN DYNAMIC SELECTIONS FOR TABLE SCARR.
LDB数据库程序SAPDBZHK中,PUT_SCARR过程中使用dynamic selection的过程如下:
FORM PUT_SCARR.
STATICS: DYNAMIC_RANGES TYPE RSDS_RANGE, "存储某个表的所有屏幕字段的Ranges
DYNAMIC_RANGE1 TYPE RSDS_FRANGE,"存储某个屏幕字段的Ranges
DYNAMIC_RANGE2 TYPE RSDS_FRANGE,
FLAG_READ."确保DYN_SEL只读取一次
IF FLAG_READ = SPACE.
DYNAMIC_RANGES-TABLENAME = 'SCARR'.
"先取出 SCARR 表的所有屏幕字段的Ranges
READ TABLE DYN_SEL-TRANGE WITH KEY DYNAMIC_RANGES-TABLENAME INTO DYNAMIC_RANGES.
"再读取出属于某个字段的Ranges
DYNAMIC_RANGE1-FIELDNAME = 'CARRNAME'.
READ TABLE DYNAMIC_RANGES-FRANGE_T WITH KEY DYNAMIC_RANGE1-FIELDNAME
INTO DYNAMIC_RANGE1.
DYNAMIC_RANGE2-FIELDNAME = 'CURRCODE'.
READ TABLE DYNAMIC_RANGES-FRANGE_T WITH KEY DYNAMIC_RANGE2-FIELDNAME
INTO DYNAMIC_RANGE2.
FLAG_READ = 'X'.
ENDIF.
SELECT * FROM SCARR
WHERE CARRID IN S_CARRID
AND CARRNAME IN DYNAMIC_RANGE1-SELOPT_T"使用IN 关键字使用Ranges内表
AND CURRCODE IN DYNAMIC_RANGE2-SELOPT_T."(与select-options屏幕参数是一样的用法)
PUT SCARR.
ENDSELECT.
ENDFORM.
SELECTION-SCREEN FIELD SELECTION FOR NODE|TABLE
在可执行报表程序里,可以通过GET node [FIELDS f1 f2 ...] 语句中的 FIELDS选项来指定要读取字段;
另外,上面LDB数据库程序中要能从SELECT_FIELDS内表读取数据,则必须在LDB选择屏幕里开启相应节点的动态选择字段:
其中,SELECT_FIELDS内表行结构如下:
PUT_
TYPE-POOLS RSFS.
DATA SELECT_FIELDS TYPE RSFS_FIELDS.
RSDS_FIELDS中的FIELDS里存储的就是GET…FIELDS…语句传递过来的用户指定的查询字段,FIELDS内表可以直接使用在 SELECT…从句中。
现假设有名为 ZHK 的LDB,SCARR为该LDB的根节点,且仅有SPFLI一个子节点。LDB选择屏幕 Include文件 DBZHKSEL内容如下:
SELECT-OPTIONS S_CARRID FOR SCARR-CARRID.
SELECT-OPTIONS S_CONNID FOR SPFLI-CONNID.
"需要先开始动态选择字段功能
SELECTION-SCREEN FIELD SELECTION FOR TABLE SPFLI.
LDB数据库程序SAPDBZHK中,PUT_SCARR过程中使用dynamic selection的过程如下:
FORM PUT_SPFLI.
STATICS: FIELDLISTS TYPE RSFS_TAB_FIELDS,
FLAG_READ."确保SELECT_FIELDS只读取一次
IF FLAG_READ = SPACE.
FIELDLISTS-TABLENAME = 'SPFLI'.
"读取相应表的动态选择字段
READ TABLE SELECT_FIELDS WITH KEY FIELDLISTS-TABLENAME INTO FIELDLISTS.
FLAG_READ = 'X'.
ENDIF.
SELECT (FIELDLISTS-FIELDS)"动态选择字段
INTO CORRESPONDING FIELDS OF SPFLI FROM SPFLI
WHERE CARRID = SCARR-CARRID AND CONNID IN S_CONNID.
PUT SPFLI.
ENDSELECT.
ENDFORM.
在相应的可执行报表程序里,相应的代码可能会是这样的:
TABLES SPFLI.
GET SPFLI FIELDS CITYFROM CITYTO.
...
GET语句中的FIELDS选项指定了除主键外需要查询来的字段,主键不管是否选择都会被从数据库表中读取出来,可以由下面报表程序中的代码来证明:
DATA: ITAB LIKE SELECT_FIELDS,
ITAB_L LIKE LINE OF ITAB,
JTAB LIKE ITAB_L-FIELDS,
JTAB_L LIKE LINE OF JTAB.
START-OF-SELECTION.
ITAB = SELECT_FIELDS. "在报表程序中也可以直接使用LDB程序中的全局变量!
LOOP AT ITAB INTO ITAB_L.
IF ITAB_L-TABLENAME = 'SPFLI'.
JTAB = ITAB_L-FIELDS.
LOOP AT JTAB INTO JTAB_L.
WRITE / JTAB_L.
ENDLOOP.
ENDIF.
ENDLOOP.
如果报表程序中的GET语句是这样的:GET SPFLI FIELDS CITYFROM CITYTO.,则输入结果为:
CITYTO
CITYFROM
MANDT
CARRID
CONNID
可以从输出结果看出,主键MANDT、CARRID、CONNID会自动的加入到SELECT_FIELDS内表中,一并会从数据库中读取出来
• FORM INIT
在选择屏幕处理前仅调用一次(在PBO之前调用)
• FORM PBO
在选择屏幕每次显示之前调用,即LDB选择屏幕的PBO事件块
• FORM PAI
用户在选择屏幕上输入之后调用,即LDB选择屏幕的PAI事件块(之后?)。
该FORM带两个接口参数FNAME and MARK将会传到subroutine 中。FNAME存储了选择屏幕中用户所选择SELECT-OPTION与PARAMETERS的屏幕字段名,MARK标示了用户选择的是单值还是多值条件:MARK = SPACE意味着用户输入了一个简单单值或者范围取值,MARK = '*'意味着用户在Multiple Selection screen 的输入(即多个条件值);FNAME = '*' 和 MARK = 'ANY',表示所有的屏幕参数都已检验完成,即可以对屏幕整体参数做一个整体的检测了(这里的意思应该就是相当于AT SELECTION-SCREEN)。
• FORM PUT_
最顶层节点
PUT
此语句用是PUT_
PUT语句是该Form(PUT_
当PUT_
首先,根节点所对应的PUT_
1. 如果LDB数据库程序包含了AUTHORITY_CHECK_