SAP 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方式连接外部数据库的验证失败. 不推荐使用. 其它三种方式根据实际情况选择使用就好.

示例代码详见文末.

示例代码,

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

sql 复制代码
*&---------------------------------------------------------------------*
*& 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.
sql 复制代码
*&---------------------------------------------------------------------*
*& 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: <fs_itab> TYPE STANDARD TABLE.
  ASSIGN ex_result_ref->* TO <fs_itab>.
  MOVE-CORRESPONDING <fs_itab> 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.
相关推荐
山茶花开时。9 天前
[SAP ABAP] 序列化与反序列化
开发语言·sap·abap
左甜甜10 天前
SAP MMPV报错MMPV_DATE_CHECK
sap
SAP女战士12 天前
【ABAP SAP】开发-BUG修补记录_采购申请打印时品名规格品牌为空
运维·数据库·bug·sap·abap开发
Cindy的SAP加油站15 天前
SAP Ariba_Order Fulfillment Status
sap·sap ariba
SAP学习成长之路15 天前
ADBC 查询语法介绍:EXECUTE_QUERY
java·服务器·开发语言·数据库·sap·abap·零售
需己以听的以听17 天前
4.模块化技术之子程序
sap·gui·abap·模块化·改行学it·子程序
syounger17 天前
近四分之一SAP ECC用户对未来规划尚不明朗
sap
SAP学习成长之路22 天前
SAP HANA 上进行 ABAP 开发:简介
开发语言·数据结构·数据库·sap·abap·代码规范
阿达_优阅达1 个月前
利用 Xtract Universal,将 SAP 数据高效迁移至 Amazon S3 或 Redshift
数据库·sap·企业数字化转型·theobald sap·redshift
左甜甜1 个月前
ABAP OOALV模板
sap·abap·alv