ZT:深度分析数据库的热点块问题

热点块的定义

数据库的热点块,从简单了讲,就是极短的时间内对少量数据块进行了过于频繁的访问。定义看起来总是很简单的,但实际在数据库中,我们要去观察或者确定热点 块的问题,却不是那么简单了。要深刻地理解数据库是怎么通过一些数据特征来表示热点块的,我们需要了解一些数据库在这方面处理机制的特性。

数据缓冲区的结构

我们都知道,当查询开始的时候,进程首先去数据缓冲区中查找是否存在查询所需要的数据块,如果没有,就去磁盘上把数据块读到内存中来。在这个过程中,涉及 到数据缓冲区中LRU链的管理(8i开始以接触点计数为标准衡量buffer冷热从而决定buffer是在LRU的冷端还是热端),关于这部分内容,从 oracle concepts 中就能得到详尽的文档,我不准备去论述这部分内容,这也不是本文的重点。现在我们的重点是,到底进程是如何地去快速定位到自己所想要的block的,或者 如何快速确定想要的block不在内存中而去进行物理读的。

我们仔细想一想,随着硬件的发展,内存越来越大,cache buffer也越来越大,我们如何才能在大量的内存中迅速定位到自己想要的block?总不能去所有buffer中遍历吧!在此数据库引出了hash的概 念(oracle中快速定位信息总是通过hash算法的,比如快速定位sql是否在shared pool size中存在就是通过hash value来定位的,也就是说shared pool size中对象也是通过hash table来管理的),了解一点数据结构的基本知识就知道,hash 的一大重要功能就是快速地查找。举个最简单的例子,假设我们有一个hash table 就是一个二维数组a[200][100],现在有1000个无序数字,我们要从这1000个数字里面查找某个值是否存在,或者说当我们接收到某个数字的时 候必须判断是否已经存在,当然,我们可以遍历这1000个数字,但这样的效率就很低。但现在我们考虑这样一种方法,那就是把1000个数字除以200,根 据其余数,放在a[200][100]里面(假设相同余数的最大数量不超过100),余数就是数组的下标。这样,平均来说一个数组a[i]里面可能有5个 左右的数字。当我们要去判别一个数字是否存在的时候,对这个数字除以200(这就是一个最简单的hash算法),根据余数i作为下标去数组a[i]中查 找,大约进行5次查找就能判别是否已经存在,这样通过开辟内存空间a[200][100]来换取了时间(当然hash 算法的选取和hash table的大小是一个很关键的问题)。

明白了基本的hash原理之后,我们再来看oracle的block的管理。数据库为这些block也开辟了hash table,假设是a,则在一维上的数量是由参数_db_block_hash_buckets 来决定的,也就是存在hash table a[_db_block_hash_buckets ],从oracle8i开始,_db_block_hash_buckets =db_block_buffers*2。而一个block被放到哪个buckets里面,则是由block的文件编号、块号(x$bh.dbarfl、 x$bh.dbablk对应了block的文件属于表空间中的相关编号和block在文件中的编号,x$bh是所有cache buffer的header信息,通过表格的形式可以查询)做hash 算法决定放到哪个bucket的,而bucket里面就存放了这些buffers的地址。这样当我们要访问数据的时候,可以获得segment的 extent(可以通过dba_extents查到看,详细的信息来源这里不做探讨),自然知道要访问的文件编号和block编号,根据文件和block 编号可以通过hash算法计算出hash bucket,然后就可以去hash bucket里面去找block对应的buffer。

除此之外,为了维护对这些block的访问和更改,oracle还提供了一种latch来保护这些block。因为要避免不同的进程随意地径直并发修改和 访问这些block,这样很可能会破坏block的结构的。latch是数据库内部提供的一种维护内部结构的一种低级锁,latch的生存周期极短(微秒 以下级别),进程加latch后快速的进行某个访问或者修改动作然后释放latch(关于latch不再过多的阐述,那可能又是需要另一篇文章才能阐述清 楚)。这种latch数量是通过参数_db_block_hash_latches 来定义的,一个latch对应的保护了多个buckets。从8i开始,这个参数的default规则为:

当cache buffers 少于2052 buffers

_db_block_hash_latches =  power(2,trunc(log(2, db_block_buffers – 4) – 1))

当cache buffers多于131075 buffers

_db_block_hash_latches =  power(2,trunc(log(2, db_block_buffers – 4) – 6))

当cache buffers位于2052与131075 buffers之间

_db_block_hash_latches =  1024

通过这个规则我们可以看出,一个latch大约可以维护128个左右的buffers。由于latch使得对block的操作的串行化(9i中有改进,读 与读可以并行,但读与写、写与写依然要串行),很显然我们可以想到一个道理,如果大量进程对相同的block进程进行操作,必然在这些latch上造成竞 争,也就是说必然形成latch的等待。这在宏观上就表现为系统级的等待。明白了这些原理,为我们下面的在数据库中的诊断奠定了基础。

如何确定热点对象

