ABAP基础知识 访问外部数据库-开发篇

2021-10-28 22:27发布


          点击此处--->   EasySAP.com 群内免费提供SAP练习系统(在群公告中)

加入QQ群:457200227(SAP S4 HANA技术交流) 群内免费提供SAP练习系统(在群公告中)

点击蓝字 关注我们

前言

前文讲解了怎么配置连接外部数据库

详见链接
无峰,公众号:ABAP 技巧与实战ABAP基础知识 访问外部数据库-配置篇


本文主要介绍通过ABAP语言访问外部数据库的几种方式



外部数据库配置

本文示例中的代码访问了两个外部数据库

MTD : 外部oracle数据库,其中示例表 ZTTEMP 字段( ZZTNO,WERKS)

S4Q : 外部HANA数据库(开发系统访问测试系统的数据库), 使用表USR02,ZTTEMP


ABAP访问外部数据库

通过ABAP访问外部数据库有四种方式.根据不同的情况,可以选择不同的方法.

  • OPEN SQL访问

  • NATIVE SQL 访问

  • ADBC(ABAP Database Connectivity)

  • AMDP ABAP Managed Database Procedures ? (未验证通过)


OPEN SQL直接访问

OPEN SQL 访问的限制条件:必须在ABAP数据字典中存在该表名,并且最好同目标系统表结构一致, 一般情况下,用来访问另外一个同版本的ECC数据库.当然,也可以把ECC的表定义语句在目标系统中创建一个同名同结构的表,然后用该方式访问.

直接访问时,在FROM TABLE 后面添加 CONNECTION s4q .

s4q是DBCO中建立的和另外一个S/4系统的连接


01

报错及处理一


可能的报错及处理方式

下图报错的原因是访问ORACLE数据库必须指定一个SCHEMA. 这个可以配置在连接参数中.



01

报错及处理二


出现下面的报错,表示系统尝试使用MANDT限制数据, 此时需要给OPEN SQL 语句添加CLIENT SPECIFIED 强制OPEN SQL 不要补充MANDT限制


NATIVE SQL访问

通过NATIVE SQL 访问外部数据库步骤

  • 打开连接

  • 执行SQL命令

  • 关闭连接

示例代码见文末


01

读取多条记录的方式


  • 游标方式     图一

  • 非游标方式 图二

非游标方式其实隐式使用了游标.性能比游标方式要差.数据量小的时候看不出来. 大量数据读取就能看出二者的性能差异了. 

图三是标准帮助中提示的优劣比较.

图一

图二

图三


ADBC访问

ADBC(ABAP Database Connectivity) 是SAP提供的原生SQL(Native SQL)接口API.可以通过ADBC执行任何数据库的原生SQL语句.

ABAP中有个标准的DEMO程序: ADBC_DEMO 演示了各种SQL语句的调用方式.图四的代码示例给出了SELECT语句的常用写法.

大概需要如下的过程

  • 创建默认数据库的链接对象

  • 创建一个查询对象

  • 基于sql语句创建一个结果对象

  • 定义传递结果集一个数据对象-内表

  • 获取数据内容

  • 关闭连接

  • 赋值数据到内表

示例代码详见文末


通过AMDP 访问?

AMDP ABAP Managed Database Procedures

标准ABAP帮助体系中提到访问外部数据库的方法中还有一种AMDP方式.为了完善本文,补充了一个AMDP访问外部数据库表的示例.(验证未通过)

尝试中发现AMDP只适用与HANA数据库. 并且尝试通过DEMO程序 DEMO_AMDP_CONNECTION 连接外部数据库S4Q. 尝试失败. (报错见图四)

感觉AMDP 并不支持连接外部数据库. 

图五中示例代码的连接 是 R/3*开头. 帮助中说S/3*是SERVICE CONNECT. 但是不理解有什么用处.


图四

图五

图六


总结

ABAP访问外部数据库的几种方式中. OPEN SQL 最简单,但是有很大局限性. NATIVE方式最简单易懂. 性能也最好. 但是不利于代码动态化. ADBC 可以动态的实现数据的读取及内表的赋值.

