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. 數量小位數格式化
該語句根據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.