如果我们经常关注statspack报告,会发现有时候出现cache buffer chains的等待。这个cache buffer chains就是_db_block_hash_latches所定义的latch的总称,通过查询v$latch也可得到:

select”>sys@OCN>select latch#,name,gets,misses,sleeps from v$latch where name   like ‘cache buffer%’;

LATCH# NAME                                 GETS     MISSES     SLEEPS
———- —————————— ———- ———- ———-
93 cache buffers lru chain          54360446      21025        238
98 cache buffers chains           6760354603    1680007      27085
99 cache buffer handles               554532          6                   0

在这个查询结果里我们可以看到记录了数据库启动以来的所有cahce buffer chains的latch的状况,gets表示总共有这么多次请求,misses表示请求失败的次数(加锁不成功),而sleeps 表示请求失败休眠的次数,通过sleeps我们可以大体知道数据库中latch的竞争是否严重,这也间接的表征了热点块的问题是否严重。由于 v$latch是一个聚合信息,我们并不能获得哪些块可能存在频繁访问。那我们要来看另一个view信息,那就是 v$latch_children,v$latch_children.addr记录的就是这个latch的地址。

select”>sys@OCN>select addr,LATCH#,CHILD#,gets,misses,sleeps from v$latch_children
2  where name = ‘cache buffers chains’  and rownum < 21;

ADDR         LATCH#     CHILD#       GETS     MISSES     SLEEPS
——– ———- ———- ———- ———- ———-
91B23B74         98       1024   10365583       3957         33
91B23374         98       1023    5458174        964         25
91B22B74         98       1022    4855668        868         15
91B22374         98       1021    5767706        923         22
91B21B74         98       1020    5607116        934         31
91B21374         98       1019    9389325       1111         25
91B20B74         98       1018    5060207        994         31
91B20374         98       1017   18204581       1145         18
91B1FB74         98       1016    7157081        920         23
91B1F374         98       1015    4660774        922         22
91B1EB74         98       1014    6954644        976         32
91B1E374         98       1013    4881891        970         19
91B1DB74         98       1012    5371135        971         28
91B1D374         98       1011    5154497        990         26
91B1CB74         98       1010    5013796        936         18
91B1C374         98       1009    5667446        939         25
91B1BB74         98       1008    4673421        883         14
91B1B374         98       1007    4589646        986         17
91B1AB74         98       1006   10380781       1020         20
91B1A374         98       1005    5142009       1110         19

20 rows selected.

到此我们可以根据v$latch_child.addr关联到对应的x$bh.hladdr(这是buffer header中记录的当前buffer所处的latch地址),通过x$bh可以获得块的文件编号和block编号。

select”>sys@OCN>select dbarfil,dbablk
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11);

DBARFIL     DBABLK
———- ———-
4       6498
40      14915
15      65564
28      34909
40      17987
1      24554
8      21404
39      29669
28      46173
28      48221

……………………

由此我们就打通了cache buffers chains和具体block之间的关系,那再继续下来,知道了block,我们需要知道究竟是哪些segment。这个可以通过dba_extents来获得。

select distinct a.owner,a.segment_name from
dba_extents a,
(select dbarfil,dbablk
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11)) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk;
OWNER                          SEGMENT_NAME                   SEGMENT_TYPE
—————————— ——————————         ——————
ALIBABA                        BIZ_SEARCHER                               TABLE
ALIBABA                        CMNTY_USER_MESSAGE             TABLE
ALIBABA                        CMNTY_VISITOR_INFO_PK          INDEX
ALIBABA                        COMPANY_AMID_IND                   INDEX
ALIBABA                        COMPANY_DRAFT                         TABLE
ALIBABA                        FEEDBACK_POST                           TABLE
ALIBABA                        IM_BLACKLIST_PK                         INDEX
ALIBABA                        IM_GROUP                                        TABLE
ALIBABA                        IM_GROUP_LID_IND                      INDEX
ALIBABA                        MEMBER                                           TABLE
ALIBABA                        MEMBER_PK                                    INDEX
ALIBABA                        MLOG$_SAMPLE                            TABLE

……………………

我们还有另外一种方式

select object_name
from dba_objects
where data_object_id in
(select obj
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11)) ;
OBJECT_NAME
————————————
I_CCOL2
RESOURCE_PLAN$
DUAL
FGA_LOG$
AV_TRANSACTION
COMPANY_DRAFT
MEMBER
SAMPLE
SAMPLE_GROUP
VERTICAL_COMPONENT
MEMBER_PK
SAMPLE_GROUP_PK
IM_BLACKLIST_PK
IM_CONTACT
IM_GROUP
CMNTY_USER_MESSAGE
CMNTY_VISITOR_INFO_PK
IM_OFFLINEMSG_TID_IND
OFFER
OFFER_PK
OFFER_EMAIL_IND
OFFER_DRAFT
CMNTY_USER_MESSAGE_TD_BSM_IND
CMNTY_MESSAGE_NUM_PK
BIZ_EXPRESS_MEMBER_ID_IND

……………………

