2022年9月

 

哪些工具可以用于性能优化?

ST05-性能追踪。包含SQL追踪加RFC,队列和缓存追踪。SQL追踪主要用于测量程序中select语句的性能。

SE30-运行时分析。用于测量应用的性能。

SAT是过时的SE30的替代品。提供了和SE30相同的功能和额外的一些特性。

ST12事务(ST-A/PI软件组件的一部分)是ST05和SAT的结合。这是个非常强大的性能分析工具,由SAP提供支持。

Code Inspectior(SCI)是最好的静态性能分析工具之一。有很多选项可以用于找到通常的错误和可能的性能瓶颈。

优化ABAP代码的步骤

1,数据库

a. 在select语句中使用使用where子句来限制数据检索的体积。很重要!(译注:工作中见到过有人写select * from marc这种语句. 导致在生产系统中直接因为内存不足dump)

b. 设计查询,使其尽可能多地在where中使用索引字段。

c. 在select语句中使用inner(或者某些情况下使用outer)join语句以实现一次性查询得到匹配的数据。

d. 避免使用嵌套的select语句,以及loop中的select语句,使用join或者for all entries in会更好。如果已经有内表可以使用,或者在某些处理结束之后,可以使用for all entries in。如果select后面正好还有选择的话,使用join。

e. 访问缓存时避免使用into corresponding fields of table。相反,应该使用最适合程序的(字段)。

f. 避免使用select * ,应该只查询需要的字段。

