【问题标题】:Create an Excel File (.xlsx) using PL/SQL使用 PL/SQL 创建 Excel 文件 (.xlsx)
【发布时间】:2017-05-09 00:23:50
【问题描述】:

关于我之前的问题Return the SQL Statement of an Explicit Cursor, 我能够使用DBMS_SQLUTL_FILE 生成Excel (.xls) 文件并传递SYS_REFCURSOR(代码在上一个问题中)。 但是,我遇到的一个挑战是生成的文件超过 25 MB。 我将通过电子邮件将其传输给商业用户,它肯定会填满他们的邮箱大小。 当我使用 SQL Developer 手动提取到 xlsx 时,它只会生成大约 4 MB 的数据。

要解决这个问题,是否可以通过PL/SQL 执行以下操作?

  1. 使用更高版本的 Excel (.xlsx) 生成文件以压缩大小
  2. 传输前压缩.xls文件

我还在 SO 中查看过类似的帖子,例如 Writing in ExcelSheet using UTL_FILE package in Oracle, 但正如答案所述,它需要使用Java。所以它不适用于我。 另一个帖子Create an Excel Spreadsheet from a Oracle Database 也在使用xls。所以它也不适用。

有什么想法吗?

Oracle 版本:

Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
"CORE   11.2.0.4.0  Production"
TNS for Solaris: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

【问题讨论】:

  • 也许如果你关心大小,那么最好使用 csv ?
  • 嗨@Leo,是的,我现在实际上正在为.csv 运行测试。但主要是更喜欢.xlsx,因为商业用户更喜欢这种方式。
  • @Leo,不走运,.csv 文件大小几乎相同,均为 25 MB。
  • 数据的实际大小是多少?因为在我提到 csv = txt with delimiters
  • @Leo,.xls 有 25.9 MB,.csv 有 25.4 MB。尚未考虑使用带分隔符的文本。我会检查我是否可以为它创建一个程序。

标签: excel oracle plsql


【解决方案1】:

我看到了一个名为 as_xlsx 的软件包,作者是 Anton Scheffer,Create an Excel-file with PL/SQL,它解决了我的问题。 我还对其进行了一些修改,以放入工作表名称并允许 SYS_REFCURSOR 作为参数,而不是我上一篇文章 (Return the SQL Statement of an Explicit Cursor) 中要求的 VARCHAR2

我在过程重载的包规范中添加了这个:

procedure query2sheet
( p_cur             IN OUT      SYS_REFCURSOR
, p_column_headers  boolean     := true
, p_directory       varchar2    := null
, p_filename        varchar2    := null
, p_sheet           pls_integer := null
, p_sheetname       varchar2    := null
);

我在程序重载的包正文中添加了这个(注意:行 cmets 是我修改的行):

procedure query2sheet
( p_cur IN OUT SYS_REFCURSOR
, p_column_headers boolean := true
, p_directory varchar2 := null
, p_filename  varchar2 := null
, p_sheet     pls_integer := null
, p_sheetname varchar2 := null
)
is
    t_sheet     pls_integer;
    t_c         integer;
    t_col_cnt   integer;
    t_desc_tab2 dbms_sql.desc_tab2;
    t_desc_tab  dbms_sql.desc_tab;
    d_tab       dbms_sql.date_table;
    n_tab       dbms_sql.number_table;
    v_tab       dbms_sql.varchar2_table;
    t_bulk_size pls_integer := 200;
    t_r         integer;
    t_cur_row   pls_integer;
    t_d         number;
