【ERP】利用内建对象Record Group做重复性验证

【ERP】利用内建对象Record Group做重复性验证

背景描述

在开发Oracle forms的过程中,唯一性验证及重复性验证时是最基本的form验证功能之一了。通常情况下,通过为基表创建唯一性索引的方式来达到唯一性验证已经可以满足日常开发的要求。但是,不免会有特殊的行为和需求唯一性索引的方式还是搞不定的,比如一条记录中的两个字段的值取自相同的数据来源,但是他们彼此之间以及记录与记录之间都不能允许有重复。

幸运的是,对于特殊的情况,开发者们也有很多解决方案加以应对,比如利用PL/SQL的内建数组来存储每一次录入的需要做验证的值,在验证时迭代所有数组中的值去做重复性验证。甚至你也可以利用字符串来存取单一的一个值的集合来做同样的事情,虽然我本人并不推荐使用这种方式;) 。

总结起来大概有这么几种:

  • 使用PL/SQL数组 (缺点:与form结合性不好)
  • 使用字符串 (操作起来复杂,不宜使用,验证多列时尤为明显)
  • 使用FIRST_RECORD, LOOP, 循环界面记录 (记录行多时速度慢,效率低,用户视觉体验不佳:因为光标一直在动。。。)

如果还有请帮我补充;) 

记录组(Record Group)

在开发实践中我们发现,在Oracle forms内部已经提供了一种内建的类似于数组的对象Record Group,我想提起他来大家肯定都不陌生~ 是啊,这有什么好惊讶的?我们在创建LOV的时候不是经常用到它吗。。。但是,Oracle也提供了一系列的内建API来帮助我们动态创建、删除和操作记录组以及记录组中的元素,而且可以以二维的形式存储多行数据。因此这个记录组对象就可以以编程的方式为我们所用。

相比其他的方式,我认为使用记录组的方式有如下优点:

  1. Form内建支持,可以与Oracle forms有机结合
  2. 提供简洁明了的API,便于使用
  3. 记录组的行号的运作机制与数据块上的 record number时完全相同的,便于同步界面上的记录
  4. 相比其他内建方法,速度快,效率也比较高

如果还有请帮我补充;)

API简介

下面就简要介绍一下能够对我们的验证所用的API函数定义,详细的说明和文档请参考Oracle Forms Builder的帮助文档。

-- FUNCTION
CREATE_GROUP();
-- 根据给定的名称在内存中创建一个非查询类型的记录组,函数返回创建的记录组的句柄。(句柄类型为RecordGroup)
 
    
   -- 
    PROCEDURE  
    
   DELETE_GROUP();  
   -- 
    从内存中删除已创建的记录组。 
   


-- FUNCTION
ADD_GROUP_COLUMN();
-- 为给定的记录组创建制定类型的列(类似于创建数据库表时初始化的字段属性一样),共有CHAR_COLUMN, DATE_COLUMN和NUMBER_COLUMN三种列类型
 
    
   -- 
    PROCEDURE 
     
   ADD_GROUP_ROW(); DELETE_GROUP_ROW();  
   -- 
    为指定的记录组新建/删除一个记录行 
    
    
   
 
   
-- FUNCTION / PROCEDURE
SET_GROUP_ < XXX > _CELL();
GET_GROUP_
< XXX > _CELL();
-- 设置/获取指定记录组的指定记录行上的某一个字段值。<XXX>一共有三种: CHAR, DATE和NUMBER
 
    
   -- 
    FUNCTION 
     
   GET_GROUP_ROW_COUNT();  
   -- 
    返回给定记录组中所拥有的行数 
   


-- FUNCTION
GET_GROUP_RECORD_NUMBER();
-- 返回包含特定值的记录行中的第一个行号,如果没有在任何记录行上找到该值,则返回0

  

一个简单的例子

下面就举一个简单的例子来说明这些API如何使用,图我就不上了,默认你们对form开发已经很熟悉啦,:)

假设form中有一个多行数据块REPLIST,现在我们要对该数据块的REPORT_ID字段做界面唯一性验证。

1. 向FORM级别的触发器WHEN-NEW-FORM-INSTANCE加入如下代码:

DECLARE
    rg_name VARCHAR2(30) := 'RG_REPORT_ID';
    rg_id   RecordGroup;
    rc_id   GroupColumn;