到这里我们基本能找到热点块对对应的对象。但实际上还有另外一个途径来获取这些信息,那就是和x$bh.tch 相关的一种方法。对于8i开始oracle提供了接触点(touch count)来作为block是冷热的标志,在一定条件满足的情况下block被进程访问一次touch count 增加一,到某个标准之后被移动到LRU热端(关于touch count 在这里不做详细介绍,那又将是一大篇文章)。那在短时间内从某种意义上讲,touch count 大的block可能暗示着在当前某个周期内被访问次数比较多。

select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk;

OWNER                          SEGMENT_NAME                   SEGMENT_TYPE
—————————— ——————————         ——————
ALIBABA                        CMNTY_USER_MESSAGE              TABLE
ALIBABA                        MEMBER_PK                                      INDEX
ALIBABA                        OFFER_DRAFT_GMDFY_IND          INDEX

同上面一样还有这个方法

select object_name
from dba_objects
where data_object_id in
(select obj
from (select obj
from x$bh order by tch desc) where rownum < 11) ;
OBJECT_NAME
—————————————————
DUAL
MEMBER_PK
SAMPLE_GROUP_PK
CMNTY_USER_MESSAGE_TD_BSM_IND
OFFER_DRAFT_MID_GMDFY_IND
OFFER_MID_GPOST_IND
OFFER_DRAFT_PK
MEMBER_GLLOGIN_IND
OFFER_MID_STAT_GEXPIRE_IND
SAMPLE_MID_STAT_IND

10 rows selected.

到这里,我们寻找热点块和热点对象的工作算是完成了,但我们还并没有解决问题。

热点问题的解决

热点块和热点对象我们都找到了,但是我们该怎么来解决这个问题呢?一般来说,热点块会导致cache buffers chains竞争等待,但并不是说cache buffer chains一定是因为热点块而起,在特别情况下有可能是因为latch数量的问题导致的,也就是一个latch管理的buffers数量太多而导致竞争 激烈。但是latch数量我们一般是不会轻易去设置的,这是oracle的隐藏参数。

实际上最有效的办法,是从优化sql入手,不良的sql往往带来大量的不必要的访问,这是造成热点块的根源。比如本该通过全表扫描的查询却走了索引的 range scan,这样将带来大量的对块的重复访问。从而形成热点问题。再或者比如不当地走了nested loops的表连接,也可能对非驱动表造成大量的重复访问。那么在这个时候,我们的目标就是找出这些sql来并尝试优化。在statspack报告中,根 据报告中sql列表,我们如果是通过dba_extents确定的热点对象而不是通过dba_objects确定的,则可以通过查找出的热点 segment转换为对应的表,对于非分区的索引,index_name就是segment_name,通过dba_indexes很容易的找到对应的 table_name,对于分区表和分区索引也能通过和dba_tab_partition和dba_ind_partitions找到segment和 table的对应关系。通过这些table到statspack报告中去找相关的sql。

select sql_text
from stats$sqltext a,
(select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk) b
where a.sql_text like ‘%’||b.segment_name||’%’ and b.segment_type = ‘TABLE’
order by  a.hash_value,a.address,a.piece;

SQL_TEXT
—————————————————————-
SELECT SEQ_SMS_TRANSACTION.nextval FROM DUAL
SELECT SEQ_BIZ_EXPRESS.nextval FROM DUAL
SELECT bizgroup.seq_grp_post.NextVal FROM DUAL
SELECT SEQ_SAMPLE.nextval FROM DUAL
SELECT bizgroup.seq_grp_user.NextVal FROM DUAL
SELECT SEQ_BIZ_SEARCHER.nextval FROM DUAL
SELECT SEQ_OFFER_DRAFT.nextval FROM DUAL
select seq_Company_Draft.NextVal from DUAL
SELECT SEQ_SAMPLE_GROUP.nextval FROM DUAL
SELECT SEQ_CMNTY_USER_MESSAGE.nextval FROM DUAL
SELECT SYSDATE FROM DUAL
select seq_News_Forum.NextVal from DUAL
SELECT SEQ_SMS_USER.nextval FROM DUAL
select seq_Biz_Member.NextVal from DUAL
select seq_Pymt_Managing.NextVal from DUAL
E= ‘+08:00’ NLS_DUAL_CURRENCY = ‘$’ NLS_TIME_FORMAT = ‘HH.MI.SSX
SELECT SEQ_COMPANY_DRAFT.nextval FROM DUAL
SELECT 1 FROM DUAL
select seq_offer_draft.NextVal from DUAL
select seq_Biz_Express_Category.NextVal from DUAL

20 rows selected.

当然这里是从statspack搜集的stats$sqltext中去找的(你可以在statspack的文本报告中去找),实际上,我们可以直接在当前数据库中的v$sqlarea或者v$sqltext里面去找到这些sql,然后来尝试优化。

