问题描述
这个问题是通用的(编辑后不再可用...),因为我知道这对于其他表也是一个常见问题,但是我将通过选择输出消息的合作伙伴来描述我的特定问题。
对于给定的发票,我想使合作伙伴链接到NAST
表中的每种消息类型。相同的消息类型可能有多个条目,所以我想要基于字段ERDAT
和ERUHR
(日期和时间)的最新条目。
我尝试对子查询进行操作,但是它变得非常难看,尤其是时间字段需要双子查询,因为您首先需要获取最新日期...
然后我实现了此解决方案,但我不喜欢它,我希望有更好的东西
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-ested ABAP SQL语句,但是您对它不满意,因为可读性受到很大影响。
对于这种情况,我们经常求助于ABAP管理的数据库过程(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,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 ) ).
,
根据comments to the AMDP-variant answer中的建议,这也可以使用CDS视图来完成。
首先,我们需要一个为数据加上时间戳的视图:
@AbapCatalog.sqlViewName: 'timednast'
define view timestamped_nast as select from nast {
kappl,parnr,concat(erdat,eruhr) as timestamp
}
第二,因为CDS的语法不允许在单个视图中进行时间戳记和分组,所以我们需要另一个视图来计算每种消息类型的最新更改日期:
@AbapCatalog.sqlViewName: 'lchgnast'
define view last_changed_nast as
select from timestamped_nast {
kappl,max(timestamp) as last_changed_on
} group by kappl,kschl
第三,我们需要选择与以下时间点关联的合作伙伴编号:
@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,包括kappl
,objky
和kschl
上的选择条件,将产生最新更改器的列表。
我不确定nast
表的键。第三个视图假定对于一个对象,将不存在两个时间戳完全相同的条目。如果不正确,则第三个视图应使用max(parnr)
而不是parnr