g. 不要在select语句中使用order by,如果它和用到的索引不同的话(正确的做法是排序内表)。因为这会增加很多额外的工作。数据库系统只有一个,而ABAP服务器有好多。(译注:不确定这种观点在HANA平台中是否依旧适用。可以参考一篇在SCN上的问答,ABAP7.51版本的文档中已经删除了这个限制相关的描述:ABAP Development : SAP HANA ORDER BY or SORT internal table

h. 索引。在为了改善性能而创建索引前,需要深思熟虑。索引可以提高查询性能,但是也会带来两个间接的负担:存储空间和写入性能。在大事务表中创建索引时,用于存储索引的空间和索引的体积是非常巨大的!当在数据库表中插入一条新的记录的时候,所有索引都需要更新。索引越多,花费的时间也就越多。数据越多,索引也就越大,更新索引所需的时间也就越大。

i. 避免多次运行相同的select(同样的select, 同样的参数)。在你的abap代码中缓存相关信息。

j. 如果有不影响性能的标准的视图,避免使用join语句。

2,表缓存

a. 将表定义为“缓冲过的”(SE11)可以提高性能,但是要小心地使用它。表的缓存会导致程序从缓存中而不是表中读取数据。缓存和表是周期性同步的,只有极少情况下、某些东西改变的时候才会同步。如果是事务表,数据在不同的选择条件下会改变,因此这类表是不适合缓存的。不建议在这种情况下使用缓存。应该为配置表和某些主数据表启用缓存

b. 避免对缓存表使用复杂的查询,因为SAP可能解释不了这个请求,并且也许会把它传递给数据库——code inspector可以说明哪些命令会绕过缓存。

3,内表

a. 尽可能使用哈希表,其次是排序表。标准表是最后的选择。

b. 如果要修改内表的话,对于大工作区,应使用assign而不是loop into

c. 有疑问的时候,运行SE30,以此检查代码

d. 如果不得不用标准表,并且要read读取其中的行的话,使用binary search来提高搜索速度。

4,杂项

a. perform:写子程序的时候,总是提供所有参数的类型。这减少了系统根据形参确定参数类型的开销。这也提高了程序的健壮性。

select single和select ... up to 1 rows的区别是什么?

  • select single和select up to 1 rows返回第一条匹配给定条件的记录。它可能不是唯一的,给定条件有可能匹配多条记录。
  • 对于oracle数据库而言,select single会被转换为select ... up to 1 rows,因此,它们是一样的。只不过ABAP的语法不允许将order by和select single放在一起用,但是允许其和select...up to 1 rows一起用。因此,如果你想获得最高/最低的一条记录,是不可以用select single的,只能用select ... up to 1 rows where ... order by.

join和for all entries in哪个性能好?

绝大多数场景下,inner join比for all entries in要好,应当被首先采用。只有当可能出现性能问题的时候才要用for all entries in,要仔细地测量更换为for all entries in前后的性能变化以验证是否真的有提升。

需要首先在一个测试程序上运行for all entries in并运行sql追踪以观察其效果。某些由BASIS设定的选项可以使for all entries in作为“OR”条件运行。这意味着如果使用for all entries in筛选的表有3条数据

,SQL追踪会显示3个SQL在执行。在这种情况下,使用for all entries in没意义。然而如果SQL追踪显示一条SQL语句,这时for all entries in是有用的,因为它实际上被当作一个in列表来执行。

比起for all entries in,更加推荐使用join。join连接的表的数量并没有实际的限制;不过太复杂的句子会难以维护,如果join里面有什么问题,也比较难以解决。如果join是使用两个表的键来连接的话,会减少程序负担,提高性能。

在某些场景下,你会需要以内表作为条件。此时,你可能没别的选择,只能用for all entries in了。

下面是使用了join的代码:

 

SELECTA~VBELN A~KUNNR A~KUNAG B~NAME1INTO TABLEI_LIKPFROMLIKP AS A
INNER JOIN KNA1 AS B
ON A~KUNNR =B~KUNNR.*For with limited data using for all entries:*Minimize entries in I_likp by deleting duplicate kunnr. LOOP AT I_LIKP INTOW_LIKP.
W_LIKP2
-KUNAG = W_LIKP-KUNAG.APPEND W_LIKP2 TOI_LIKP2.ENDLOOP.SORT I_LIKP2 BYKUNNR.DELETE ADJACENT DUPLICATES FROMI_LIKP2 COMPARING KUNNR.*GET DATA FROM kna1 IF NOT I_LIKP2[] IS INITIAL.SELECTKUNNR NAME1INTO TABLEI_KNA1FROMKNA1FOR ALL ENTRIES INI_LIKP2WHERE KUNNR = I_LIKP2-KUNNR.ENDIF.

 

在ABAP中,存在着一条法则:名字不一定代表实际规则(具体可看最近的相关讨论)。

但是如你们所知的,存在着一个很好的例外: 所有涉及到使用CORRESPONDING为结构赋值的关键字的语法形式(偶然地)有着相同的名字..

  • 在ABAP 7.40之前,主要有用MOVE-CORRESPONDING来复制结构组件、Open SQL的SELECT的CORRESPONDING附加字段,以及某些过时的计算语句等。
  • 在ABAP 7.40中,MOVE-CORRESPONDING可以用于操纵带有结构的内表。并且7.40引入了一个新的构造器操作符CORRESPONDING,它允许显式地将结构的组件映射到不同名字的组件上。

还缺了点什么?答案是动态的映射!这个特性在ABAP 7.50中得到了引入。

新的系统类  CL_ABAP_CORRESPONDING允许你适用动态指定的映射规则为结构或内表的组件赋值。

映射规则需要创建在一个映射表中,然后传递给映射对象。

例子如下:

DATA(mapper) =cl_abap_corresponding=>create(

source
=struct1

destination
=struct2

mapping
= VALUE cl_abap_corresponding=>mapping_table(

( level
= 0kind= cl_abap_corresponding=>mapping_component

srcname
=‘…’

dstname
=‘…’ )

( level
= 0kind= cl_abap_corresponding=>mapping_component

srcname
=‘…’

dstname
=‘…’ )

( level
= 0kind= cl_abap_corresponding=>mapping_component

srcname
=‘…’

dstname
= ‘…’ ) ) ).

 

在长期的停滞后,Open SQL的发展终于从沉睡中醒来。从ABAP 7.40开始,SAP推进了某些关键的改变,以尽可能地包含SQL92中的特性,并提供与ABAP CDS中的DDL里面的SELECT一样的功能给Open SQL。为了实现这些目标,ABAP运行时环境中引入了一个新的SQL parser作为Open SQL的新基础。结果就是,Open SQL现在可以在ABAP中扮演一些和以往不同的角色了。

虽然在7.40之前,Open SQL更多地被视为ABAP语言本身的一部分,但在同时,SQL关键字变得越来越介词化了。关于这点,主要的体现之一便是有关宿主变量的新规则。在7.40之前,你可以像在其它ABAP语句中使用ABAP变量那样在Open SQL中使用它们。实际上,这种自由阻止了更高效的开发。Open SQL语句在被转换为native SQL之后才会在数据库中运行。为了在WHERE条件中实现比简单比较更为复杂的东西,Open SQL parser必须能清晰的区分运算符两端的东西到底是ABAP变量、还是数据库内容,从而发送相应的内容给数据库。为了完成这一任务,Open SQL中的ABAP变量因此成为了完全的宿主变量host variables)。就像ABAP变量在native SQL中的成分一样(EXEC SQL)。你可以(而且应当)在Open SQL中的ABAP 宿主变量前加上转义符@实际上,只有这样做了,你才能使用ABAP 7.40版本之后的全部Open SQL新特性。Open SQL中引入的其它的基础修改也是为了让其更加适应未来,比如逗号分隔、以及将SELECT语句的INTO附加项放在authentic SQL子句的后面。(译注:authentic是什么意思没看懂,不过这句话的意思应当是指INTO语句不应是SQL本身的一部分,所以要放到后面以示区分)

 