前文DB02 SQL编辑器SQL语句自动生成报表 就采用了ADBC访问数据库的方法: 根据语句动态定义选择屏幕,动态定义内表, 读取的数据写入内表呈现.

详见链接
无峰,公众号:ABAP开发技巧SAP工具箱之一键生成报表

该工具在付费文章中可以获取.


文中通过AMDP方式连接外部数据库的验证失败. 不推荐使用. 其它三种方式根据实际情况选择使用就好.

示例代码详见文末.

THE

END

约定

如果你对这篇文章感兴趣,请帮忙点赞,在看,分享.       

    (如果你真的喜欢这篇文章,请记得回来打个赏,作为支持我继续下去的动力,这是一个正反馈过程. 越多的人打赏,作者越有动力分享,读者就能享受更多的福利.毕竟打赏的金额富不了我,穷不了你,却能支持这个公众号长久发文.)




公众号 : syjf1976_abap

          ABAP开发技巧

微信号 : 392077


请微信联系管理员: 

syjf1976 

sharry_xlp  

Yannick_Duan 申请进入公众号讨论群


示例代码,OPEN SQL:

*&---------------------------------------------------------------------**& Report ZTS_SQL_DBCO*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zts_dbco_opensql.
PARAMETERS: p_s4 RADIOBUTTON GROUP ra1, p_ora RADIOBUTTON GROUP ra1.
START-OF-SELECTION. CASE 'X'. WHEN p_s4.*访问另一个S4系统的同名表*需要注意的是,目标系统CLIENT和当前CLIENT 很可能不一样, 所以需要加上CLIENT SPECIFIED 避免CLIENT不同干扰数据获取 SELECT * FROM usr02 CLIENT SPECIFIED INTO TABLE @DATA(lt_usr02) BYPASSING BUFFER CONNECTION R/3*S4Q WHERE bname = '00177'. ."可以获取数据 DATA(lv_subrc) = sy-subrc . cl_demo_output=>write( lv_subrc ). cl_demo_output=>write( lt_usr02 ). cl_demo_output=>display( ). WHEN p_ora.*访问另一个其它系统的同名表*如果ABAP表有MANDT , 目标表没有, 则需要添加CLIENT SPECIFIED 避免系统自动添加MANDT 的限制条件,导致报错:字段MANDT不存在 DATA: BEGIN OF ls_temp, zztno(30), werks(4), END OF ls_temp. DATA: lt_temp LIKE TABLE OF ls_temp.
SELECT zztno,werks FROM zttemp CLIENT SPECIFIED CONNECTION mtd INTO TABLE @lt_temp.
cl_demo_output=>display( lt_temp ). ENDCASE.* COMMIT CONNECTION s4q. "在连接中提交.

示例代码 NATIVESQL