BEGIN
    -- 查找内存中是否已创建具有相同名称的记录组
    rg_id := Find_Group(rg_name);

    -- 如果有,从内存中删除
    IF NOT id_null(rg_id) THEN
        Delete_Group(rg_name);
    END IF;

    -- 创建记录组
    rg_id :=    Create_Group('RG_REPORT_ID');
    -- 为记录组添加列
    rc_id :=  Add_Group_Column(rg_id, 'REPORT_ID', CHAR_COLUMN, 100);
END;

-- 转向目标数据块
GO_BLOCK('REPLIST');
-- 执行查询
EXECUTE_QUERY;

2. 向数据块REPLIST添加块级触发器PRE-QUERY,添加代码如下:

DECLARE
    rg_name VARCHAR2(30) :=  'RG_REPORT_ID';
BEGIN
    -- 删除记录组中的所有行
    Delete_Group_Row(rg_name, ALL_ROWS);
END;

3.  向数据块REPLIST添加块级触发器WHEN-REMOVE-RECORD,添加代码如下:

DECLARE
    rg_name VARCHAR2(30) :=  'RG_REPORT_ID';
BEGIN
    -- 当数据块中某一条记录被删除后,其对应记录组中的记录也需要被删除
    Delete_Group_Row(rg_name, :system.trigger_record);
END

4. 向数据块REPLIST添加块级触发器WHEN-CREATE-RECORD,添加代码如下:

DECLARE
    rg_name VARCHAR2(30) := 'RG_REPORT_ID';
BEGIN
    -- 当用户创建记录时,也想记录组中添加相应一行
    -- 注意:该触发器会在form创建初期调用两次,显然那时
    -- 并不是我们创建记录的时候,故使用:system.block_status != 'NEW'这一句来过滤掉那两次触发
    IF :system.block_status != 'NEW' THEN
        Add_Group_Row(rg_name, :system.trigger_record);
    END IF;
END;

5. 向数据块REPLIST添加块级触发器POST-QUERY,添加代码如下:

DECLARE
    rg_name VARCHAR2(30) := 'RG_REPORT_ID';
BEGIN
    -- 创建新行
    Add_Group_Row(rg_name, :system.trigger_record);
    -- 为该行更新字段值
    Set_Group_Char_Cell(rg_name || '.REPORT_ID', :system.trigger_record, :replist.report_id);
END;

6. 向数据块REPLIST的REPORT_ID项添加ITEM级触发器WHEN-VALIDATE-ITEM,添加代码如下:

DECLARE
    rg_name      VARCHAR2(30) :=  'RG_REPORT_ID';
    n_row_number pls_integer;
BEGIN
    IF ( :REPLIST.report_id IS NOT NULL ) THEN

        -- 检查当前值是否有重复,如果有,返回找到的第一条记录的行号
        n_row_number := Get_Group_Record_Number(rg_name || '.REPORT_ID', :replist.report_id);
        -- 如果有重复,且不是本身,提示信息,并终止当前操作
        IF n_row_number > 0 AND n_row_number != :system.trigger_record THEN
        
            FND_MESSAGE.DEBUG('Duplicated!');
            RAISE form_trigger_failure;
                
        ELSE                
            -- 若未发现重复,则像记录组中更新值
            Set_Group_Char_Cell(rg_name || '.REPORT_ID', :system.triiger_record, :replist.report_id);

        END IF;
                
    END IF;            
END;

OK,大功告成了,界面上的验证部分就此告一段落,另外我们还要结合界面和数据库两个层面进行充分的验证,因为并不是所有的记录都显示在了界面上。

 

程序包zz_record_group

虽然内建的API已经很简洁了,但是某些操作为了简化API和验证需要在每次调用api之前做,很麻烦,故而本人干脆直接写了一个方便同学们(包括我自己)易于使用的通用程序包ZZ_RECORD_GROUP,该程序包有如下特点:

  1. 对记录行的创建和删除做了封装,简化了操作
  2. 对重复值查找提供了更方便的VALUE_EXISTS函数,分别为字符类型、数字类型和日期类型重载了三次
  3. 对单元格(cell)的修改操作改为set_value方便使用
  4. 以library形式发布,使用时直接attach到本机form上即可
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小麦进城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值