这些方法带来的第一个好处,已经在ABAP 7.40版本中放出,包括可以在不同操作数位置使用的SQL表达式,以及内联声明的可能性。在ABAP 7.50中,Open SQL依然在发展着,本文将介绍一些新特性(未来还会有更多)。

 

本文链接:http://www.cnblogs.com/hhelibeb/p/7135899.html

原文标题:ABAP News for Release 7.50 – Host and Other Expressions in Open SQL

宿主表达式

在大多数可以放置宿主变量的地方,包含7.40版本以来的SQL表达式的操作数位置或者写SQL语句的工作区,现在可以通过如下方式放置宿主表达式(host expression):

… @( abap_expression ) …

宿主表达式abap_expression可以是任何ABAP表达式,可以是一个构造器表达式、表表达式、算术表达式、字符串表达式、bit表达式、内建函数、函数性的方法、或者是方法,它通过括号()包围起来,并且要加上前缀@。Open SQL中的宿主表达式从左到右计算,并且它们的结果会作为宿主变量传递给数据库。事实上,你可以将宿主表达式视为通过ABAP表达式为ABAP帮助变量赋值的简写。以下例子演示了一个表表达式,表达式从内表carriers中读取值,并放置在where条件右端:

SELECTcarrid, connid, cityfrom, citytoFROMspfliWHERE carrid =@( VALUE spfli-carrid( carriers[ KEY name
carrname
= name ]-carrid
OPTIONAL ) )
INTO TABLE @DATA(result)

 

SAP标准的REST adapter有着XML/JSON转换的功能,它很有用,因为一方面SAP PI/PO内部以XML格式处理数据,而另一方面,在处理REST架构风格的时候,JSON才是事实上的格式。

然而,观察下网上有关REST Adapter的相关问题,可以得出一个结论:XML消息处理后生成的JSON输出并非总是正确的,有时候它会把人引入歧途。SAP积极地增强了REST Adapter的各方面功能——定制化和特性丰富的JSON处理就是其中一个重点领域。许多相关特性已经在记录在SAP Help的文档中。但是其中的一项相当强大、灵活的功能——名为“增强XML/JSON转换”的功能,却只是简单地在SAP Note 2175218中提及。在本文中,我将阐述这个功能的用法,以及提供有效参数化方法的细节。

