为了应对审计导出大量凭证的要求,写了此程序来导出bkpf及bseg表数据,此程序分两种导出方式,导出至本地及导出至服务器,因为数据量巨大,所以也支持后台导出至服务器(导出至本地不支持后台执行,sap服务器没办法把文件写到本地)。
经过测试导出到服务器的速度目前是,100万张凭证生成一个100兆的文件导出时间1分钟左右,但是这种方法有个弊端就是无法控制单元格格式(如果有大佬知道,欢迎留言)只能控制单元格写入的内容
程序设计的思路:
1.创建个自建表,维护两个表中需要导出的字段清单
2.取自建表清单数据,构造动态内表
3.构造动态取表字段数据
4.将动态数据存储至动态内表
5.根据自建表取表字段的描述,构造导出结构的表头
6.循环取到数据的动态内表,构造导出文件
7.导出至本地或者服务器
具体代码如下
代码语言:javascript复制REPORT ZFIR252.
TABLES :BKPF,BSEG.
TABLES:SSCRFIELDS.
DATA:OK_CODE LIKE SY-UCOMM.
DATA:STBL TYPE LVC_S_STBL.
DATA: GT_FIELD TYPE LVC_T_FCAT.
DATA: GT_FIELD2 TYPE LVC_T_FCAT.
DATA: GS_FIELD TYPE LVC_S_FCAT.
FIELD-SYMBOLS:<ITAB> TYPE STANDARD TABLE,
<WA>,<FT> TYPE LVC_T_STYL,
<FV>,<F1>,<F2>,<F3>.
FIELD-SYMBOLS:<ITAB2> TYPE STANDARD TABLE,
<WA2>,<FT2> TYPE LVC_T_STYL,
<FV2>,<F12>,<F22>,<F32>.
DATA: DY_TABLE TYPE REF TO DATA, ""取值的动态内表
DY_LINE TYPE REF TO DATA.
DATA: DY_TABLE2 TYPE REF TO DATA, ""输出的动态内表,每个字段暂定char128,
DY_LINE2 TYPE REF TO DATA.
FIELD-SYMBOLS: <DYN_TABLE> TYPE TABLE,
<GT_TABLE> TYPE TABLE,
<DYN_WA> TYPE ANY.
FIELD-SYMBOLS : <FS_TAB> TYPE ANY TABLE .
SELECTION-SCREEN BEGIN OF BLOCK A1 WITH FRAME TITLE TEXT-001.
SELECT-OPTIONS:S_BUKRS FOR BKPF-BUKRS . "公司代码
SELECT-OPTIONS:S_BLART FOR BKPF-BLART . "凭证类型
SELECT-OPTIONS:S_BELNR FOR BKPF-BELNR . "会计凭证
SELECT-OPTIONS:S_HKONT FOR BSEG-HKONT . "会计科目
SELECT-OPTIONS:S_BUDAT FOR BKPF-BUDAT OBLIGATORY . "过账日期
PARAMETERS : P_FILE LIKE RLGRAP-FILENAME DEFAULT '/usr/sap/tmp/'. "/gacmotor/sap "AL11中的文件目录都可以导出,需要与basis联系询问具体地址
PARAMETERS : P_NUMB TYPE SY-INDEX DEFAULT '1000000' .
PARAMETERS : P_SERV RADIOBUTTON GROUP R1 DEFAULT 'X'.
PARAMETERS : P_LOCA RADIOBUTTON GROUP R1 .
SELECTION-SCREEN END OF BLOCK A1.
SELECTION-SCREEN FUNCTION KEY 1. "
AT SELECTION-SCREEN ON VALUE-REQUEST FOR P_FILE.
PERFORM FRM_F4_P_FILE .
*************************************************************************
** INITIALIZATION
*************************************************************************
"该事件在屏幕未显示之前执行,对程序设置值及屏幕元素进行初始化赋值。
INITIALIZATION.
STBL-ROW = 'X'.
STBL-COL = 'X'.
SSCRFIELDS-FUNCTXT_01 = '维护审计导出字段配置表'.
SSCRFIELDS-UCOMM = 'FC01'.
*************************************************************************
** AT SELECTION SCREEN
*************************************************************************
AT SELECTION-SCREEN.
CASE SSCRFIELDS-UCOMM. "处理按钮命令
WHEN'FC01'.
CALL FUNCTION 'VIEW_MAINTENANCE_CALL'
EXPORTING
ACTION = 'U'
VIEW_NAME = 'ZTFI252'.
WHEN OTHERS.
ENDCASE.
*************************************************************************
** AT SELECTION SCREEN OUTPUT
*************************************************************************
AT SELECTION-SCREEN OUTPUT.
*************************************************************************
** EVENT TOP OF PAGE
*************************************************************************
TOP-OF-PAGE.
*************************************************************************
* EVENT START OF SELECTION
*************************************************************************
START-OF-SELECTION.
PERFORM GET_DATA .
*************************************************************************
**EVENT END-OF SELECTION
*************************************************************************
END-OF-SELECTION.
*************************************************************************
**EVENT END-OF PAGE
*************************************************************************
END-OF-PAGE.
FORM GET_DATA .
DATA LV_SELET TYPE STRING . ""select 哪些字段
DATA LV_SELEB TYPE STRING . ""select 哪些字段
DATA LV_SELEA TYPE STRING . ""select 哪些字段
DATA LV_LENGE TYPE I .
DATA LV_STR TYPE STRING . "用于字段转换位string类型
DATA LT_BKPF TYPE TABLE OF BKPF .
DATA LT_BSEG TYPE TABLE OF BSEG .
DATA LS_BKPF TYPE BKPF .
DATA LS_BSEG TYPE BSEG .
DATA LINE TYPE STRING.""行数据
DATA LINE1 TYPE STRING.""第一行表头
DATA LINES TYPE STRING.""表数据
DATA LD_XSTRING TYPE XSTRING.
DATA LD_STRING TYPE STRING.
DATA WF_MSG TYPE STRING.
TYPES: BEGIN OF ty_fieldname,
name TYPE char20,
END OF ty_fieldname.
DATA: lt_fieldname TYPE TABLE OF ty_fieldname WITH HEADER LINE.
FIELD-SYMBOLS: <lt_table> TYPE table.
"取配置表数据
SELECT * FROM ZTFI252 INTO TABLE @DATA(LT_252) .
IF LT_252 IS INITIAL.
MESSAGE '请先维护表ZTFI252' TYPE 'S' DISPLAY LIKE 'E' .
ENDIF .
CHECK LT_252 IS NOT INITIAL .
"排序,删除重复维护的字段,活动取值不能有相同的字段
SORT LT_252 BY REF_FIELD REF_TABLE XH .
DELETE ADJACENT DUPLICATES FROM LT_252 COMPARING REF_FIELD .
SORT LT_252 BY XH REF_TABLE REF_FIELD . "删除后按照顺序拍好
CLEAR : GT_FIELD .
LOOP AT LT_252 INTO DATA(LS_252).
"构造导出至服务器的动态内表的表字段
CLEAR :GS_FIELD .
GS_FIELD-FIELDNAME = LS_252-REF_FIELD .
GS_FIELD-REF_TABLE = LS_252-REF_TABLE .
GS_FIELD-REF_FIELD = LS_252-REF_FIELD .
APPEND GS_FIELD TO GT_FIELD .
IF P_LOCA = 'X' .
"构造导出至本地的动态内表的表字段 暂时用长度128
CLEAR :GS_FIELD .
GS_FIELD-FIELDNAME = LS_252-REF_FIELD .
GS_FIELD-INTTYPE = 'C' .
GS_FIELD-INTLEN = '128' .
APPEND GS_FIELD TO GT_FIELD2 .
ENDIF.
"构造动态取值的select字段
IF LV_SELEA IS INITIAL.
LV_SELEA = LV_SELEA && LS_252-REF_TABLE && '~' && LS_252-REF_FIELD .
ELSE .
LV_SELEA = LV_SELEA && ',' && LS_252-REF_TABLE && '~' && LS_252-REF_FIELD .
ENDIF.
ENDLOOP .
"调用方法生成动态内表存储取出的数据
CALL METHOD CL_ALV_TABLE_CREATE=>CREATE_DYNAMIC_TABLE
EXPORTING
IT_FIELDCATALOG = GT_FIELD
IMPORTING
EP_TABLE = DY_TABLE.
ASSIGN DY_TABLE->* TO <ITAB>. "将指针分配给动态内表
CREATE DATA DY_LINE LIKE LINE OF <ITAB>.
ASSIGN DY_LINE->* TO <WA>. "将指针分配给动态内表行
IF P_LOCA = 'X'.
CALL METHOD CL_ALV_TABLE_CREATE=>CREATE_DYNAMIC_TABLE
EXPORTING
IT_FIELDCATALOG = GT_FIELD2
IMPORTING
EP_TABLE = DY_TABLE2.
ASSIGN DY_TABLE2->* TO <ITAB2>. "将指针分配给动态内表
CREATE DATA DY_LINE LIKE LINE OF <ITAB2>.
ASSIGN DY_LINE->* TO <WA2>. "将指针分配给动态内表行
ENDIF.
"动态取出数据
IF LV_SELEA IS NOT INITIAL.
SELECT (LV_SELEA) FROM BKPF
INNER JOIN BSEG ON BKPF~BUKRS = BSEG~BUKRS AND BKPF~BELNR = BSEG~BELNR AND BKPF~GJAHR = BSEG~GJAHR"
WHERE BKPF~BUKRS IN @S_BUKRS
AND BKPF~BLART IN @S_BLART
AND BKPF~BELNR IN @S_BELNR
AND BSEG~HKONT IN @S_HKONT
AND BKPF~BUDAT IN @S_BUDAT
INTO TABLE @<ITAB> .
ENDIF.
IF <ITAB> IS INITIAL.
MESSAGE '未找到数据' TYPE 'S' DISPLAY LIKE 'E' .
ENDIF.
CHECK <ITAB> IS NOT INITIAL .
DATA: LV_FILE TYPE RLGRAP-FILENAME.
DATA: LV_TIME TYPE C .
DATA: p_filename TYPE STRING .
DATA: LV_ASSGIN TYPE C LENGTH 30 .
DATA: LV_INDEX TYPE SY-INDEX .
SELECT * FROM ZTFI252 INTO TABLE @DATA(LT_252B) .
SORT LT_252B BY XH .
DELETE ADJACENT DUPLICATES FROM LT_252B COMPARING XH .
""取表头描述
SELECT TABNAME, FIELDNAME, DDTEXT ,XH FROM DD03M AS A
INNER JOIN @LT_252B AS B ON TABNAME = B~REF_TABLE AND FIELDNAME = B~REF_FIELD AND DDLANGUAGE = '1'
INTO TABLE @DATA(LT_03M) .
SORT LT_03M BY XH .
DELETE ADJACENT DUPLICATES FROM LT_03M COMPARING XH .
LOOP AT LT_03M INTO DATA(LS_03M).
IF P_SERV = 'X' . "构造导出至服务器数据表头
CONCATENATE LINE1 LS_03M-DDTEXT ',' INTO LINE1 . "每个数据后面加个逗号,excel就会换单元格
ELSEIF P_LOCA = 'X' ."构造导出至本地数据表头
CONCATENATE '<WA2>-' LS_03M-FIELDNAME INTO LV_ASSGIN .
ASSIGN (LV_ASSGIN) TO FIELD-SYMBOL(<FS_VALUE>) .
IF <FS_VALUE> IS ASSIGNED .
<FS_VALUE> = LS_03M-DDTEXT .
ENDIF.
UNASSIGN <FS_VALUE> .
lt_fieldname-name = LS_03M-DDTEXT. "EXCEL表头 LOOP p_table的组件,并依次将字段中文描述赋值给lt_fieldname
APPEND lt_fieldname.
ENDIF.
ENDLOOP.
" APPEND <WA2> TO <ITAB2> .""函数自带表头传入参数lt_fieldname就会有表头
DO . ""
LV_TIME = SY-INDEX .
CONCATENATE P_FILE S_BUDAT-LOW '-' S_BUDAT-HIGH '-' SY-UZEIT LV_TIME '.CSV' INTO LV_FILE. "服务器保存的目录和文件名
CONCATENATE LINES LINE1 INTO LINES . "将表头添加进表
CLEAR : LS_BKPF ,LS_BSEG .
DO P_NUMB TIMES. "按照凭证上的次数生成文件
UNASSIGN <WA> .
LV_INDEX = LV_INDEX 1 .
READ TABLE <ITAB> ASSIGNING <WA> INDEX LV_INDEX .
IF <WA> IS ASSIGNED .
IF P_SERV = 'X'. "构造导出到服务器的数据
LOOP AT LT_252B INTO LS_252.
CONCATENATE '<WA>-' LS_252-REF_FIELD INTO LV_ASSGIN .
ASSIGN (LV_ASSGIN) TO <FS_VALUE> .
IF <FS_VALUE> IS ASSIGNED.
LV_STR = <FS_VALUE> . "可能有金额字段,需要转化位字符串
CONCATENATE LINE LV_STR ',' INTO LINE . "每个数据后面加个逗号,excel就会换单元格
ENDIF.
ENDLOOP.
UNASSIGN <WA> .
CONCATENATE LINES CL_ABAP_CHAR_UTILITIES=>CR_LF(1) LINE INTO LINES. "将换行符和这行数据加入表数据中
CLEAR LINE .
ELSEIF P_LOCA = 'X'.
MOVE-CORRESPONDING <WA> TO <WA2> .
APPEND <WA2> TO <ITAB2> .
UNASSIGN: <WA> .
CLEAR :<WA2> .
ENDIF.
ELSE .
EXIT .
ENDIF.
ENDDO.
"每循环一次导出一次数据
IF P_SERV = 'X'.
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
TEXT = LINES
ENCODING = '8404'
IMPORTING
BUFFER = LD_XSTRING
EXCEPTIONS
FAILED = 1
OTHERS = 2.
" DATA(utf8) = cl_abap_codepage=>convert_to( LINES ). "如果要使用AL11直接下载后台跑出的文件到本地用这个方法转码,而不是上面那个函数
OPEN DATASET LV_FILE FOR OUTPUT IN BINARY MODE ."TEXT MODE encoding UTF-8
" OPEN DATASET LV_FILE FOR OUTPUT IN TEXT MODE ENCODING UTF-8 WITH BYTE-ORDER MARK."TEXT MODE encoding UTF-8
IF SY-SUBRC = 0.
TRANSFER LD_XSTRING TO LV_FILE.
" TRANSFER LD_XSTRING TO LV_FILE.
CLOSE DATASET LV_FILE.
ENDIF .
CLEAR : LINE,LINES .
ELSEIF P_LOCA = 'X' .
CONCATENATE P_FILE S_BUDAT-LOW '-' S_BUDAT-HIGH '-' SY-UZEIT LV_TIME '.XLS' INTO LV_FILE. "服务器保存的目录和文件名
p_filename = LV_FILE .
CALL FUNCTION 'GUI_DOWNLOAD'
EXPORTING
filename = p_filename
filetype = 'DAT' "ASC格式 1000- 不会显示为 -1000 DBF格式 字符前空格 前导0不会显示
codepage = '8404' "四位字符集代码 可通过表TCP00A,查询对应字符集代码
TABLES
data_tab = <ITAB2>
fieldnames = lt_fieldname.
.
CLEAR: <ITAB2>.
ENDIF.
IF <WA> IS NOT ASSIGNED .
EXIT .
ENDIF.
ENDDO.
CLEAR : LV_INDEX .
ENDFORM .
FORM FRM_F4_P_FILE .
DATA LV_NAME TYPE C LENGTH 30 .
LV_NAME = SY-DATUM && SY-UZEIT .
CALL FUNCTION 'WS_FILENAME_GET' "用于选择本地文件,获取本地文件的路径
EXPORTING
DEF_FILENAME = LV_NAME "默认的文件名称
* DEF_PATH = ' ' "默认的文件地址
* MASK = ',EXCEL.XLS,*.XLSX,*.XLS,TEXT.TXT,*.TXT.'
MODE = 'O' "S 保存 O 打开
TITLE = '选择文件' "窗口的显示名称
IMPORTING "传出给选择屏幕上的文本框
FILENAME = P_FILE
* RC =
EXCEPTIONS
INV_WINSYS = 1
NO_BATCH = 2
SELECTION_CANCEL = 3
SELECTION_ERROR = 4
OTHERS = 5.
IF SY-SUBRC <> 0.
* Implement suitable error handling here
ENDIF.
ENDFORM .
总结:这个程序导出速度很快,主要有以下原因
1.没有多余的取值字段,都是按照用户维护,活动内表,活动取值,不浪费算力
2.可以服务器后台处理,比本机快
3.直接sap底层转码,构造文件,速度很快
扩展:如果想批导出其他的表而不是bkpf及bseg,可以看着修改选择屏幕以及动态取出表数据的那段select就行了