使用 APEX 实施动态矩阵报告的最佳方法是什么?

What are the best ways to implement a dynamic matrix report using APEX?

我需要使用 Oracle Application Express 框架完成此任务。

假设我们有这样一个查询:

select   
  col1,  
  col2,  
  val1,  
  val2,  
  val3,  
  val4,  
  val5,  
  val6,  
  val7,  
  val8,  
  val9,  
  val10,  
  val11  
from table(mega_function(city => ?, format => ?, percent => ?, days => ?));

而这个查询 returns 是这样的(以 CSV 格式显示):

col1;col2;val1;val2;val3;val4;val5;val6;val7;val8;val9;val10;val11
S2;C1;32000;120;"15:38:28";1450;120;1500;1200;31000;120;32600;300
S1;C1;28700;120;"15:35:01";150;120;1500;1800;2700;60;28900;120
S1;C2;27000;240;"14:44:23";0;1500;240;1200;25500;60;null;null

简而言之,查询基于一个流水线函数,该函数采用一些参数和 returns 前两列 col1;col2 的不同值对的一些值集 col1;col2

我需要实现的是一个矩阵报告,其中 col1 的值用作报告的行,col2 的值用作列。在交叉点上,有一些单元格包含一对值的集合,并应用了一些格式和样式。还需要什么 - 按行排序(应该按列 'val1' 的值对列排序)。

或者如果我们在模型上展示上述需求:

所以问题是 - 实施这种具有一些交互和自定义样式的矩阵报告的最佳实践是什么?

我已经尝试研究的内容:

根据您的要求,APEX 可能不是正确的工具,您将无限地受到 JQuery 等底层库和功能的限制。

我不会冒险在 APEX 上开发这样的应用程序,在我看来,对于 APEX 中提供的技术来说,要求太高了。

另一方面,您谈论的是数据库性能。很好,您从规划阶段就考虑到了这一点,但不好,因为您已经限制了自己。有多种选择...例如在数据库中获取内存中选项 and/cache 实体化视图的结果。将表固定到内存中。

你是对的。我只是根据我对你的需求描述的早期理解提供了一个意见。现在我更仔细地阅读了它,我改变了主意,因为我意识到你所拥有的并不是一个困难的矩阵。事实上我有很多这样的矩阵。 .

我本身并不知道最佳实践,但我可以与您分享我针对类似要求所做的工作:

对于基于矩阵的报告,我更喜欢经典报告,我将我的过滤条件放在 header 部分(就像在您的模拟中一样),并且根据用户选择,信息变化非常好。这就是我处理过滤和排序的方式。

您的要求中最难的部分 (IMO) 是检查用于导出目的的单元格。您应该能够在查询中动态启用切换控件,并且您应该 AJAX能够提取选定的出口。

不幸的是,我在问题中提到的 none 个选项满足所有要求,因为报告将存在的条件:

  • 数据应该每隔 5 秒动态更新一次。
  • 报告的状态应在数据更新时保存。
  • 报告的列数是可变的(列的定义是 提供数据),行数也是可变的。报告应该有 排序、分页和滚动(按 X 和 Y)选项。所有的东西 (排序等)应该在客户端完成。
  • 样式和自定义单元格渲染应应用于 table 的单元格。
  • 单元格应该是可点击的(点击应该产生一个事件,即 拦截table).

我意识到对于这样的任务,最好在客户端即时操作 DOM 而不是使用一些开箱即用的 APEX 解决方案,如经典报告、交互式报告或网格。

我为此方法使用了 DataTables.js jQuery 插件。经过一周的技术评估和一些基础知识的学习 JavaScript(这不是我的主要技能),我得到了以下结果:

在 APEX 应用程序中,我实现了一个 Ajax 回调过程(称为 TEST_AJAX),它运行 PL/SQL 代码,其中 returns JSON-object到 SYS.HTP 输出(使用 APEX_JSONHTP 包)。来源:

declare 
    l_temp sys_refcursor;
begin  
    open l_temp for go_pivot;
    APEX_JSON.open_object;
    APEX_JSON.open_array('columns');
    APEX_JSON.open_object;
    APEX_JSON.write('data', 'COL2');
    APEX_JSON.write('title', '/');
    APEX_JSON.close_object;
    for x in (select distinct col1 from test order by 1) loop
        APEX_JSON.open_object;
        APEX_JSON.write('data', upper(x.col1));
        APEX_JSON.write('title', x.col1);
        APEX_JSON.close_object;
    end loop;
    APEX_JSON.close_array;
    APEX_JSON.write('data', l_temp); 
    APEX_JSON.close_object;