在内部,REST adapter使用了第三方的Jettison以实现JSON处理。在标准配置中,REST adapter依赖于Jettison处理器默认的转换逻辑,它不会考虑或关联相应的消息类型中定义的有效元素属性,而是有自己的优化和类型机制,该机制基于所要处理的XML文档的元素的值的本来性质,而非消息的XSD schema。这样一来的结果是,有时转换会导致不合需求的输出。以下是两个通常的例子:

  • 如果一个XML元素定义为数组,但是在被转换的XML有效数据中只包含一行,Jettison处理器将可能会将其转换为非数组类型
  • 如果一个XML元素定义为字符串,但是在被转换的XML有效数据中只有数字型的值,Jettison处理器将可能会将其转换为整数类型

在某些情况下,不合适的类型转换对程序而言是不可接受的——这也是增强XML/JSON转换可以帮助我们解决的问题。

SAP Note 2175218介绍了增强XML/JSON转换背后的一个想法,它是有关JSON处理器如何对待特别的XML元素的明确的描述。让我们基于一个练习例子来测验这个功能:

 

下面是一个消息类型的定义,它用于同步场景的返回消息,我们在其中使用了REST sender channel。如你所见,它包含了多种类型的元素,包含一个数组:

 

 XML格式的示例回复消息是这样的:

 

使用REST sender channel的标准配置,将上面的XML消息格式化后的JSON相应消息是这样的:

 

可以注意到,某些元素类型被错误的解释了,比如:

  • 元素“ID”没有被视为字符串,而是数字——Jettison处理器将它作为数字对待,因为元素的值只包含数字类型的字符;
  • 元素“Properties”没有被视为数组,Jettison处理器将它作为嵌套结构中的非数组对象,因为这个元素只包含“Property”的一个子实体(没有其它兄弟元素)。

让我们通过增强XML/JSON转换来修复它。在REST sender channel中,增强XML/JSON转换的参数化信息存储在表 “Custom XML/JSON Conversion Rules”中。下面是针对之前高亮的有问题的类型和转换不匹配的配置。

 

 

在再次执行接口后,检查被格式化为JSON的响应消息,可以观察到,现在产生了正确的JSON输出:

 

我在官方材料中没有看到有关于参数化的细节,所以让我来总结下增强XML/JSON转换中可以使用的可接受的和有效值,以及有关它们的使用的解释性说明。内容在下表:

字段

描述

有效值

XML 命名空间

XML元素的命名空间

 

前缀

XML元素命名空间前缀

 

名称

XML元素名

 

类型

XML元素类型。

以下类型是当前支持的:

String, Integer, Decimal, Boolean.

只要它是有效值列表中提到的值之一,就不会区分类型值的符号。

如果没有指定值,不会应用指定的XML/JSON转换指令,而是会执行默认的Jettison处理器逻辑。

String type

string

xs:string

xsd:string

Integer type

int

integer

xs:integer

xsd:integer

Decimal type

decimal

numeric

float

xs:decimal

xsd:decimal

Boolean type

bool

boolean

xs:boolean

xsd:boolean

数组类型

XML元素是否是数组的指示符。

只要它是有效值列表中提到的值之一,就不会区分类型值的符号。

如果没有指定值,数组指示符默认为false。

如果是数组:

1

true

yes

如果不是数组:

0

false

no

默认值

在XML/JSON转换失败的情况下会赋给JSON元素的值。

 

例如,在上面给的demo中,元素“Quantity”的值会被作为整数处理。如果原始值不能转换为整数(比如含有字母),JSON输出会得到一个默认值。在该情况下,这个值是“0”。

需要注意的是,对于默认值而言,系统不会针对在“TYPE”中指定的类型进行元素类型检查——它会被当作字符串。在这种方式下,比如,你可以指定默认值“Invalid value”给“Quantity”。系统不会提示错误,无论是在communication channel激活的时候还是REST adapter运行期间处理相关消息的时候,即便默认值和元素类型(整型)完全不匹配。记住这点,应当注意设置默认值时要保持其类型的一致性。

Any value.

下面的值有点特别:

“null”

(带引号) – 被解释为字符串“null”

null

(无引号) – 被解释为null

“”