*&---------------------------------------------------------------------**& Report ZTS_SQL_DBCO*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zts_dbco_nativesql.
PARAMETERS: p_1 RADIOBUTTON GROUP ra1, p_2 RADIOBUTTON GROUP ra1, p_3 RADIOBUTTON GROUP ra1, p_4 RADIOBUTTON GROUP ra1.
DATA: BEGIN OF gs_temp, zztno(30), werks(4), END OF gs_temp.DATA: gt_temp LIKE TABLE OF gs_temp.DATA conn TYPE dbcon-con_name.
INITIALIZATION. %_p_1_%_app_%-text = 'SELECT方式一:DO循环读取游标,添加内表'. %_p_2_%_app_%-text = 'SELECT方式二:通过例程添加内表'. %_p_3_%_app_%-text = 'UPDATE:更新表内容'. %_p_4_%_app_%-text = 'INSERT:写入表内容'.
START-OF-SELECTION. conn = 'MTD'. "检查连接是否已经打开 EXEC SQL. SET CONNECTION :conn ENDEXEC. IF sy-subrc <> 0. "如果连接没有打开, 打开连接 EXEC SQL. CONNECT TO :conn ENDEXEC. ENDIF.
*两种方式: 方式一性能好于方式二 CASE 'X'. WHEN p_1. PERFORM frm_method_1. "SELECT方式一:DO循环读取游标,添加内表'. WHEN p_2. PERFORM frm_method_2. "SELECT方式二:通过例程添加内表'. when p_3. perform frm_update. when p_4. perform frm_insert. ENDCASE.
"关闭数据库连接 EXEC SQL. DISCONNECT :CONN ENDEXEC.
*输出结果 CASE 'X'. WHEN p_1 or p_2. cl_demo_output=>display( gt_temp ). ENDCASE.*&---------------------------------------------------------------------**& Form FRM_METHOD_1*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_method_1 . DATA: ls_temp LIKE gs_temp, lt_temp LIKE TABLE OF ls_temp. "执行SQL语句:通过open dbcur打开游标 EXEC SQL. OPEN dbcur FOR SELECT zztno,werks FROM zttemp ENDEXEC. "循环通过游标读取记录 " 两种赋值方式: " 1.按字段顺序赋值,select 字段与 INTO 字段顺序必须一致 " FETCH NEXT dbcur INTO :ls_TEMP-ZZTNO,:LS_TEMP-WERKS " 2.按结构整体赋值:select 字段必须与结构字段顺序一致,且字段长度一致. " FETCH NEXT dbcur INTO :ls_TEMP DO. EXEC SQL. FETCH NEXT dbcur INTO :ls_TEMP-ZZTNO,:LS_TEMP-WERKS ENDEXEC. IF sy-subrc <> 0. EXIT. ELSE. APPEND ls_temp TO lt_temp. ENDIF. ENDDO. "关闭游标 EXEC SQL. CLOSE dbcur ENDEXEC. gt_temp[] = lt_temp[].ENDFORM.*&---------------------------------------------------------------------**& Form FRM_METHOD_2*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_method_2 . conn = 'MTD'. "检查连接是否已经打开 EXEC SQL. SET CONNECTION :conn ENDEXEC. IF sy-subrc <> 0. "如果连接没有打开, 打开连接 EXEC SQL. CONNECT TO :conn ENDEXEC. ENDIF.*注意:工作区 gs_temp 内表 gt_temp 必须是全局变量 EXEC SQL PERFORMING FRM_FILL_DATA. SELECT zztno,werks FROM zttemp INTO :GS_TEMP ENDEXEC.
ENDFORM.FORM frm_fill_data. APPEND gs_temp TO gt_temp.
ENDFORM.*&---------------------------------------------------------------------**& Form FRM_UPDATE*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_update . DATA: lv_werks(4). lv_werks = '1002'. EXEC SQL. UPDATE ZTTEMP SET WERKS = :LV_WERKS WHERE WERKS = '1003' ENDEXEC. IF sy-subrc = 0.*提交数据更新 EXEC SQL. COMMIT WORK ENDEXEC. DATA: lv_msg(50). lv_msg = '更新成功记录数:' && sy-dbcnt . cl_demo_output=>display( lv_msg ). ELSE. cl_demo_output=>display( '更新失败' ). ENDIF.ENDFORM.*&---------------------------------------------------------------------**& Form FRM_INSERT*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_insert . DATA: lv_werks(4). lv_werks = '1002'. EXEC SQL. INSERT INTO ZTTEMP VALUES ('4502',:LV_WERKS) ENDEXEC. IF sy-subrc = 0.*提交数据更新 EXEC SQL. COMMIT WORK ENDEXEC. DATA: lv_msg(50). lv_msg = '写入成功记录数:' && sy-dbcnt . cl_demo_output=>display( lv_msg ). ELSE. cl_demo_output=>display( '写入失败' ). ENDIF.ENDFORM.

示例代码 ADBC