select sql_text
from v$sqltext a,
(select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk) b
where a.sql_text like ‘%’||b.segment_name||’%’ and b.segment_type = ‘TABLE’
order by  a.hash_value,a.address,a.piece;
SQL_TEXT
—————————————————————-
SELECT NULL FROM DUAL FOR UPDATE NOWAIT
SELECT SEQ_SMS_TRANSACTION.nextval FROM DUAL
SELECT SEQ_BIZ_EXPRESS.nextval FROM DUAL
SELECT SEQ_IM_GROUP.nextval FROM DUAL
SELECT SEQ_SAMPLE.nextval FROM DUAL
=’DD-MON-RR HH.MI.SSXFF AM TZR’ NLS_DUAL_CURRENCY=’$’ NLS_COMP=’
SELECT SEQ_BIZ_SEARCHER.nextval FROM DUAL
SELECT SEQ_OFFER_DRAFT.nextval FROM DUAL
SELECT SEQ_SAMPLE_GROUP.nextval FROM DUAL
DD-MON-RR HH.MI.SSXFF AM TZR’ NLS_DUAL_CURRENCY=’$’ NLS_COMP=’BI
SELECT SEQ_CMNTY_USER_MESSAGE.nextval FROM DUAL
SELECT SYSDATE FROM DUAL
SELECT SEQ_SMS_USER.nextval FROM DUAL
IMESTAMP_TZ_FORMAT=’DD-MON-RR HH.MI.SSXFF AM TZR’ NLS_DUAL_CURRE
SELECT SEQ_COMPANY_DRAFT.nextval FROM DUAL
SELECT 1 FROM DUAL
SELECT USER FROM DUAL
SELECT DECODE(‘A’,’A’,’1′,’2′) FROM DUAL

18 rows selected.

除了优化sql外,当然对于热点的表或者索引来说,如果小的话,我们可以考虑cache在内存中,这样可能降低物理读提高sql运行速度(这并不会减少 cache buffer chains的访问次数),对于序列,我们可以对序列多设置一些cache。如果是并行服务器环境中的索引对象,并且这个索引是系列递增类型,我们可以考 虑反向索引(关于反向索引这里就不过多地做介绍了)。

热点块的其他相关症状

在数据库中还可能存在一些其他方面的热点块症状,通过v$waitstat的等待可以看出一些端倪,v$waitstat是根据数据缓冲区中各种block的类型(x$bh.class)而分类统计的等待状况。

select”>sys@OCN>select * from v$waitstat;

CLASS                   COUNT       TIME
—————— ———- ———-
data block            1726977     452542
sort block                  0          0
save undo block             0          0
segment header             40         11
save undo header            0          0
free list                   0          0
extent map                  0          0
1st level bmb             611        112
2nd level bmb              42         13
3rd level bmb               0          0
bitmap block                0          0
bitmap index block          0          0
file header block          13         92
unused                      0          0
system undo header        111         28
system undo block           7          0
undo header              2765        187
undo block                633        156

比 如在ASSM表空间出现之前,由于freelist的存在,如果表经常被并发的进程DML,则可能存在大量的data block的等待,或者有free list的等待。那么这个时候我们发现这样的segment之后需要考虑增加freelist数量。再比如经常发生长时间的DML的表被频繁地访问,这样 将会造成过多的对回滚段中块的访问,这时可能undo  block 的等待会比较多。那么我们可能需要控制DML的时间长度或者想办法从应用程序入手来解决问题。如果是undo header的等待比较多,没使用undo tablespace 之前,可能需要考虑增加回滚段的数量。

总结

本文从热点块的原理入手,详细地由oracle的内部结构特征开始介绍了热点块的产生和表现特征。进而阐述了诊断热点对象和找出造成热点对象的sql的方法。并从解决热点问题方面提供了解决方向。

文章来源:http://blog.csdn.net/biti_rainy/article/details/35188

报表练习题:销售订单打印的相关说明

报表练习题:销售订单打印

一、此练习的目的在于帮助同学们掌握:

1.1、如何从界面查数据来源(表或视图)

1.2、如何从界面查“界面字段对应的数据库字段”

1.3、初步掌握EBs标准Form代码的分析方法

1.4、进一步巩固基础报表开发能力

二、说明:

2.1、此练习的功能设计文档 参考doc/Md060/ 下的文档:

image

2.2、此练习的技术设计文档 参考doc/Md070/ 下的文档:

image

2.3、此练习的参考代码 参考doc/program/ 下的文档:

image

 警告:参考代码是有问题的,直接照搬照抄是不行的,只有在弄懂了销售订单的表结构关系以后才能正确的更正并完成报表开发。

三、提示:

3.1、附件中提供的rdf中的SQL语句可能存在问题 :

比如:

3.1.1 附件中rdf中的SQL语句的where条件对session的语言环境有依赖,这是不对的。(比如以状态的中文名称作为条件就是不对的,如果用户切换到英文环境就完蛋了,应该以一个跟语言环境无关的状态代码作为条件)

         况且where 条件中使用函数(没有建函数索引)将严重影响性能,因此就错的更离谱了。

3.1.2 原来的 sql语句是针对特定项目环境的,我能练习是针对Demo环境的,需要自己弄清楚销售订单的表结构和数据逻辑关系后加以修改。