(只有引号 – 被解释为空字符串

 

本文链接:http://www.cnblogs.com/hhelibeb/p/7395567.html

英文原文:REST Adapter in PI/PO: Enhanced XML/JSON Conversion

参考阅读:PI REST Adapter – JSON to XML conversion

       JSONTransformBean Part 1: Converting JSON content to XML

动机

现在大家都知道单元测试对我们代码的好处。并且我们都承认它是开发过程中不可或缺的一部分。但是在把代码切换到数据库的模式下的时候,我们被粗暴地打回了软件测试的黑暗年代...我们现在面临着逻辑下推到ABAP CDS entities后,代码要如何测试的难题。

CDS Test Double Framework允许开发者们通过众所周知的ABAP Unit Test Framework自动化地测试CDS entities。

本文链接:http://www.cnblogs.com/hhelibeb/p/7376232.html

英文原文:Introduction to CDS Test Double Framework – How to write unit tests for ABAP CDS Entities?

挑战

因为CDS entity中的逻辑运行在下层的数据库中(独立于abap runtime),使用传统的ABAP依赖注入解决方案以实现测试成为了不可能的事情。entity的依赖组件需要在数据库中加上测试替身,并且我们必须确保CDS entity测试的时候数据库引擎调用/执行这些测试替身(double)。

为了可以在CDS entity under test (CUT)中可控地测试逻辑,我们需要通过测试替身注射测试专用数据。这意味着必须将测试数据插入到测试替身中,这样数据可以在CUT执行时被测试替身们返回。对于在ABAP CDS上下文中有着固有的只读属性的依赖组件(比如数据库视图和数据库函数),这是一项特别的挑战。

本文中重要的缩写:

CUT = CDS entity Under Test

DOC = Depended-On Component

CDS Test Double Framework

CDS Test Double Framework处理了以上的挑战,并且可以实现CDS entities测试的自动化:

  1. 在相同的Database Schema中为每个依赖组件创建临时的可更新测试替身
    1. 复制依赖组件表,但不复制任何数据。不复制依赖数据库表的主键约束。这允许你轻松地插入测试数据,而不用担心数据的完整性。相似的,数据库索引也不会被复制。
    2. 为依赖数据库视图创建数据库表。这些表有着和依赖数据库视图相同的结构。
    3. 依赖数据库functions(由带有参数的依赖CDS视图和依赖表function产生)会被复制,并且function的测试替身实现会被修改为允许插入需要的测试数据。
  2. 在相同的Database Schema创建一个CDS entity under test(CUT)的临时副本。从各种意义上来看,这个副本是为CUT服务的。在原始的CDS entity中实现的逻辑在副本中同样存在,但是依赖组件被替换为了相应的测试替身。测试替身是由我们的CDS Test Double Framework所创建的。

测试什么?

单元测试应当专注于由一个给定视图实现的有价值的功能定义。不是所有的CDS视图都需要一个单元测试。在实现单元测试之前,建议辨别出entity中与测试有关的方面。

通常,需要为某些包含了代码下推的方法的entity进行单元测试。潜在的测试候选者包括:

Calculations and/or filters, conversions, conditional expressions 比如 CASE…THEN…ELSE or COALESCE, type changing CAST operations, cardinality changes or checks against NULL values,  JOIN behavior, complex where conditions 等.

单元测试不应用于测试那些更适用于静态检查、集成测试等技术的CDS entities属性。如果不能从单元测试中获取任何价值的话,也不应进行它,比如对于那些简单的CDS投影视图。

怎样使用CDS Test Double Framework写单元测试?

在下一部分,我们会通过被广泛应用的ABAP Unit Test Framework为以下的CDS视图创建单元测试。

@AbapCatalog.sqlViewName: 'zSo_Items_By_1'
@EndUserText.label: 'Aggregations/functions in SELECT list'
@AbapCatalog.compiler.compareFilter: true
define
viewSalesorder_Items_By_TaxRateas select fromCdsFrwk_Sales_Order_Item
association
[1] to snwd_so as _sales_order on so_guid =_sales_order.node_key
{
so_guid,
coalesce ( _sales_order.so_id, '9999999999' ) asso_id,
currency_code,
sum( gross_amount ) assum_gross_amount,
tax_rate,
_sales_order
}
group byso_guid,
_sales_order.so_id,
currency_code,
tax_rate