有一个APEX小应用需要提供Excel数据上传功能,并且要对上传的数据进行后续加工处理,于是就有了下面这段有点通用的代码。
APEX 4.0中上传的存储位置有两个选择,一个是集中式的WWV_FLOW_FILES,一个是自定义的表。对于前者我不喜欢,所有应用上传的文件都存储在一个地方,有些时候管理起来不够方便;如果是自定义的表,每个应用单独存放,那么不论在迁移还是某些维护都相对简单,当然,对于上传功能的设计而言就需要多花几分钟时间了。下面是自定义表的一个例子。
1. 先创建存储表
| attach_data |
blob |
二进制数据 |
| attach_mimetype |
varchar2(255) |
文件类型 |
| attach_filename |
varchar2(255) |
文件名 |
| attach_last_update |
date |
上传时间 |
| attach_charset |
varchar2(128) |
字符集 |
| attach_id |
number |
文件ID |
一般而言,最好将该表最小化,包含必须的几个字段即可,如果需要和其他表关联,则可以通过ATTACH_ID关联。
2. APEX页面中增加File…上传的item,属性设置如下:

3. 添加提交表单的按钮,提交时触发一次Automatic Row Processing (DML)的Process,设置表名和主键,系统会自动将相关数据(包括上传的文件)插入对应的表中。关于主键ID的自动生成,可以在After header位置增加一个自动fetch row的设置即可。
4. 文件上传功能本身是非常简单的,接下来是针对该上传文件的后续处理。在顺序上,要先有处理上传的Process(就是第3步),然后再有后续处理的Process。手工在Page Process处再添加一个名为Parsing Attachment(名字随意)的Process,在Source处理加入匿名PL/SQL代码。例如我喜欢直接调用写好的包,那就写上:
process_upload.process_file_upload;
这里需要用户首先对Excel做一个另存为文本文件的操作,目前为止,我还没发现可以用PL/SQL来直接操作二进制Excel文件。如果有必要,可以考虑使用Java Procedure对Excel文件进行处理。
附上几段代码:
PROCEDURE process_file_upload IS
l_blob_data BLOB;
l_blob_len NUMBER;
l_position NUMBER;
l_raw_chunk RAW(2);
l_raw_line RAW(32767);
l_line VARCHAR2(32767) DEFAULT NULL;
l_array wwv_flow_global.vc_arr2;
l_first_line_flag BOOLEAN;
BEGIN
SELECT s.attach_data
INTO l_blob_data
FROM sw_detail_attachment s
WHERE s.attach_id = v('P7_ATTACH_ID');
DELETE FROM sw_detail_attachment s
WHERE s.attach_id = v('P7_ATTACH_ID');
l_blob_len := dbms_lob.getlength(l_blob_data);
l_position := 1;
l_first_line_flag := TRUE;
WHILE (l_position <= l_blob_len) LOOP
l_raw_chunk := dbms_lob.substr(l_blob_data, 1, l_position);
-- chr(10)
IF l_raw_chunk = utl_raw.cast_to_raw(chr(10)) THEN
l_line := utl_raw.cast_to_varchar2(l_raw_line);
-- remove carriage return
l_line := REPLACE(l_line, chr(13), '');
l_array := string_to_table(l_line);
IF NOT l_first_line_flag THEN
insert_data(l_array);
END IF;
l_raw_line := NULL;
l_first_line_flag := FALSE;
ELSE
l_raw_line := l_raw_line || l_raw_chunk;
END IF;
l_position := l_position + 1;
END LOOP;
END process_file_upload;
上传的文件是以TAB分割的Excel表格数据,我这里先取出文件数据放到l_blob_data中,然后针对换行符进行分割(Windows文件还需要清理掉回车符号),再对每一行使用标准函数wwv_flow_utilities.string_to_table进行解析。该函数可能存在一个问题,就是如果Excel最后一列数据为空,将导致解析后的列数缺少一列,我用下面这种方式修复该问题:
FUNCTION string_to_table(x_str IN VARCHAR2) RETURN wwv_flow_global.vc_arr2 IS
l_col_num NUMBER := 20;
l_array wwv_flow_global.vc_arr2;
BEGIN
-- CHR(9) is tab key
l_array := wwv_flow_utilities.string_to_table(x_str, chr(9));
IF l_array.count = l_col_num - 1 THEN
l_array(l_col_num) := NULL;
END IF;
RETURN l_array;
END string_to_table;
Excel数据还可以采用复制粘贴的方式直接通过表单提交上去,不过,如果Excel文件内容比较复杂,存在多个标签页,则直接上传导入Oracle数据库应该还是有相当大的需求的,如果有时间做一个配置性强的上传插件的话估计下载量会不少。
Update
APEX 4.0.1 的一个bug:如果采用非WWV_FLOW_FILES表存储上传的文件,如果用IE内核的浏览器,无法上传任何数据。