四、常见问题:

4.1   如何从销售订单的界面查找到销售订单的数据源?

答:一般说到数据源即指: View->synonym->Table

针对销售订单:

销售订单的导航路径在:operations/welcome登录 Nav: Order Management Super User, Vision Operations (USA)-》Ordders,Return -》销售订单

查询:客户名称= ABC Corporation Asia 的订单

查历史记录可看到界面对应的View , 对View进行分析可获得其来源表信息;

clip_image001

clip_image002

所以销售订单对应的视图就是:OE_ORDER_HEADERS_V

在PLSQL Developer中打开该视图的定义,在定义中最主要的一张表一般就是主表

image

对销售订单头而言:oe_order_headers 看上去就是主表,但实际上,它是个同义词:

image

该同义词指向:ONT.OE_ORDER_HEADERS_ALL

image

对我们开发人员来说,在R12版本中我们客户化的程序中一般也是要使用同意词,而不是同义词指向的最终表,原因是R12的VPD策略一般都是建立在同义词上,而不是最终的表上,请看:

image

我们写的程序在访问数据时理应受VPD控制,所以我能对数据源的追踪也是追到同意词即可;

 

同理:光标停到订单行上,查看历史记录可看到订单行对应的View, 对View进行分析可获得其来源表信息.

clip_image005

 

4.2  报表中的参数:订单类型参数需要用LOV输入,如何实现?

答:订单类型的定义在

Nav:Order Management Super User, Vision Operations (USA)-》Setup-》事务处理类型-》定义

image

OE_transaction_types_V 的定义如下:

image

可见该视图主要来源是OE_TRANSACTION_TYPES_VL, 对VL进一步分析可见其最终来源于TL和同意词,因此到VL可不在往下追了,直接使用VL作为值列表的数据来源不会造成多余的查询而导致性能浪费;

image

在销售订单头的数据源里面,保存的是订单类型的ID, 也就是对应OE_TRANSACTION_TYPES_VL.TRANSACTION_TYPE_ID  , 界面上的订单类型名称也就对应OE_TRANSACTION_TYPES_VL.name ;  所以,对于销售订单类型的值列表而言可以把SQL写成:

select  TRANSACTION_TYPE_ID,name from  OE_TRANSACTION_TYPES_VL; 考虑到要对OU进行屏蔽,再加一个条件:

select  TRANSACTION_TYPE_ID,name from  OE_TRANSACTION_TYPES_VL  where org_id =fnd_profile.value(‘ORG_ID’)

可把值集定义成如下样子:

Nav:Application Developer->应用产品->验证->请求集:

image

编辑信息:

image

值集定义好以后,再把该值集赋给参数即可:

在并发程序的参数定义界面:

image

提交并发程序时可能出现订单类型值列表没有值的情况:

image

详细资料:

image

此现象的原因分析:

因为VL视图来源于:OE_TRANSACTION_TYPES_TL T, OE_TRANSACTION_TYPES_SYN B ,其中OE_TRANSACTION_TYPES_SYN  上面是有VPD策略的:

image

倘若在Form中访问VL,肯定是没有问题的,因为用户登录后,上下文环境已经存在了;但对于并发程序而言,并发程序的参数值列表是通过fdlfch()来获取的,该程序是个C写的编译后的二进制文件,我们估计它获取上下文的逻辑与form不太一致;

我们猜测:它需要并发程序必须定义业务模式后才能正确获取到用户登录上下文:

因此需要定义并发程序的业务模式:

Nav:系统管理->并发->程序

image

点击 更新 按钮,一定要选择一个业务模式,然后保存;

image

这样再次提交报表时,参数就可以选到了:

image

 

4.3  报表中的参数:订单状态需要用LOV输入,如何实现?

 

答:报表参数使用LOV输入,只需要给参数定义一个值集即可,这个在上文已经讲过,对订单状态而言,关键是找到订单状态的内部Code 和 外部名称的Mapping定义来源:

光标停到状态字段,选择 帮助-》诊断-》检查 ,可知道界面上的状态对应哪个block的哪个item,一般来说,item名称与数据表中的字段一致。

clip_image003

clip_image004

如果不一致,需要打开form源代码分析,这个例子中界面上看到的Status这个字段,在View里面不存在,所以要对form进行分析,知道status的具体来源, 首先要知道当前Form的名称,这可以通过帮助->关于oracle Applications 找到:

image

image

这里的表单路径中的,OEXOEORD.fmx就是编译后的form名称,其源代码对应于OEXOEORD.fmb , 源代码一般存在于$AU_TOP/forms/US   和 $AU_TOP/forms/ZHS目录下,我们这下载中文版的Form源代码就应该从

$AU_TOP/forms/ZHS中找:

image

下载下来,打开时提示:此Form引用了很多其他Form, 则其他Form 也需要下载:

image

image

此例中可能需要下载的被引用的Form包括:

image

打开后,找到order块,打开post-query Trigger, 里面有代码OE_ORDERS.Post_Query

image

