获取每个 NAST table 消息类型的最新合作伙伴记录?

Get the newest partner record for each NAST table message type?

问题是一般性的(编辑后不再是...),因为我知道这也是其他 table 的常见问题,但我将描述我选择的特定问题输出消息的合作伙伴。

对于给定的发票,我想让合作伙伴链接到 NAST table 中的每个消息类型。同一消息类型可能有多个条目,因此我想要基于字段 ERDATERUHR(日期和时间)的最新条目。

我尝试用子查询来做,但它变得非常难看,尤其是时间字段需要双重子查询,因为你首先需要获取最新的日期...

然后我实现了这个解决方案,但我不喜欢它,我希望有更好的东西

DATA: lt_msg_type_rg TYPE RANGE OF kschl.

lt_msg_type_rg = VALUE #( FOR ls_msg_type IN me->mt_message_type 
                          ( sign = 'I' option = 'EQ' low = ls_msg_type-kschl ) ).
SELECT FROM nast AS invoice_msg_status
      FIELDS invoice_msg_status~kschl AS message_type,
             invoice_msg_status~parnr AS partner_num,
             CONCAT( invoice_msg_status~erdat, invoice_msg_status~eruhr ) AS create_timestamp
      WHERE invoice_msg_status~kappl  = @c_app_invoicing
        AND invoice_msg_status~objky  = @me->m_invoice_num
        AND invoice_msg_status~kschl IN @lt_msg_type_rg
      ORDER BY create_timestamp DESCENDING
      INTO TABLE @DATA(lt_msg_partner).

DATA: lt_partner_rg TYPE RANGE OF parnr.

LOOP AT lt_msg_partner ASSIGNING FIELD-SYMBOL(<lgr_msg_partner>) GROUP BY <lgr_msg_partner>-message_type.
  lt_partner_rg = COND #( WHEN line_exists( lt_partner_rg[ low = <lgr_msg_partner>-partner_num ] )
                          THEN lt_partner_rg
                          ELSE VALUE #( BASE lt_partner_rg ( sign = 'I' option = 'EQ' low = <lgr_msg_partner>-partner_num ) ) ).
ENDLOOP.

示例输入(跳过不相关的字段)

+-------+-------+-------+-------+------------+-------+
| KAPPL | OBJKY | KSCHL | PARNR |   ERDAT    | ERUHR |
+-------+-------+-------+-------+------------+-------+
| V3    | 12345 | Z001  |    11 | 27.10.2020 | 11:00 |
| V3    | 12345 | Z001  |    12 | 27.10.2020 | 12:00 |
| V3    | 12345 | Z002  |    13 | 27.10.2020 | 11:00 |
+-------+-------+-------+-------+------------+-------+

预期输出:

[12]
[13]

不幸的是,SQL 没有为这种相当常见的选择提供简单的语法。解决方案将始终涉及多个后续或嵌套选择。

根据你的描述,我假设你已经找到了 do-it-all-in-a-single-deeply-nested ABAP SQL 语句,但是你对它不满意,因为可读性太差了。

对于这种情况,我们经常诉诸 ABAP-Managed 数据库过程 (AMDP)。它们允许将复杂的嵌套选择分解为一系列简单的后续选择。

CLASS cl_read_nast DEFINITION
    PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.

    INTERFACES if_amdp_marker_hdb.

    TYPES:
      BEGIN OF result_row_type,
        parnr TYPE char2,
      END OF result_row_type.

    TYPES result_table_type
      TYPE STANDARD TABLE OF result_row_type
        WITH EMPTY KEY.

    TYPES:
      BEGIN OF key_range_row_type,
        kschl TYPE char4,
      END OF key_range_row_type.

    TYPES key_range_table_type
      TYPE STANDARD TABLE OF key_range_row_type
        WITH EMPTY KEY.

    CLASS-METHODS select
      IMPORTING
        VALUE(application)  TYPE char2
        VALUE(invoice_number)  TYPE char5
        VALUE(message_types)  TYPE key_range_table_type
      EXPORTING
        VALUE(result) TYPE result_table_type.

ENDCLASS.

CLASS cl_read_nast IMPLEMENTATION.

  METHOD select
      BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT
      USING nast.

    last_changed_dates =
      select kappl, objky, kschl,
          max( erdat || eruhr ) as last_changed_on
        from nast
        where kappl = :application
          and objky = :invoice_number
          and kschl in
            ( select kschl from :message_types )
        group by kappl, objky, kschl;

    last_changers =
      select nast.kschl,
          max( nast.parnr ) as parnr
        from nast
        inner join :last_changed_dates
          on nast.kappl = :last_changed_dates.kappl
          and nast.objky = :last_changed_dates.objky
          and nast.kschl = :last_changed_dates.kschl
          and nast.erdat || nast.eruhr = :last_changed_dates.last_changed_on
        group by nast.kschl;

    result =
      select distinct parnr
        from :last_changers;

  ENDMETHOD.

