使用 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' 的值对列排序)。
或者如果我们在模型上展示上述需求:
所以问题是 - 实施这种具有一些交互和自定义样式的矩阵报告的最佳实践是什么?
我已经尝试研究的内容:
- 交互式报表数据透视表功能 (https://docs.oracle.com/cd/E71588_01/AEEUG/managing-pivot-reports.htm#AEEUG29137) - 缺乏自定义,不能很好地处理许多值,尤其是当它们不是数字时。
- 基于函数的经典报表 - 我已经实现了 PL/SQL function which returns dynamic PIVOT SQL query, in the properties of the report
Use Generic Column Names
set to Yes
(in order to parse the query only in runtime) and for headings of the report I used another PL/SQL function,它生成格式为 heading1:headning2:...:headingN
的字符串。
该解决方案有效(您可以在此处查看 - https://apex.oracle.com/pls/apex/f?p=132832:2),但我需要每隔 5 秒动态刷新一次报告,这在性能方面会很糟糕(动态 SQL 总是如果我们谈论执行计划,这是一种糟糕且难以管理的方式)。此解决方案也不适合,因为标题与数据不一致(实际上我在两个 PL/SQL 函数的查询中使用 order by col1
使标题在它们的位置)而且我不知道如何在此处使行可排序。
- PL/SQL 动态内容区域 - 我还没有尝试在这里编写代码,但我意识到只要使用 HTP 包和 APEX API 就可以在这里做任何事情。棘手的是这个解决方案非常复杂,我需要实现报告的所有逻辑 'from scratch' 我相信有更好更简单的方法来成功完成任务,我不知道。
根据您的要求,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_JSON
或 HTP
包)。来源:
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();
});
这一切的作用:
- 静态内容区域中的静态
<div>
接收到一个空的
table 应用 DataTables 构造函数的位置。
- JavaScript 代码通过触发 Ajax 回调服务器开始工作
过程,并在成功时使用此过程返回的结果。
- DataTables构造函数支持不同类型的数据源,例如
它可以解析 html-table 或进行 ajax 调用,但我
首选使用 APEX 流程,然后将 table 基于
JSON-object,这个进程returns。
- 然后脚本监视更改。如果列发生变化,table 将被删除
来自文档并使用新数据重新初始化,如果只有行发生变化,
然后 table 只是用这个数据重新绘制。如果数据没有变化
然后脚本什么都不做。
- 这个过程每秒重复一次。
因此,有一个完全交互式的、动态刷新的报表,具有排序、分页、搜索、事件处理等选项。所有这些都是在客户端完成的,无需对服务器进行额外查询。
您可以使用此 live demo 检查结果(顶部区域是 DataTables 报告,其下方是源 table 上的 editable 交互式网格,以查看更改,您可以使用交互式网格更改数据)。
我不知道这是否是最好的方法,但它符合我的要求。
2017 年 9 月 5 日更新: 添加了 APEX_JSON
Ajax 回调过程和 go_pivot
PL/SQL 函数的列表。
我需要使用 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' 的值对列排序)。
或者如果我们在模型上展示上述需求:
所以问题是 - 实施这种具有一些交互和自定义样式的矩阵报告的最佳实践是什么?
我已经尝试研究的内容:
- 交互式报表数据透视表功能 (https://docs.oracle.com/cd/E71588_01/AEEUG/managing-pivot-reports.htm#AEEUG29137) - 缺乏自定义,不能很好地处理许多值,尤其是当它们不是数字时。
- 基于函数的经典报表 - 我已经实现了 PL/SQL function which returns dynamic PIVOT SQL query, in the properties of the report
Use Generic Column Names
set toYes
(in order to parse the query only in runtime) and for headings of the report I used another PL/SQL function,它生成格式为heading1:headning2:...:headingN
的字符串。 该解决方案有效(您可以在此处查看 - https://apex.oracle.com/pls/apex/f?p=132832:2),但我需要每隔 5 秒动态刷新一次报告,这在性能方面会很糟糕(动态 SQL 总是如果我们谈论执行计划,这是一种糟糕且难以管理的方式)。此解决方案也不适合,因为标题与数据不一致(实际上我在两个 PL/SQL 函数的查询中使用order by col1
使标题在它们的位置)而且我不知道如何在此处使行可排序。 - PL/SQL 动态内容区域 - 我还没有尝试在这里编写代码,但我意识到只要使用 HTP 包和 APEX API 就可以在这里做任何事情。棘手的是这个解决方案非常复杂,我需要实现报告的所有逻辑 'from scratch' 我相信有更好更简单的方法来成功完成任务,我不知道。
根据您的要求,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_JSON
或 HTP
包)。来源:
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();
});
这一切的作用:
- 静态内容区域中的静态
<div>
接收到一个空的 table 应用 DataTables 构造函数的位置。 - JavaScript 代码通过触发 Ajax 回调服务器开始工作 过程,并在成功时使用此过程返回的结果。
- DataTables构造函数支持不同类型的数据源,例如 它可以解析 html-table 或进行 ajax 调用,但我 首选使用 APEX 流程,然后将 table 基于 JSON-object,这个进程returns。
- 然后脚本监视更改。如果列发生变化,table 将被删除 来自文档并使用新数据重新初始化,如果只有行发生变化, 然后 table 只是用这个数据重新绘制。如果数据没有变化 然后脚本什么都不做。
- 这个过程每秒重复一次。
因此,有一个完全交互式的、动态刷新的报表,具有排序、分页、搜索、事件处理等选项。所有这些都是在客户端完成的,无需对服务器进行额外查询。
您可以使用此 live demo 检查结果(顶部区域是 DataTables 报告,其下方是源 table 上的 editable 交互式网格,以查看更改,您可以使用交互式网格更改数据)。
我不知道这是否是最好的方法,但它符合我的要求。
2017 年 9 月 5 日更新: 添加了 APEX_JSON
Ajax 回调过程和 go_pivot
PL/SQL 函数的列表。