end;

go_pivot函数来源:

create or replace function go_pivot return varchar2
  is
      l_query long := 'select col2';
  begin
      for x in (select distinct col1 from test order by col1)
      loop
          l_query := l_query ||
             replace(', min(decode(col1,''$X$'',v)) $X$',
                      '$X$',
                     x.col1);
     end loop;
     l_query := l_query || ' from test group by col2';
    return l_query;
 end;

然后我在页面上创建了一个静态内容区域,其来源如下:

<div id="datatable_test_container"></div>

我上传了 CSS 和 DataTables.js 的 JS 文件到应用程序静态文件,并将它们包含在页面属性中。在 Function and Global Variable Declaration 页面的 JavaScript 部分,我添加了这个 javascript 代码:

var $ = apex.jQuery;
var table;
var columns;
var rows;

//table initialization function
function table_init(json_data) {
    return $('#datatable_test').DataTable({
        //column defaults options
        columnDefs: [{
            "data": null,
            "defaultContent": "-",
            "targets": "_all"
        }],
        columns: json_data.columns,
        data: json_data.data,
        stateSave: true
    });
}
//function to asynchronously get data from APEX AJAX CALLBACK
//process and then to draw a table based on this data
function worker() {
    //run the process called TEST_JSON
    apex.server.process(
        "TEST_JSON", {}, {
            success: function(pData) {
                //on first run we need to initialize the table
                if (typeof table == 'undefined') {
                    //save current data for future use
                    columns = $.extend(true, [], pData.columns);
                    rows = $.extend(true, [], pData.data);
                    //generate empty html-table in the container
                    $('#datatable_test_container').append('<table id = "datatable_test" class = "display" cellspacing = "0" width = "100%" > < /table>');
                    //init the table
                    table = table_init(pData);
                    //when columns of the table changes we need to 
                    //reinitialize the table (DataTables require it due to architecture)
                } else if (JSON.stringify(columns) !=
                    JSON.stringify(pData.columns)) {
                    //save current data for future use
                    columns = $.extend(true, [], pData.columns);
                    rows = $.extend(true, [], pData.data);
                    //delete the table from DOM
                    table.destroy(true);
                    //generate empty html-table in the container
                    $('#datatable_test_container').append('<table id = "datatable_test" class = "display" cellspacing = "0" width = "100%" > < /table>');
                    //reinit the table
                    table = table_init(pData);
                }
                //if data changes, clear and re-draw the table
                else if (JSON.stringify(rows) != JSON.stringify(pData.data)) {
                    //save current data for future use
                    //we don't need to save the columns, they didn't change
                    rows = $.extend(true, [], pData.data);
                    //clear table, add rows from recieved JSON-object, re-
                    draw the table with new data
                    table.clear().rows.add(pData.data).draw(false);
                }
                //if nothing changes, we do nothing
            }
        }
    );
    //repeat the procedure in a second
    setTimeout(worker, 1000);
};

对于Execute when Page Loads我补充说:

$(document).ready(function() {
    worker();
});

这一切的作用:

  1. 静态内容区域中的静态 <div> 接收到一个空的 table 应用 DataTables 构造函数的位置。
  2. JavaScript 代码通过触发 Ajax 回调服务器开始工作 过程,并在成功时使用此过程返回的结果。
  3. DataTables构造函数支持不同类型的数据源,例如 它可以解析 html-table 或进行 ajax 调用,但我 首选使用 APEX 流程,然后将 table 基于 JSON-object,这个进程returns。
  4. 然后脚本监视更改。如果列发生变化,table 将被删除 来自文档并使用新数据重新初始化,如果只有行发生变化, 然后 table 只是用这个数据重新绘制。如果数据没有变化 然后脚本什么都不做。
  5. 这个过程每秒重复一次。

因此,有一个完全交互式的、动态刷新的报表,具有排序、分页、搜索、事件处理等选项。所有这些都是在客户端完成的,无需对服务器进行额外查询。

您可以使用此 live demo 检查结果(顶部区域是 DataTables 报告,其下方是源 table 上的 editable 交互式网格,以查看更改,您可以使用交互式网格更改数据)。

我不知道这是否是最好的方法,但它符合我的要求。

2017 年 9 月 5 日更新: 添加了 APEX_JSON Ajax 回调过程和 go_pivot PL/SQL 函数的列表。