查找,OE_ORDERS ,可知这个Api 在OEXOEHDR 这个pll 中;

image

打开: OEXOEHDR 这个pll

image

在导航对象查找:OE_ORDERS

image

打开 OE_ORDERS, 找到Post-query() , Ctrl+F 在编辑器中查找 “STATUS” , 可以找到这么一段代码:

IF Name_In(l_orders_block||’.flow_status_code’) IS NOT NULL THEN
   x_lookup_value:=null;
   l_count:=0;
   l_count:=OE_Cache_Values.IS_Value_In_Cache(
             p_code      =>Name_In(l_orders_block||’.flow_status_code’),
             p_rg_name   =>’FLOWSTATUS_CACHE’,
             x_value     => x_lookup_value
             );
   IF l_count <>0 AND x_lookup_value IS NOT NULL THEN
     Copy(x_lookup_value,l_orders_block||’.status’);
   ELSE
      p_header_rec.flow_status_code := Name_In(l_orders_block||’.flow_status_code’);
   END IF;
END IF;

从代码可知: Block 上的status 字段是根据flow_status_code 而显示出来的,所以该字段实际对应数据库表中的Flow_status_code  字段;

而且,从代码我们基本猜测到:flow_status_code 的 Code 和名称的mapping关系是在record Gruop FLOWSTATUS_CACHE 中,如果我们找到FLOWSTATUS_CACHE这个record group的定义,自然就知道其SQL来源了,因此我能查找对象FLOWSTATUS_CACHE:

image

可惜我们没找到,因为form中不存在FLOWSTATUS_CACHE 这个记录组的定义,既然如此,那么这个记录组有可能是在程序中动态的populate出来的,因此PLSQL检索FLOWSTATUS_CACHE :

image

 

image

共找到两处代码:另外一处指向 write_value_in_cache, 是向FLOWSTATUS_CACHE记录组中写内容,看来这个记录组不是从sql语句populate出来的;那么我们要最终是谁从哪里把数据读出来,然后写进去的

image

我们在所有代码中查找:write_value_in_cache, 我们发现数据来源于x_header_val_rec.status

于是我们在继续全代码搜索:x_header_val_rec.status

image

我们发现是由:Oe_Oe_Form_Header.POPULATE_CONTROL_FIELDS 获取的:

在对象中搜索:Oe_Oe_Form_Header

image

什么也没有搜索到,说明这不是一个Form Pll 的package ,而是一个数据库package ,因此转到PLSQL Developer中查看其定义:

image

从PLsQL 程序中我们可以看到Flow_status的Code和名称的Mapping定义是在Fnd_lookup_Values表中,因此状态的值列表Sql语句我们可以写成:

SELECT lookup_code,meaning
FROM FND_LOOKUP_VALUES LV
     WHERE LANGUAGE = userenv(‘LANG’)
     and VIEW_APPLICATION_ID = 660
     and lookup_type=’FLOW_STATUS’

定义值集并赋个报表参数的步骤此处就省略了。

 

4.4 如何知道销售订单在挑库过程中涉及的表与表之间的关系

 

答: 每次挑库都会有个挑库单报表,可以通过这个来追踪订单行与发运、库存表之间的关系

首先我们看清楚并发程序的名称:

image

 

然后我们切换到 Application Developer责任 ,

查找并发程序定义和可执行定义:

image

image

 

确定要到WHS模块下去找rdf 文件:

image

使用Report Builder 打开这个文件,查看SQL:

image

 

从SQL的From和Where 条件可以看到订单行表与各表之间的关系:

image

如果你不知道这里面有些表是干什么用的,可以查询Oracle 的在线TRM 表信息文档,比如,你想知道wsh_delivery_details 表是干什么的,可以登录:

EBS.R12表结构文档路径:       

http://etrm.oracle.com/pls/etrm/etrm_search.search

查询这张表:

image

 

可以得到这张表的详细信息:

image

 

如果你想知道挑库单号是来自哪张表的哪个字段:可以看报表的布局,然后点挑库单号对应的框,可以追溯到其在数据模型中的字段名:

image

 

image

 

image

 

 

回过头来,做一张订单

 

image

image

 

挑库:

image

 

查看挑库报表: 生成了类型=挑库通知的 物料搬运单(3905075)、交货(3773389)

image

 

这个物料搬运单可以在库存模块的物料搬运单界面找到: 其对应的视图就是MTL_TXN_REQUES_LINES_V

image

而交货号可以在发运模块的发运事务处理界面找到

image

然后做延交发运(由于某种原因,当前没有商品可发运,所以就延迟交货,意味着本次交货作废)

image

 

已延交

image

 

进一步发放已延交的部分:

image

重新生成性的物料搬运单和交货号

image

重新针对新的交货,进行发运事务处理:

image

我们可以看到交货号是新的的交货号,但是详细信息字段还是一样的。

image

 

全部发运掉:

image

 

 

 

通过对挑库单报表的SQL和这些表、视图进行分析,可以知道:

1)交货号=wsh_new_deliveries.delivery_id=wsh_delivery_assignments_v.delivery_id

2) 详细信息的这个字段=wsh_delivery_details.delivery_detail_id

3) wsh_delivery_details 与 订单行oe_order_lines_all是一对一关系,通过

    wsh_delivery_details.source_line_id =oe_order_lines_all.line_id 关联;

4)wsh_delivery_details  与 wsh_delivery_assignments_v 是一对一关系,而wsh_delivery_assignments_v 与 wsh_new_deliveries 是一对多关系,因为在发运延交的情况下,wsh_new_deliveries 就会有多条记录。

5)物料搬运单行与 wsh_delivery_details 表是通过这个关联的  mtrl.line_id = wdd.move_order_line_id

6)物料搬运单行 与 物料事务处理临时表 是一对一关系 ,关联条件是:  wpsv.move_order_line_id = mtrl.line_id

7)挑库报表完成后如果显示成功挑库,就意味着已经生成了两条库存事务处理,可以根据挑库单号查询出来,比如:

image

PLSQL加密和解密

加密:使用oracle提供的wrap工具:
举例:
D:\mytest> c:\devsuitehome_1\bin\wrap iname=mypackage.pck

输出:在当前目录下生成一个同名的.plb文件 mypackage.plb

编译加密文件:
登录plsqldeveloper ,打开commond窗口 ,编译加密文件:
SQL>@d:\mytest\mypackage.plb;
编译成功;

