15. 数据格式化、转换
15.1. 数据输入输出转换
15.1.1. 输出时自动转换
如果某个变量参照的数据元素所对应的Domain具有转换规则,那么在输出时(如Write输出、ALV展示、文本框中显示),最后显示的结果会自动发生转换,如参照 ekpo-meins 表字段的变量赋值时就会发生转换,因为 ekpo-meins 所对应的元素Doamin设置了转换规则:
所以,在显示输出这样的数据时要注意,如果要显示原始数据,则不能参照该表字段来定义变量,而是自己定义。
DATA:i_meins LIKE ekpo-meins,
i_meins2 TYPE c LENGTH 3.
START-OF-SELECTION.
SELECT meins meins FROM ekpo INTO (i_meins,i_meins2) WHERE ebeln = '4500012164'.
"输出时, i_meins会自动发生转换,但 i_meins2 不会
WRITE: i_meins,i_meins2.
ENDSELECT.
SKIP.
DATA: i_meins3 LIKE ekpo-meins.
"注:这里只能是内部单位ST,而不是PC,因为Write时是输出转换(即内->外的转换)
i_meins3 = 'ST'.
"只要是参考过 ekpo-meins 的变量,Write输出时自动转换
WRITE:/ i_meins3.
在调试过程中发现都是原始数据,自动转换发生在Write输出时:
15.1.2. 输入时自动转换
输出时会发生自动转换,那么,在输入时,如从选择屏幕上录入的数据是参照带有规则转换的Domain的数据元素创建的选择屏幕字段时,从界面录入到ABAP程序中时,会自动按照转换规则进行转换,如下面从界面上输入的是 PC (外部格式的单位),但录入到ABAP程序中时,自动转换为ST(内部格式的部位),但再次Write输出时,又将 ST转换为PC输出(从内部转换为外部格式):
15.1.3. 通过转换规则输入输出函数手动转换
除了上面通过借助于参照带有转换规则的表字段进行自动转换外,实质上可以通过转换规则对应的输入输出函数进行手动转换,如VBAK-vbeln的转换规则:
CONVERSION_EXIT_ALPHA_INPUT:输入转换,前面补齐零
此函数将字符类型的变量转换成SAP数据库中内部格式数据,如定单号vbeln的类型为 Char 10,如果输入的vbeln为6位,则会在前面补4个零(注:该函数的转换规则为:如果含有其他非数字,则不会补零,只有全部是数字时才补,这可以通过VBELN查看到),Number类型的不需要,因为在ABAP程序中N类型不足时长度时默认就会在前面补零(如 POSNR),而且Number类型的默认值就是全为零,而C类型不足时会以后面全补空格
CONVERSION_EXIT_ALPHA_OUTPUT:输出转换,去掉前导零
DATA: vbeln TYPE vbak-vbeln.
DATA: str TYPE string VALUE '600000'.
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING input = str
IMPORTING output = vbeln.
"自动输出转换,输出最初始数据,但程序内部已发生变化
WRITE: / vbeln."600000
15.2. 数量小位数格式化
WRITE <f> UNIT <u>.
该语句根据Unit <u>来设置<f>的小数位数(即保留小数点多少位,或精确到小数点后多少位),<u>为<f>的单位。<u>必须要在T006中进行过配置,并且<u>的值(单位KEY值)就是T006-MSEHI字段值,而T006-DECAN字段值决定<f>显示的小数位数,如果<u>在表T006中没有找到,将会忽略该UNIT选项
该选项的使用限制如下:
? <f>必须是P类型的
? 如果<f>本身的小数位比<u>所配置的小数位少时,系统会忽略该选项
? 如果<f>本身的小数位比<u>所配置的要多时,并且多余的小数位全部是零时,会被截断;如果多余的小数部分不是零时,也会直接忽略该选项
从上面的限制条件来看,该格式化输出只针对<f>的小数位超过了其单位<u>设置的小数位,且超过的小数要全是零才会起作用(去掉多余的零),如果<f>的小数位短于<u>设置的小数位,也不会再补后输出
"必须是P类型
DATA: p1 TYPE p LENGTH 8 DECIMALS 2.
p1 = '1.10'.
"如果<f>本身的小数位比<u>所配置的小数位小时,系统会忽略该选项
WRITE:/ p1 UNIT 'D10'."1.10
DATA: p3 TYPE p LENGTH 8.
p3 = '1'.
WRITE:/ p3 UNIT 'D10'."1
DATA:p2 TYPE p LENGTH 8 DECIMALS 4.
p2 = '1.1000'.
"多余的小数位全部是零时,会被截断
WRITE:/ p2 UNIT 'D10'."1.100
p2 = '1.1001'.
"多余的小数部分不是零时,也会直接忽略该选项
WRITE:/ p2 UNIT 'D10'."1.1001
DATA: i_menge LIKE ekpo-menge VALUE '1.000'.
"注:UNIT选项后面一定要是内部单位ST,而不是外部单位PC,因为这里是WRITE输出,
"即内部转换外部,将数据库表存储的原数据格式化输出显示
WRITE: / i_menge UNIT 'ST'."1
WRITE: / i_menge."1.000
15.2.1. 案例
问:通过se11 我们可以看到ekpo中menge的数据元素是BSTMG,BSTMG的域是长度13小数位3位。在程序中我参照ekpo-menge定义的变量显示的时候后面都有3位小数,而我希望输出时与me23n一样,即去掉小数点后面多余的零,请问大侠们有没有比较好的办法。为什么me23n中“PO数量”显示的时候没有多余的零,而他们的数据元素是一样的。
答:MENGE实际上是个存储度量衡值的字段,他的基本数据类型是QUAN,他的小数位数并不是你看到的3,而是由这个字段关联的度量衡单位决定的,以MENGE为例,你可以在SE11的最右边一个Tab页,Currency/Quantity Fields里看到,他关联的单位是EKPO-MEINS
DATA: i_menge LIKE ekpo-menge,
i_meins LIKE ekpo-meins,
i_meins2 TYPE c LENGTH 3. "没有参照表字段ekpo-meins,所以Write输出时不会自动输出转换
SELECT menge meins meins FROM ekpo INTO(i_menge,i_meins,i_meins2) WHERE ebeln = '4500012164'.
"带单位的数量需要根据单位进行格式化输出,这样才与ME23N 中显示的数据一样
WRITE: / i_menge UNIT i_meins,i_meins, i_menge,i_meins2.
ENDSELECT.
在ALV中显示时,如果是金额或数量时,需通过Fieldcat设置cfieldname 、ctabname ;qfieldname、qtabname这样在显示时才会正确
也可直接使用Domain所配置的转换规则所对应的输入输出转换函数CONVERSION_EXIT_CUNIT_INPUT、 CONVERSION_EXIT_CUNIT_OUTPUT来手动对单位进行转换:
15.3. 单位换算:UNIT_CONVERSION_SIMPLE
PARAMETERS: p_in TYPE p DECIMALS 3,
unit_in LIKE t006-msehi DEFAULT 'M',"米
unit_out LIKE t006-msehi DEFAULT 'MM',"毫米
round(1) TYPE c DEFAULT 'X'.
DATA: result TYPE p DECIMALS 3.
CALL FUNCTION 'UNIT_CONVERSION_SIMPLE'
EXPORTING
input = p_in
round_sign = round"舍入方式(+ up, - down, X comm, SPACE.)
unit_in = unit_in
unit_out = unit_out
IMPORTING
output = result.
WRITE: 'Result: ',result.
15.4. 货币格式化
WRITE <f> CURRENCY <c>.
输出金额<f>时,会根据该语句设置的货币代码<C>来决定其小数位置,如果货币代码<c>在表TCURX(CURRKEY)表中存在,则系统将根据TCURX-CURRDEC字段的值来设置<f>的小数点的位置,否则将<f>转换成具有2位小数的数字。这就意味着除非<f>本身就是类型为P(.2)(即货币的最大单位与最小单位换算为100时,如CNY人民币、USD美元)的金额字段,否则需要在TCURX表中配置所对应币种的小数位(因为不配置时会采用默认的2位)。
注意:这里的<f>一般是从数据库里读取出来的金额数据才需要这样格式化输出的,如果<f>本身存储的就是真实的金额,则不需要格式再输出,而是直接输出;另外,这里的格式化只是简单机械的根据TCURX-CURRDEC所配置的小数位置来设置金额的小数点位置(而并不是乘以或除以某个转换率),并与金额变量<f>类型本身的具有多少小数位有关:如果<f>的类型为P(6.5),值为<f> = 1.234时,且TCURX表里配置的小数位为2时,最后输出的是 1234.00 ,而不是12.34(如果是根据转换率来除,则结果会正确),因为在格式化前,会将小数末的0(1.23400)也参与处理,并不理会<f>本身原来的小位数,而是将所有的数字位(抛开小数点,但包括末尾的0)看作是待格式会的数字字符串:
DATA: p(6) TYPE p DECIMALS 5.
p = '1.234'.
WRITE: p CURRENCY 'aa'."1,234.00
TCURX:货币小数位表
TCURC:货币代码表
TCURR:汇率表
SAP表里存储的并不是货币的最小单位,一般是以货币最大单位(也是常用计量单元)来存储,不过在存储之前会使用经过转换:比如存储的金额是 100,则存储到表之前会除以一个转换因子后再存入数据表中(该转换因子是通过CURRENCY_CONVERTING_FACTOR函数获得的,如比CNY的转换因子为1,JPY为100),所以如果要读取出来自已进行展示,则需要再次乘以这个因子才能得到真正的金额数。另外,数据库中存储的虽然不是最小单位,但取出来后都是放在P类型的变量中的,所以取出来在内存中统计是不会有精度丢失的(P类型相当于Java中的BigDecimal类类型)。
TCURX-CURRDEC中存储的小数位实质上是根据同种币种的最大单位与最小的换算率= 10X来计算得到的,式中的X即TCURX-CURRDEC表字段中的小数位,如CNY中的最大单位元与最小单位分相差100倍,所以100 = 10X,X就为2,最后TCURX-CURRDEC存储的就是2(但如果值为2是可以不需要在TCURX表中配置的,所以查不到CNY的配置数据,因为不配置时默认值也是2);另外,JPY日元没有最小单位,所以最大单位与最小单位的换算率就是1(1 = 10X),所以X就为0,所以TCURX-CURRDEC存储的就是0。而转换因子计算式为:转换因子 = 100/10X,(CNY人民币:100/10X=100/102 =1,JPY日元:100/10X=100/100 =100),即转换因子 = 100/货币的最大单位与最小单位换算率,金额入库时需要除以这个转换因子,读取出来展示前需要乘以这个转换因子
数据库中用来存储金额的字段的类型都是P(.2),即带两位小数,因为转换因子最大也就是100(除以100后,即为小数点后2位),有的是零点几(在存入之前会将真实金额除以这个转换因子后再存入),所以存储类型为两位小数的数字类型即可。ABAP程序中用来存储从表中读取出来的内部金额的变量类型一定要具有两位类型的,否则在使用诸如CONVERT_TO_LOCAL_CURRENCY、CONVERT_TO_FOREIGN_CURRENCY转换函数或者是格式化输出时,都会有问题,所以在ABAP程序中定义这些用来存储数据库表中所存内部金额变量时,最好参照相应词典类型。
15.4.1. 从表中读取日元并正确的格式化输出
DATA: netpr LIKE vbap-netpr,"实际的类型为p(6.2)
waers LIKE vbap-waerk,
jpy_netpr TYPE i,
netpr1(6) TYPE p DECIMALS 3.
"通过SQL从数据库查询出来的是真实存储在表里的数据,没有经过其他转换,日元存到数据库中时缩小了100倍,所以要还原操作界面上输入的日元金额,则需要使用后面的格式化输出
SELECT SINGLE netpr waerk INTO (netpr,waers) FROM vbap WHERE waerk = 'JPY' AND vbeln = '0500001326'.
WRITE: waers,netpr."数据库中的值,被缩小了100倍
"第一种还原方式
WRITE: / 'Format:', netpr CURRENCY waers.
"第二种转换方式:也可以通过以下函数先获取JPY货币代码的转换因子,再直乘以这个因子也可
DATA: isoc_factor TYPE p DECIMALS 3.
CALL FUNCTION 'CURRENCY_CONVERTING_FACTOR'
EXPORTING
currency = waers
IMPORTING
factor = isoc_factor.
jpy_netpr = netpr * isoc_factor."乘以100倍,因为在存入表中时缩小了100倍
WRITE: / 'Calc factor:', jpy_netpr.
"格式化输出实质上是与存储金额的变量本身的类型小数位有关:上面将从表中读出的金额(小数两位)赋值给变量netpr1(小数三位),格式化后会扩大10倍(因为多了一位小数位)。所以格式化正确输出的前提是要用来接收从表中读取的金额变量的类型要与数据表相应金额字段类型相同,否则格式化输出会出错
netpr1 = netpr.
WRITE: / netpr1, netpr1 CURRENCY waers."格式化的结果是错误的
15.4.2. SAP 货币转换因子
一般而言,币种的小数位为2,所以系统默认的位数也是2,但是有一些特殊币种如日元JPY,没有小数位。只要小数位不等于2,需要在系统中特殊处理(通过转换因子进行转换,具体请参看后面SAP提供的函数 currency_converting_factor 实现过程)。在编程中
l List中,当输出CURR字段时,记得指定对应的货币:
如:WRITE: vbap-netwr CURRENCY vbap-waerk.
l Screen中,对于CURR字段,需要设置对应的货币字段:
l ALV中,需要对FIELD CATALOG进行设置
如:ls_cfieldname = 'WAERS'. "这里的WAERS是内表中的另一货币字段,里面存储了相应金额的货币代码
货币的是:fieldcat-cfieldname、fieldcat-ctabname(内表名,可以不设置)
顺便数量也是相似的方法来处理的:
数量的是:fieldcat-qfieldname、fieldcat-qtabname(内表名,可以不设置)
下面是SAP转换因子函数,在金额存储与在ALV展示时都会自动除以与乘以这个转换因子:
FUNCTION currency_converting_factor.
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:
* IMPORTING
*" VALUE(CURRENCY) LIKE TCURR-TCURR
*" EXPORTING
*" VALUE(FACTOR) TYPE ISOC_FACTOR
*" EXCEPTIONS
*" TOO_MANY_DECIMALS
*"----------------------------------------------------------------------
DATA: cur_factor TYPE isoc_factor.
*- determine Decimal place in currency from TCURX
CLEAR tcurx.
"首先根据币种到db表tcurx中读取相应的小数位数currdec
SELECT SINGLE * FROM tcurx WHERE currkey EQ currency.
"如果没有维护相应币别信息则默认currdec = 2
IF sy-subrc NE 0.
tcurx-currdec = 2.
ENDIF.
"如果currdec 大于了 5就报错
IF tcurx-currdec GT 5.
*- entry in tcurx with more than 5 decimals not allowed
RAISE too_many_decimals.
ENDIF.
*- compute converting factor respecting currency
"然后默认转换比率是100。如果表tcurx中的currdec = 0就默认转换比率为100
cur_factor = 100.
IF tcurx-currdec NE 0.
"在currdec不等于0的情况下循环currdec次,每次将转换比率除以10
DO tcurx-currdec TIMES.
"当表tcurx中没有找到相应数据时则默认currdec = 2,转换比率也就是100 / 10 / 10 = 1。其他
"的比如表tcurx中的currdec = 4,则转换比率应该为 100 / 10 / 10 / 10 / 10 = 0.01
cur_factor = cur_factor / 10.
ENDDO.
ENDIF.
IF cur_factor = 0.
*- factor 0 not allowed; check data definition of factor
*- entry in tcurx with more than 5 decimals not allowed
RAISE too_many_decimals.
ENDIF.
factor = cur_factor.
ENDFUNCTION.
简单的使用Function CURRENCY_CONVERTING_FACTOR,输入币种,就可以得到相应的转换比率了。我们在SE16中看到的货币金额基本上都经过了这个转换,如日元,都是除以100后存入数据库的。所以当我们从数据库中读取日元金额时也应该作相应的转换,乘以100 。
1、如果某货币的小数位不是2位,则需要通过OY04设置其小数位数,即需在TCURX表中进行维护
2、系统中的数据表存放的日元JPY、俄卢布RUR等货币比前台输入的金额小100倍,因为它们没有小数位,所以转换因子为100,存入表之前SAP会先将金额除以这个因子后再存入
3、系统根据转换因子将原金额转换成含小位小数的金额后存储(据说根据ISO的什么标准),如日元为0位小数,转换因子为100,120日元除以因子100后转换后变成1.20,缩小100倍。如为USDN为5位小数,其转换因子为100/10/10/10/10/10=0.001,12.01230除以0.001后则转换成12012.30,扩大1000倍。SAP在金额数据存储时会自动的转换,其实SAP是有external及internal的数据格式,可以调用以下函数实现相互转换。BAPI_CURRENCY_CONV_TO_INTERNAL:转换成数据库中内部存储金额,BAPI_CURRENCY_CONV_TO_external:转换成外部实际金额
4、每次币别的汇率更改在正式生产系统中新创建一条记录,利用函数CONVERT_TO_LOCAL_CURRENCY自动会把当前最近的时间的汇率作为转化的汇率,而不是直接在原纪录上更改
5、OB07、OB08,维护各币种之间的汇率。
6、碰到比较变态的货币,例如日元,它们是没有小数点的,系统内存储的和你看到的不同,有个BAPI可以使用:BAPI_CURRENCY_CONV_TO_INTERNAL
7、还有两个不同币种之间的转换FM:CONVERT_TO_FOREIGN_CURRENCY,和CONVERT_TO_LOCAL_CURRENCY基本没有区别,功能都是一样的,只是转换的源与目标相反而已:CONVERT_TO_FOREIGN_CURRENCY是将外币转换为本位币,而CONVERT_TO_LOCAL_CURRENCY是将本位币转换为其他外币
15.4.3. 货币内外格式转换
"所有金额的在数据库里(内部格式)都是带两位的小数数字类型用来存储内部金额时,用来存储金额的变量类型一定要与数据库表里的类型一致,否则使用WRITE输出时会不准确
DATA: usd(7) TYPE p DECIMALS 2,
jpy(7) TYPE p DECIMALS 2,
jpy_e(12) TYPE p DECIMALS 4.
DATA: usd_k TYPE waers, jpy_k TYPE waers.
DATA: ret TYPE bapireturn.
"此处为实际金额,所以不宜直接格式化(只有对内部表中存储格式的金额格式化输出才有意义,否则是错误的输出),不过这里为实际的金额似乎也有点不对,因为日元真实金额是不会有小数的,所以变量jpy用来存储外部实际金额是不妥的,jpy应该为整数类型才恰当
jpy = '10000.01'.
usd_k = 'USD'.
jpy_k = 'JPY'.
"使用CONVERT_TO_LOCAL_CURRENCY、CONVERT_TO_FOREIGN_CURRENCY函数时,涉及到的金额输入输出参数都是采用内部金额,所以在使用这些函数时,如果是外部金额,应先将它们转换为内部金额后再传入
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'"将一种货币兑换成另一种货币
EXPORTING
date = sy-datum
foreign_amount = jpy"该程序中的jpy本身为外部金额,但在这里会将
"它当作是内部金额,所以最后相当于外部金额1000001
foreign_currency = jpy_k
local_currency = usd_k
IMPORTING
local_amount = usd."转换出来的也是内部金额
*CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
* EXPORTING
* date = sy-datum
* foreign_amount = '1.00'"内部金额,美元的外部金额也是1.00美元
* foreign_currency = 'USD'
* local_currency = 'JPY'
* IMPORTING
* local_amount = usd."结果为内部金额:1.15,相当于外部金额为115日元
*CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
* EXPORTING
* date = sy-datum
* "如果内部金额没有小数,也要补上两位小数位0,否则实质金额不准确,这里正是
* "因为末尾未补两位0,所以这里的金额实质上为0.01美元,而不是1美元
* foreign_amount = '1'"内部金额,相当于外部0.01美元
* foreign_currency = 'USD'
* local_currency = 'JPY'
* IMPORTING
* local_amount = usd. "结果为:0.01内部金额,实质相当于外部金额1日元
WRITE: jpy, jpy_k,usd, usd_k.
"由于jpy本身为实际金额,所以不能在这里格式输出;但usd为内部
"格式的金额,所以需要使用格式化输出(但usd本身就是带两位小数
"的内部金额,转换
WRITE:/ jpy CURRENCY jpy_k, jpy_k,
usd CURRENCY usd_k, usd_k.
ULINE.
jpy_e = jpy.
"将外部金额转换为内部存储金额,实质上过程是将外部金额除以转换因子即可得到
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_INTERNAL'
EXPORTING
currency = jpy_k
amount_external = jpy_e"外部金额
max_number_of_digits = 23"没什么作用,一般写23即可
IMPORTING
amount_internal = jpy "转换后的内部存储金额
return = ret.
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = sy-datum
foreign_amount = jpy "源货币金额(内部格式)
foreign_currency = jpy_k"源货币类型
local_currency = usd_k"目标货币类型
IMPORTING
local_amount = usd."目标货币金额(内部格式)
WRITE: jpy, jpy_k,usd, usd_k.
WRITE: / jpy CURRENCY jpy_k, jpy_k,
usd CURRENCY usd_k, usd_k.