*&---------------------------------------------------------------------**& Report ZTS_DBCO_ADBC*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zts_dbco_adbc.
DATA: BEGIN OF gs_temp, zztno(30), werks(4), END OF gs_temp.DATA: gt_temp LIKE TABLE OF gs_temp.DATA conn TYPE dbcon-con_name.DATA: gv_sql TYPE string.
START-OF-SELECTION. gv_sql = 'SELECT zztno,werks FROM zttemp'. PERFORM frm_get_data_adbc_simple.* PERFORM frm_get_data_adbc. cl_demo_output=>display( gt_temp ).*&---------------------------------------------------------------------**& Form FRM_GET_DATA_ADBC*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_get_data_adbc . DATA: r_adbc_conn TYPE REF TO cl_sql_connection, r_adbc_query TYPE REF TO cl_sql_statement, r_metadata TYPE REF TO data, it_metadata TYPE adbc_rs_metadata_descr_tab, lv_len TYPE i, lv_off TYPE i, wa_metadata LIKE LINE OF it_metadata, r_adbc_result TYPE REF TO cl_sql_result_set, r_tabletype TYPE REF TO cl_abap_tabledescr, r_cxadbc TYPE REF TO cx_dba_adbc, r_cxsql TYPE REF TO cx_sql_exception, tabix_n(4) TYPE n, column_names TYPE HASHED TABLE OF adbc_name WITH UNIQUE KEY table_line. DATA: lv_stmt_type TYPE string. DATA: ex_structdescr TYPE REF TO cl_abap_structdescr, ex_result_ref TYPE REF TO data.*获取sql语句的类型 lv_stmt_type = cl_hdb_sql_executor=>get_statement_type( gv_sql ).*创建默认数据库的链接对象 r_adbc_conn = cl_db6_con=>get_connection( 'MTD' ).*创建一个查询对象 r_adbc_query = r_adbc_conn->create_statement( ).*基于sql语句创建一个结果对象 r_adbc_result = r_adbc_query->execute_query( gv_sql ).*获取结果集合的字段名 it_metadata = r_adbc_result->get_metadata( ).*使用结果集合的字段信息,创建一个数据对象-结构 r_metadata = r_adbc_result->get_struct_ref( md_tab = it_metadata p_strict = abap_false ).*创建一个数据对象-内表 ex_structdescr ?= cl_abap_typedescr=>describe_by_data_ref( r_metadata ). r_tabletype = cl_abap_tabledescr=>create( p_line_type = ex_structdescr p_table_kind = cl_abap_tabledescr=>tablekind_std ).
CREATE DATA ex_result_ref TYPE HANDLE r_tabletype.*传递结果集一个数据对象-内表 r_adbc_result->set_param_table( itab_ref = ex_result_ref ).*获取数据内容 r_adbc_result->next_package( EXPORTING upto = 100 ).*关闭连接 r_adbc_result->close( ).*赋值数据到内表 FIELD-SYMBOLS: TYPE STANDARD TABLE. ASSIGN ex_result_ref->* TO . MOVE-CORRESPONDING TO gt_temp.ENDFORM.
FORM frm_get_data_adbc_simple . DATA: r_adbc_conn TYPE REF TO cl_sql_connection, r_adbc_query TYPE REF TO cl_sql_statement, r_metadata TYPE REF TO data, it_metadata TYPE adbc_rs_metadata_descr_tab, lv_len TYPE i, lv_off TYPE i, wa_metadata LIKE LINE OF it_metadata, r_adbc_result TYPE REF TO cl_sql_result_set, r_tabletype TYPE REF TO cl_abap_tabledescr, r_cxadbc TYPE REF TO cx_dba_adbc, r_cxsql TYPE REF TO cx_sql_exception, tabix_n(4) TYPE n, column_names TYPE HASHED TABLE OF adbc_name WITH UNIQUE KEY table_line. DATA: lv_stmt_type TYPE string. DATA: ex_structdescr TYPE REF TO cl_abap_structdescr, ex_result_ref TYPE REF TO data.
*创建默认数据库的链接对象 r_adbc_conn = cl_db6_con=>get_connection( 'MTD' ).*创建一个查询对象 r_adbc_query = r_adbc_conn->create_statement( ).*基于sql语句创建一个结果对象 r_adbc_result = r_adbc_query->execute_query( gv_sql ).
*定义 DATA: lr_ref LIKE REF TO gt_temp. CREATE DATA lr_ref .*传递结果集一个数据对象-内表 r_adbc_result->set_param_table( itab_ref = lr_ref ).*获取数据内容 r_adbc_result->next_package( EXPORTING upto = 100 ).*关闭连接 r_adbc_result->close( ).*赋值数据到内表
gt_temp = lr_ref->*.ENDFORM.


赞赏支持