ENDCLASS.

已通过以下集成测试验证:

CLASS integration_tests DEFINITION
    FOR TESTING RISK LEVEL CRITICAL DURATION SHORT.

  PRIVATE SECTION.

    TYPES db_table_type
      TYPE STANDARD TABLE OF nast
        WITH EMPTY KEY.

    CLASS-METHODS class_setup.

    METHODS select FOR TESTING.

ENDCLASS.

CLASS integration_tests IMPLEMENTATION.

  METHOD class_setup.

    DATA(sample) =
      VALUE db_table_type(
        ( kappl = 'V3' objky = '12345' kschl = 'Z001' parnr = '11' erdat = '20201027' eruhr = '1100' )
        ( kappl = 'V3' objky = '12345' kschl = 'Z001' parnr = '12' erdat = '20201027' eruhr = '1200' )
        ( kappl = 'V3' objky = '12345' kschl = 'Z002' parnr = '13' erdat = '20201027' eruhr = '1100' ) ).

    MODIFY nast
      FROM TABLE @sample.

    COMMIT WORK AND WAIT.

  ENDMETHOD.

  METHOD select.

    DATA(invoicing) = 'V3'.

    DATA(invoice_number) = '12345'.

    DATA(message_types) =
      VALUE zcl_fh_read_nast=>key_range_table_type(
        ( kschl = 'Z001' )
        ( kschl = 'Z002' ) ).

    cl_read_nast=>select(
      EXPORTING
        application = invoicing
        invoice_number = invoice_number
        message_types = message_types
      IMPORTING
        result = DATA(actual_result) ).

    DATA(expected_result) =
      VALUE cl_read_nast=>result_table_type(
        ( parnr = '12' )
        ( parnr = '13' ) ).

    cl_abap_unit_assert=>assert_equals(
        act = actual_result
        exp = expected_result ).

  ENDMETHOD.

ENDCLASS.

首先,您的文章不正确,因为您仅通过合作伙伴编号检查存在(重复数据删除),并且可能同一合作伙伴可以提供不同的消息类型,至少在我的测试系统上的数据集中我看到了这样的行。因此,您还应该按消息类型进行检查。按消息类型对循环进行分组并按合作伙伴编号进行重复数据删除是没有意义的,因为您正在剥离以不同类型出现的有效合作伙伴。您需要:

SELECT 
....
ORDER BY message_type, create_timestamp DESCENDING
....

因此您的 LOOP 分组可以简化为这两行:

DELETE ADJACENT DUPLICATES FROM lt_msg_partner COMPARING message_type.
lt_partner_rg = VALUE #( BASE lt_partner_rg FOR GROUPS value_no OF <line_no> IN lt_msg_partner GROUP BY ( partner_num = <line_no>-partner_num ) WITHOUT MEMBERS ( sign = 'I' option = 'EQ' low = value_no-partner_num ) ).

正如 中所建议的那样,这也可以通过 CDS 视图来完成。

首先,我们需要一个为数据添加时间戳的视图:

@AbapCatalog.sqlViewName: 'timednast'
define view timestamped_nast as select from nast {
    kappl,
    objky,
    kschl,
    parnr,
    concat(erdat, eruhr) as timestamp
}

其次,因为 CDS 的语法不允许在单个视图中添加时间戳和分组,我们需要另一个视图来计算每种消息类型的最新更改日期:

@AbapCatalog.sqlViewName: 'lchgnast'
define view last_changed_nast as
select from timestamped_nast {
    kappl,
    objky,
    kschl,
    max(timestamp) as last_changed_on
} group by kappl, objky, kschl

第三,我们需要select与这些时间点关联的合作伙伴编号:

@AbapCatalog.sqlViewName: 'lchbnast'
define view last_changers_nast as
  select from last_changed_nast
  inner join timestamped_nast 
    on timestamped_nast.kappl = last_changed_nast.kappl
    and timestamped_nast.objky = last_changed_nast.objky
    and timestamped_nast.kschl = last_changed_nast.kschl
    and timestamped_nast.timestamp = last_changed_nast.last_changed_on
{
    timestamped_nast.kappl,
    timestamped_nast.objky,
    timestamped_nast.kschl,
    parnr
}

最后一个视图 last_changers_nast 上的 SELECT,包括 kapplobjkykschl 上的 selection 标准将然后生成最新更改者列表。

我不确定 nast table 的键。第三种观点假设一个对象不会有两个具有完全相同时间戳的条目。如果这不是真的,第三个视图应该使用 max(parnr) 而不是 parnr

添加另一个聚合