为了应对审计导出大量凭证的要求,写了此程序来导出bkpf及bseg表数据,此程序分两种导出方式,导出至本地及导出至服务器,因为数据量巨大,所以也支持后台导出至服务器(导出至本地不支持后台执行,sap服务器没办法把文件写到本地)。
经过测试导出到服务器的速度目前是,100万张凭证生成一个100兆的文件导出时间1分钟左右,但是这种方法有个弊端就是无法控制单元格格式(如果有大佬知道,欢迎留言)只能控制单元格写入的内容
程序设计的思路:
1.创建个自建表,维护两个表中需要导出的字段清单
2.取自建表清单数据,构造动态内表
3.构造动态取表字段数据
4.将动态数据存储至动态内表
5.根据自建表取表字段的描述,构造导出结构的表头
6.循环取到数据的动态内表,构造导出文件
7.导出至本地或者服务器
具体代码如下
*&---------------------------------------------------------------------*
*& Report ZFIR252
*&---------------------------------------------------------------------*
*& author 李强 Qunending 2022.01.04 18273737253 https://blog.csdn.net/Qunending?spm=1019.2139.3001.5343
*&---------------------------------------------------------------------*
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'EXPORTINGACTION = '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' ."构造导出至本地的动态内表的表字段 暂时用长度128CLEAR :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_TABLEEXPORTINGIT_FIELDCATALOG = GT_FIELDIMPORTINGEP_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_TABLEEXPORTINGIT_FIELDCATALOG = GT_FIELD2IMPORTINGEP_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 BKPFINNER JOIN BSEG ON BKPF~BUKRS = BSEG~BUKRS AND BKPF~BELNR = BSEG~BELNR AND BKPF~GJAHR = BSEG~GJAHR"WHERE BKPF~BUKRS IN @S_BUKRSAND BKPF~BLART IN @S_BLARTAND BKPF~BELNR IN @S_BELNRAND BSEG~HKONT IN @S_HKONTAND BKPF~BUDAT IN @S_BUDATINTO 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 AINNER 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_fieldnameAPPEND 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'EXPORTINGTEXT = LINESENCODING = '8404'IMPORTINGBUFFER = LD_XSTRINGEXCEPTIONSFAILED = 1OTHERS = 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-8IF 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'EXPORTINGfilename = p_filenamefiletype = 'DAT' "ASC格式 1000- 不会显示为 -1000 DBF格式 字符前空格 前导0不会显示codepage = '8404' "四位字符集代码 可通过表TCP00A,查询对应字符集代码TABLESdata_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' "用于选择本地文件,获取本地文件的路径EXPORTINGDEF_FILENAME = LV_NAME "默认的文件名称
* DEF_PATH = ' ' "默认的文件地址
* MASK = ',EXCEL.XLS,*.XLSX,*.XLS,TEXT.TXT,*.TXT.'MODE = 'O' "S 保存 O 打开TITLE = '选择文件' "窗口的显示名称IMPORTING "传出给选择屏幕上的文本框FILENAME = P_FILE
* RC =EXCEPTIONSINV_WINSYS = 1NO_BATCH = 2SELECTION_CANCEL = 3SELECTION_ERROR = 4OTHERS = 5.IF SY-SUBRC <> 0.
* Implement suitable error handling hereENDIF.
ENDFORM .
自建表
自建表数据附件excel,附件中是bkpf及 bseg两个表所有的字段,SE16N编辑进表后删除字段中带.的字段就可以了
选择屏幕文本
总结:这个程序导出速度很快,主要有以下原因
1.没有多余的取值字段,都是按照用户维护,活动内表,活动取值,不浪费算力
2.可以服务器后台处理,比本机快
3.直接sap底层转码,构造文件,速度很快
扩展:如果想批导出其他的表而不是bkpf及bseg,可以看着修改选择屏幕以及动态取出表数据的那段select就行了