begin
    -- Changed
    if p_sheetname is not null then
        new_sheet(p_sheetname);      
    else
        new_sheet;
    end if;
    -- End of Change
    --t_c := dbms_sql.open_cursor;                       
    --dbms_sql.parse( t_c, p_sql, dbms_sql.native );

    t_d := DBMS_SQL.TO_CURSOR_NUMBER(p_cur);

    --dbms_sql.describe_columns2( t_c, t_col_cnt, t_desc_tab );
    dbms_sql.describe_columns( t_d, t_col_cnt, t_desc_tab );

    for c in 1 .. t_col_cnt
    loop
        if p_column_headers
        then
        cell( c, 1, t_desc_tab( c ).col_name, p_sheet => t_sheet );
        end if;
        --dbms_output.put_line( t_desc_tab( c ).col_name || ' ' || t_desc_tab( c ).col_type );
        case
        when t_desc_tab( c ).col_type in ( 2, 100, 101 )
        then
            --dbms_sql.define_array( t_c, c, n_tab, t_bulk_size, 1 );
            dbms_sql.define_array( t_d, c, n_tab, t_bulk_size, 1 );
        when t_desc_tab( c ).col_type in ( 12, 178, 179, 180, 181 , 231 )
        then
            --dbms_sql.define_array( t_c, c, d_tab, t_bulk_size, 1 );
            dbms_sql.define_array( t_d, c, d_tab, t_bulk_size, 1 );
        when t_desc_tab( c ).col_type in ( 1, 8, 9, 96, 112 )
        then
            --dbms_sql.define_array( t_c, c, v_tab, t_bulk_size, 1 );
            dbms_sql.define_array( t_d, c, v_tab, t_bulk_size, 1 );
        else
            null;
        end case;
    end loop;
    --
    t_cur_row := case when p_column_headers then 2 else 1 end;
    t_sheet := nvl( p_sheet, workbook.sheets.count() );
    --
    --t_r := dbms_sql.execute( t_c );
    loop
        --t_r := dbms_sql.fetch_rows( t_c );
        t_r := dbms_sql.fetch_rows( t_d );
        if t_r > 0
        then
        for c in 1 .. t_col_cnt
        loop
            case
            when t_desc_tab( c ).col_type in ( 2, 100, 101 )
            then
                --dbms_sql.column_value( t_c, c, n_tab );
                dbms_sql.column_value( t_d, c, n_tab );
                for i in 0 .. t_r - 1
                loop
                if n_tab( i + n_tab.first() ) is not null
                then
                    cell( c, t_cur_row + i, n_tab( i + n_tab.first() ), p_sheet => t_sheet );
                end if;
                end loop;
                n_tab.delete;
            when t_desc_tab( c ).col_type in ( 12, 178, 179, 180, 181 , 231 )
            then
                --dbms_sql.column_value( t_c, c, d_tab );
                dbms_sql.column_value( t_d, c, d_tab );
                for i in 0 .. t_r - 1
                loop
                if d_tab( i + d_tab.first() ) is not null
                then
                    cell( c, t_cur_row + i, d_tab( i + d_tab.first() ), p_sheet => t_sheet );
                end if;
                end loop;
                d_tab.delete;
            when t_desc_tab( c ).col_type in ( 1, 8, 9, 96, 112 )
            then
                --dbms_sql.column_value( t_c, c, v_tab );
                dbms_sql.column_value( t_d, c, v_tab );
                for i in 0 .. t_r - 1
                loop
                if v_tab( i + v_tab.first() ) is not null
                then
                    cell( c, t_cur_row + i, v_tab( i + v_tab.first() ), p_sheet => t_sheet );
                end if;
                end loop;
                v_tab.delete;
            else
                null;
            end case;
        end loop;
        end if;
        exit when t_r != t_bulk_size;
        t_cur_row := t_cur_row + t_r;
    end loop;
    --dbms_sql.close_cursor( t_c );
    dbms_sql.close_cursor( t_d );
    if ( p_directory is not null and  p_filename is not null )
    then
        save( p_directory, p_filename );
    end if;
exception
when others
then
    --if dbms_sql.is_open( t_c )
    if dbms_sql.is_open( t_d )
    then
    --dbms_sql.close_cursor( t_c );
    dbms_sql.close_cursor( t_d );
    end if;
end query2sheet;

这是创建文件的并发请求中的示例块:

Procedure EMP_ROSTER_REPORT (p_empno        per_all_people_f.employee_number%type                              
                           , p_bg_id        per_business_groups.business_group_id%type
                           , p_email_add    per_all_people_f.email_address%type)