解密:使用“好事者”提供的解密工具:
base   (来源:http://www.itpub.net/thread-1175718-8-1.html)

用法:base <加密文件名>  <解密文件名>

可把加密的plsql 进行解密:
举例:把dbms_output的加密的部分另存为dbms_output.plb文件
d:\mytest>base dbms_output.plb dbms_output.txt

即可成功解密;

FOLDER FORM 问题

Folder查询结果不对的问题:

问题:在一次项目UAT时,一个用户测试时发现她总是查询不出结果,这个FORM是我们客户化一个标准的FORM。

解决过程:因为是标准FORM,而且我们之前自己测试时也没有遇到这个问题,所以以为系统对用户权限有什么控制,我们查看FORM源代码,但是找了很久还是没有找到问题的所在,打印DEFAULT_WHERE发现每一次查询后,DEFAULT_WHERE会增加一段相同的我们很费解的条件,就是这个条件导致查询不出结果。

问题原因:最后多亏吴前进提醒,最终我们找到问题所在。原来该用户在操作时,保存了FOLDER,在保存时勾选了”包括查询”,结果就把当时的查询条件给保存了下来,所以该用户在每一次查询是都会带上该条件,导致查询不出结果。对于保存的FOLDER,点击 文件夹-》查看查询,查看保存的查询。

总结:在用户培训时,对于FOLDER的FORM,要提醒用户此类问题,如果保存FORM时,要慎重勾选“包括查询”,除非确实需要保存的条件。对于FOLDER FORM,如果碰到某种情况加查询结果不准确,要首先考虑是不是FOLDER保存查询的原因。

FOLDER修改多堆叠画布问题

问题:在做一个Tab FOLDER FORM时,由于需要变化每一个Tab页上的ITEM,重新分配,将原来在第一个画布上的ITEM放到第三个画布上面,我直接修改的画布属性。结果导致第一个画布的ITEM布局乱了,出现了一部分ITEM堆到了另一部分ITEM上面。

解决过程:因为修改ITEM比较多,我以为是ITEM没有对应上或者Promt与内容字段顺序没有对应上的问题,结果比对了好久,发现没有问题。

问题原因:多谢徐晓晓同学提醒,原来是我直接修改ITEM属性,而没有去调整ITEM的顺序。对于这种多堆叠画布的FOLDER,需要把属于同一个画布的ITEM都放在一起,如果交叉放的话会导致一个画布上的ITEM堆起来。

思考:FOLDER将ITEM放到画布上时,应该是按照ITEM的顺序去填到画布上去的,而从一个画布到另一个画布上时,都会把ITEM从画布的最左边开始摆放,所以导致了ITEM堆起来的结果。

FORM 中LOV使用别名问题

问题:在一个LOV的记录组中SELECT nvl(p.full_name, fu.user_name) FULL_NAME
,fu.user_name
,fu.user_id
FROM fnd_user fu
,per_all_people_f p
WHERE fu.employee_id = p.person_id(+)
ORDER BY 1

LOV选择在显示之前过滤

发现查询的结果中,总是没有取到user_name的情况。

解决&总结:通过做TRACE发现,在显示之前过滤时,会自动在后面拼上一段条件FULL_NAME like :1 …..实际上执行的时候,FULL_NAME指向的是per_all_people_f里面的 FULL_NAME,所以没有出现user_name的值(其实这样使用别名是会报错,这是一个巧合);而当把别名修改成为其他(非数据库字段名)NAME时,会报NAME标识符无效错误。这是肯定的,应为SQL就是不能用别名来作为查询条件的。解决方法有两个:1,将显示之前过滤设为否,这样就会把所有记录先查出来,然后在查询出来的记录组里再筛选。不会在之前拼SQL了。

2,如果一定要在显示之前过滤的话,使用嵌套查询。

帮助》历史记录灰掉问题

问题:在一个FORM中发现历史记录菜单灰掉

解决:这个跟块查询灰掉一样,块查询是把块级触发器WHEN-NEW-RECORD-INSTANCE触发器层级修改为BEFORE,

这个问题需要将WHEN-NEW-BLOCK-INSTANCE触发器层级修改为BEFORE,因为这两个的可用与否,是FORM级触发器判断的,如果覆盖,就不可用了。

PS:使用历史记录时 报“历史记录不可用”,是由于在该块中没有5个数据库WHO字段的原因,添加上就行了。

Office2010开发BI Publisher RTF模版的若干疑难问题

1、图表刷新问题:

     现象:使用BI publisher在Word中插入图表以后,右键单击图表没有“BI Publisher 图表…”菜单项,且预览时,不会因为xml数据变化而使得图表跟着变化,感觉是一幅静态图片。

     分析:在把文档另存为97-2003的兼容格式后,再另存为RTF格式文档后,发现在图片上右键菜单有“BI Publisher 图表…”菜单项了,预览时图形也确实会跟着xml数据变化而变化。

     解决方法:按照分析中的方法创建RTF文档 ,这可能是一个兼容性问题。

 

2、模板文档属性问题:

     现象:在RTF模版的Advanced例子中有一个需要通过设置文档属性来控制的例子 Properties, 但很难找到自定义文档属性的地方。

     分析与解决:word2010设置自定义属性的地方确实够隐蔽,但也很容易找到,看下图:

     在文件->信息->属性->高级属性

image

    

    image

FROM批量处理界面效果

FROM批量处理界面效果

当遇到对FORM上记录进行批量处理时,一般情况下都会选择在记录前面加上复选框,然后勾选上需要处理的数据,最后获取勾选的记录数据进行业务处理:

clip_image002

如果只是添加复选框,显得界面效果太单调,勾选结果显示也不够明显,将勾选的行进行特殊显示,会使得勾选效果更好。

通过在标准界面查找类似业务,找到对应API:

app_record.highlight(‘SELECTED_DATA’);高亮显示当前行所有ITEM

app_record.highlight(”);取消高亮显示当前行所有ITEM

效果如下图:

clip_image004

这样看起来用户会更明确的看到选择的行。

效果已经做出来,但是在操作上,用户会有一些不便,选择几行或者全选还好。但是如果一次连续选择50条,100条用户需要点击100次?用户会疯掉。

解决方法:

在我们用电脑选择多个对象时,习惯性使用CTRL或者SHIFT键来多选记录,可以考虑使用这个方法来处理。

通过查找标准FORM的库代码,找到了解决方法。

核心在一个SYSTEM属性

name_in(‘SYSTEM.MOUSE_BUTTON_MODIFIERS’);

返回varchar2。

在WHEN-MOUSE-CLICK触发器时,这个属性会返回在你点击鼠标时,同时按住的那些按键名称用’+’拼接起来,如:同时按住Ctrl和Shift时返回Shift+Control+。有了这个后面问题就好解决了,只需要判断返回值是否包含Control或者Shift即可。

处理逻辑:

1. 按住Ctrl点击左键,如果原来勾选高亮显示,则变成取消勾选高亮显示,反之,则变成勾选高亮。

2. 按住Shift时需要增加一个变量记录上一次点击行Last_record

然后再根据当前点击行,循环中间所有行,勾选高亮显示,按住Shift的优先级应该高于Ctrl。

注意:因为勾选复选框时会触发WHEN-CHECKBOX-CHANGED,然后也触发WHEN-MOUSE-CLICK事件,这样按住CTRL会导致做两次事件,此时需要判断去跳过其中一个事件。

这样,就有多种操作方式选择记录了,使用户操作更加个性化,同时节省操作时间。

附:批量处理界面数据获取的心得:

对于这种勾选批量处理界面数据获取,首先想到的就是去循环界面所有记录去检查是否勾选,从而获取数据。这种方法简单,代码也很好写。但是当在界面操作时,问题就会出现:只勾选一条记录,却要去循环整个界面所有数据,还包含没有fetch到界面的数据。使得处理速度大大增加。“想尽办法去避免循环界面“这是我在做这个功能一个的体会(全选是不可避免的要循环界面了)。所以这种勾选模式要提前去获取数据。首先定义一个全局Table类型的变量,当勾选某一条记录时,将值存在Table(:SYSTEM.CURSOR_RECORD)里,取消勾选时Table.delete(:SYSTEM.CURSOR_RECORD)。这样在最后处理逻辑是只需要循环Table就行了。避免去Fetch不需要的数据(处理过程中,Table中间delete掉的记录在获取时会报No_data_found异常,只需要抛出异常跳过就行了)。即使勾选第一条和最后一条,虽然循环Table和循环界面次数一样,但是速度却有很大差别。