is

    l_fh            UTL_FILE.FILE_TYPE;
    l_directory     VARCHAR2(30) := 'EXT_TAB_DATA';
    l_filename      VARCHAR2(100);
    emp_cur         SYS_REFCURSOR;
    l_message       varchar2(100);
    g_stage         varchar2(100);
    g_zipped_blob   blob;

    cursor  p_payroll_cur is
    select  payroll_id
        ,   payroll_name
        ,   business_group_id
    from    pay_all_payrolls_f
    where   business_group_id = p_bg_id;

BEGIN

    -----------------------------------
    g_stage := 'setting the filename';
    -----------------------------------

    l_filename := 'EMPLOYEE_ROSTER_REPORT_'||TO_CHAR(SYSDATE, 'DD-MON-YYYY-HHMISS');

    ------------------------------------------
    g_stage := 'Assigning Emp SysRefCursor';
    ------------------------------------------

    for i in p_payroll_cur loop

        OPEN emp_cur FOR
        SELECT  'extra long query here with parameters'
        from    table_a
        where   payroll_id = i.payroll_id;

        ----------------------------------------------------------
        g_stage := 'open Employee Cursor and write into the File';
        ----------------------------------------------------------

        as_xlsx.query2sheet( p_cur          => emp_cur            -- Uses Sys_RefCursor Instead of Dynamic SQL (Varchar2)
                           , p_sheetname    => i.payroll_name);   -- This is where we assign the Sheet Names         
        as_xlsx.freeze_pane( 1,1 );                               -- Freeze the topmost and rightmost pane in the Excel Sheet

    end loop;

    ------------------------------
    g_stage := 'Create the File';
    ------------------------------

    as_xlsx.save( l_directory , l_filename||'.xlsx');

END EMP_ROSTER_REPORT;

希望这对某人有所帮助! :)

【讨论】:

  • 当你创建你的 XLSX 文件时,它存储在哪里?在数据库所在的远程机器上?如何使用 UTL_MAIL 发送这个创建的文件?
  • @sbrbot 是的,它位于数据库所在的远程计算机上。有很多 UTL_MAIL 包可以作为附件发送。
  • 我最大的问题是 UTL_MAIL 在附件大小(32K 或类似的东西)方面非常有限。所以我不能发送更大的文件,现在如果附件保存在外部,我可以使用 UTL_MAIL 发送它吗?
  • 这个包的依赖是什么?还是独立的?
  • @ManuelJE 这是一个独立的包
【解决方案2】:

支持 Oracle 数据库中的 xlsxlsx 文件。

您可以做的是创建 CSV(逗号分隔)文件,然后使用使用 java.util.zip 的自定义 java stored procedure 或使用 UTL_COMPRESS 的 PL/SQL 过程将其压缩为 zip 文件。

【讨论】:

  • 嗨@gurwinder-singh,我能够找到满足需求的本机PL/SQL 包。请在下面查看我的答案。谢谢!
【解决方案3】:

如果您需要生成 XLSX 文件并将其发送给业务用户,这听起来更像是 DB 之外的某些服务的工作。您应该准备返回ref cursor 和报告内容的程序,然后设置一些消费数据的服务,生成PDF 或XLSX 并发送。

如果您不能在 DB 之外执行此操作,您仍然可以在 Oracle 中使用 Java。您可以创建将创建 XLSX 的 Java 过程。 Here 是 Java 创建 Excel 文件的示例。但是创建复杂的 Java 程序并不是最好的解决方案,可能需要您在 DB 服务器上安装一些 jar,因此我将创建返回 ref cursor 的程序,其中包含数据和创建文件并将其发送到 DB 外部的小程序。

【讨论】:

  • 嗨@kacper,是的,这是我在上一篇文章stackoverflow.com/questions/41281665/… 中采用的方法,但是,我在文件大小和行限制方面遇到了挑战。我通过 Oracle EBS 中的并发程序发送这些报告。
  • 嗨@kacper,我找到了解决方案,请看看我在这个问题中的回答。我还对指定的包进行了一些修改,并且能够将SYS_REFCURSOR 作为参数传递。
猜你喜欢
  • 2017-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多