Oracle отчеты в excel

No matter how user-friendly the expensive BI tool ( or the dirt-cheap Application Express for that matter), sometimes, only Excel will do for your users.
No, you can’t get away with giving them a CSV and telling them to open it in Excel.
Nor can you avail yourself of SQLDeveloper’s ability to export result sets in XLSX format.
They must have their report in Excel, ready and waiting first thing in the morning.

This leads you to ask the question, “How do you generate Excel Spreadsheets from PL/SQL ?”

The answer is, of course, that you fire up your favourite search engine and hope that it’ll land on this work of genius by Anton Scheffer.

Incidentally, if you use Morten Braten’s Alexandria PL/SQL Library, you will already have a version of Anton’s package in the form of XLSX_BUILDER_PKG.

In the example that follows, the report we need to produce is based on the HR schema.

As we’re producing a report in Excel, we may as well utilise the main advantage of an xlsx file over a delimited text file and create multiple worksheets…

The Report

The report will run on an Oracle 18c XE database (mainly because I’ve got one handy with the HR schema already installed).

Regular readers will know that use Ubuntu at home. This means that LibreOffice Calc – rather than Excel – is my spreadsheet of choice. That said, I’ve checked that the spreadsheet renders correctly in both Calc, and Excel itself, thanks to the freely available online version provided by Microsoft.
It’s worth noting that, although Calc can read xlsx formatted files, there are some minor differences in how they are rendered. This may also be the case with a desktop version of Excel as compared to the Web version that I’m using here.

Our report will consist of three worksheets :

  • a title – including the parameter value used to generate the report
  • a summary – a listing of total salary by job within the Department
  • a detail tab – listing of all of the employees in the Department

Before we start, we need a directory to write the xlsx file out to.
In this instance, we have a directory object called APP_DIR on which we have write privileges.

create or replace directory app_dir as '/opt/oracle/appdir';

grant read, write on directory app_dir to hr;

We also need to deploy the package, which we’ve downloaded from the link above.

Note that Anton has included a number of coding examples in the header comments, which provide some guidance as to how the package works.

The code I’ve written to generate the report is in a package called salary_by_dept_report. The header looks like this :

create or replace package salary_by_dept_rpt
as

    GC_DIR constant all_directories.directory_name%type := 'APP_DIR';
    
    -- Package members to generate the individual worksheets in the report workbook.
    -- These would normally be private.
    procedure title_sheet( i_dept in departments.department_id%type);
    procedure job_summary_sheet(i_dept in departments.department_id%type);
    procedure detail_sheet(i_dept in departments.department_id%type);
    
    -- Main entry point - this is the procedure to create the report itself.
    procedure run_report( i_dept in departments.department_id%type);
end salary_by_dept_rpt;
/

Let’s take a look at each of the package members in turn.

The Main Procedure ( run_report)

The code is :

procedure run_report( i_dept in departments.department_id%type)
is
    v_fname varchar2(250);
begin
    v_fname := 'salary_report_dept_'||i_dept||'.xlsx';
    -- Ensure we have nothing hanging around from a previous run in this session
    as_xlsx.clear_workbook;

    -- For the query2sheet call to work where it's not the first sheet or
    -- any other sheet preceeding it is not being created using this call,
    -- you need to define all of the sheets upfront.
    -- Otherwise, it will only write a single integer to that sheet.
    as_xlsx.new_sheet('Title');
    as_xlsx.new_sheet('Summary by Job');
    as_xlsx.new_sheet('Department Employees');

    -- Call each procedure in turn to populate the worksheets
    title_sheet( i_dept);
    job_summary_sheet(i_dept);       
    detail_sheet(i_dept);
    
    as_xlsx.save( GC_DIR, v_fname);        
        
end run_report;    

Once we’ve dealt with the admin of determining the name of the file we want to create, we come to our first interaction with AS_XLSX :

as_xlsx.clear_workbook

This ensures that we don’t have any settings hanging around from a previous execution of this package in our session.

The next step is to create all of the worksheets we’re going to populate for this report.
This is because we’ll be populating the last of these ( Department Employees ) using as_xlsx.query2sheet. We’ll look at that call in more detail a little later.

I’m mentioning it now because, if you use this call after populating any workbooks not using it, it does not seem to work as expected, as you can see from the comments in the procedure.

To create a new worksheet :

as_xlsx.new_sheet("My New Worksheet");

Naming the worksheet, as I’ve done here, is optional.
In calls to AS_XLSX, worksheets are referred to by their number – in this case, Title is 1, “Summary by Job” is 2 and “Department Employees” is 3.

The final part of this procedure is fairly self-explainatory – i.e. call the procedures to populate their respective worksheets before saving the entire file to disc by calling :

as_xlsx.save(directory object, filename);

Generating the Title sheet

The code for this procedure is :

procedure title_sheet( i_dept in departments.department_id%type)
is 
    v_dept_name varchar2(250);
begin
    
    -- the value of width appears to be 1/10th of the default width of a column.
    -- Default is 2.47 cm (24.7 mm), which makes 1 2.47 mm
    -- For reference, the title of the report is 27 characters
    as_xlsx.set_column_width( 
        p_col => 1,
        p_width => 25,
        p_sheet => 1);

    -- Print the Report Title in bold in cell A1 
    as_xlsx.cell(
        p_col => 1,
        p_row => 1,
        p_sheet => 1,
        p_value =>  'Salary by Department Report', 
        p_fontid => as_xlsx.get_font('Arial', p_bold => true),
        p_numFmtId =>  0);

    -- Detail the parameters used to generate the report
    -- Section label in A2
    as_xlsx.cell( 1,2, 'Parameters :', p_sheet => 1, p_numFmtId =>  0);
               
    -- Parameter Name in A3
    as_xlsx.cell(1,3, 'Department', p_sheet => 1, p_numFmtId =>  0);
    
    -- Parameter value and Department name in B3
    select department_id||' ( '||department_name||')'
    into v_dept_name
    from departments
    where department_id = i_dept;
    
    as_xlsx.cell(2,3, v_dept_name, p_sheet => 1, p_numFmtId => 0);

    -- Run Date label in A4
    as_xlsx.cell( 1, 4, 'Report Run Date', p_sheet => 1, p_numFmtId =>  0);

    -- Run Date in B4
    as_xlsx.cell( 2, 4, sysdate, p_sheet => 1, p_numfmtid => as_xlsx.get_numfmt('dd/mm/yyyy h:mm'));

end title_sheet;

We begin by setting the column width.

Note that columns are referred by by their numeral position in the worksheet ( A is 1, B is 2 etc).

Next, we finally get to put some data directly into cells in the worksheet.

as_xlsx.cell(
            p_col => 1,
            p_row => 1,
            p_sheet => 1,
            p_value =>  'Salary by Department Report', 
            p_fontid => as_xlsx.get_font('Arial', p_bold => true),
            p_numFmtId =>  0);

The p_numFmtId is specified here for the purposes of consistency.
This is because, if your workbook contains multiple worksheets, then calls to this procedure in second and subsequent worksheets will cause the following error to be raised if this parameter is not specified :

ORA-1403 no data found at line 724 of AS_XLSX.

Once the workbook is created, the Title worksheet looks like this :

Summary Sheet

Here we’re going to fetch some data in a for loop and apply some conditional formatting. There are even a couple of formulas thrown in for good measure :

procedure job_summary_sheet(i_dept in departments.department_id%type)
is
    v_row pls_integer := 1; -- need to account for the header row
    v_tot_row pls_integer;
    v_formula varchar2(4000);
    v_ave_sal number;
    v_sal_mid_range number;
    
    v_rgb varchar2(25);
begin

-- Set the column widths to allow for the header lengths
as_xlsx.set_column_width( p_col => 1, p_width => 15, p_sheet => 2);
as_xlsx.set_column_width( p_col => 2, p_width => 15, p_sheet => 2);
as_xlsx.set_column_width( p_col => 3, p_width => 15, p_sheet => 2);
as_xlsx.set_column_width( p_col => 4, p_width => 15, p_sheet => 2);

-- Set the column headings
-- NOTE - for second and subsequent sheets, we need to specify the number format or else we'll get
-- ORA-1403 no data found at line 724 of AS_XLSX.
-- Specifying the sheet number as it seems sensible to do so at this point !
as_xlsx.cell(1,1, 'Department Name', p_sheet => 2, p_numFmtId =>  0);
as_xlsx.cell(2,1, 'Job Title', p_sheet => 2, p_numFmtId =>  0);
as_xlsx.cell(3,1, 'No of Employees', p_sheet => 2, p_numFmtId =>  0);
as_xlsx.cell(4,1, 'Total Salary', p_sheet => 2, p_numFmtId =>  0);

-- Retrive the data rows and populate the relevant cells, applying any
-- conditional formatting.
for r_jobs in (
    select dept.department_name, job.job_title, 
        count(emp.employee_id) as employees,
        sum(emp.salary) as total_salary,
        job.min_salary, job.max_salary
    from employees emp
    inner join departments dept
        on dept.department_id = emp.department_id
    inner join jobs job
        on job.job_id = emp.job_id
    where dept.department_id = i_dept    
    group by dept.department_name, job.job_title,
    job.min_salary, job.max_salary
    order by job.max_salary desc, job.job_title)
loop
    v_row := v_row + 1;
    as_xlsx.cell(1, v_row, r_jobs.department_name, p_numFmtId =>  0, p_sheet => 2);
    as_xlsx.cell(2, v_row, r_jobs.job_title,  p_numFmtId =>  0, p_sheet => 2);
    as_xlsx.cell(3, v_row, r_jobs.employees,  p_numFmtId =>  0, p_sheet => 2);
    
    -- If the average salary is lower than the mid-range for this job then
    -- display in red because someone needs a pay rise !
    -- Otherwise, display in green
    v_ave_sal := r_jobs.total_salary / r_jobs.employees;
    v_sal_mid_range :=  r_jobs.min_salary + ((r_jobs.max_salary - r_jobs.min_salary) / 2);
    if v_ave_sal > v_sal_mid_range then
        v_rgb := '009200'; -- green
    else    
        v_rgb := 'ff0000'; -- red
    end if;
    as_xlsx.cell(4, v_row, r_jobs.total_salary, 
        p_fontId => as_xlsx.get_font( 'Arial', p_rgb => v_rgb ),
        p_numFmtId => 0, 
        p_sheet => 2);
        
end loop;
    if v_row = 1 then
        raise_application_error(-20001, 'No data returned from query');
    end if;    
    v_tot_row := v_row + 1;
    as_xlsx.cell(1, v_tot_row, 'Total', p_numFmtID => 0, p_sheet => 2);
    -- Number of employees
    v_formula := '=SUM(C2:C'||v_row||')';
    -- Number format requried to avoid ORA-1403
    as_xlsx.num_formula( 3, v_tot_row, v_formula,  p_numFmtID => 0, p_sheet => 2);
    
    -- Total Salary
    v_formula := '=SUM( D2:D'||v_row||')';
    as_xlsx.num_formula( 4, v_tot_row, v_formula,  p_numFmtID => 0, p_sheet => 2);

end job_summary_sheet;

Whilst you may prefer to to do most of your data wrangling in SQL, it’s worth knowing that you can throw in an Excel formula should you need to.

In this example I’ve used the call :

as_xlsx.num_formula(column, row, formula, numberFormat, sheet no);

Excel formula generated from PL/SQL. It’s legal, but is it moral ?

Detail Sheet – Data from a Query

Here, we get to make use of another recent addition to the package. Whilst it’s long had the ability to generate data in a worksheet directly from a SQL query, the package can now accept a Ref Cursor for this purpose :

procedure detail_sheet(i_dept in departments.department_id%type) is
    v_rc sys_refcursor;
begin 
    -- Format the column headings in the ref cursor query
    open v_rc for 
        select emp.employee_id as "Employee ID", 
            emp.first_name as "First Name", 
            emp.last_name as "Last Name",
            job.job_title as "Job Title", 
            emp.salary as "Salary"
        from employees emp
        inner join jobs job
            on job.job_id = emp.job_id
        where emp.department_id = i_dept;
    
    as_xlsx.query2sheet( v_rc, p_sheet => 3);
    
    -- Any formatting gets overwritten by the query2sheet so
    -- we need to do it afterwards
    as_xlsx.set_column_width( p_col => 1, p_width => 15, p_sheet => 3);
    as_xlsx.set_column_width( p_col => 2, p_width => 15, p_sheet => 3);
    as_xlsx.set_column_width( p_col => 3, p_width => 15, p_sheet => 3);
    as_xlsx.set_column_width( p_col => 4, p_width => 15, p_sheet => 3);
    -- Leave salary as is

end detail_sheet;

Here, I’ve taken the opportunity to format the column headings in the query itself.

The sheet looks like this :

Some Observations on Performance

In the current Office365 version, the maximum number of rows an Excel Worksheet can hold is just over a million ( 1,048,576 to be precise). Therefore, this approach is not really suitable for large datasets.
It’s probably not that surprising therefore, that I haven’t experienced too many issues with performance when using this package.
I have occasionally found significant runtime differences between environments for the same report with the same data.
I suspect this may have been due to the resources allocated to the PGA on the respective environments but I have no specific evidence to support this.
I thought it was worth mentioning however, because a report that ran in about 5 minutes on one environment took several hours on another. If you come across something similar, that may be one area that would bear investigating.

Source Code

I’ve uploaded a copy of the version of AS_XLSX I used in this post, together with the source for the salary_by_dept_rpt package to this github repo.

And finally…

I’d just like to say a big “thank you” to Anton, who, through the medium of this package, has made a number of my users extremely happy over the years !

3 / 1 / 5

Регистрация: 13.12.2013

Сообщений: 68

1

22.03.2017, 17:41. Показов 10211. Ответов 4


Студворк — интернет-сервис помощи студентам

Добрый день!
Подскажите пожалуйста как максимально просто выгрузить данные из таблицы Оракла в эксель. Обязательное условие: делать это через процедуру или функцию. Например имеется таблица ТОВАР c полями код, название и цена. Требуется в итоге получить файл с такой структурой:

код | название | цена
102 Товар А 200



0



Programming

Эксперт

94731 / 64177 / 26122

Регистрация: 12.04.2006

Сообщений: 116,782

22.03.2017, 17:41

4

761 / 662 / 195

Регистрация: 24.11.2015

Сообщений: 2,158

22.03.2017, 18:21

2

Можно поискать в интернете пакеты AS_XLSX и AS_READ_XLSX. Первый пишет, а второй читает файл *.xlsx . Сделать можно не все, но достаточно много. Есть некоторые баги (если очень большой набор данных) поэтому я переделывал (один из пакетов точно) с varchar2 на clob. Но для небольшого количества столбцов работают.

Добавлено через 1 минуту
Якобы в 12 версии есть готовый драйвер для подключения внешних excel-файлов. Но у меня нет 12 версии.



0



93 / 71 / 33

Регистрация: 02.08.2015

Сообщений: 202

23.03.2017, 05:56

3

Здравствуйте!
Я в своё время на VBS писал утилиту выгрузки данных из базы и запись в файл MS Excel.
Советую посмотреть в эту сторону. Это может расширить круг решаемых задач.

Можете посмотреть объекты:
Создание/чтение/запись файла — Scripting.FileSystemObject
Работа с Excel форматом — Excel.Application
Подключение к Oracle — ADODB.Connection



0



761 / 662 / 195

Регистрация: 24.11.2015

Сообщений: 2,158

23.03.2017, 10:47

4

Цитата
Сообщение от orion2014
Посмотреть сообщение

Подключение к Oracle — ADODB.Connection

Я так понимаю, что Вы на клиентском компьютере запускаете Вашу утилиту и качаете данные из одной бочки в другую (из Oracle в Excel). (Возможно, понимаю неверно.)
Но есть еще вариант, когда Вы запускаете сессию в Oracle, и у Вас нет в принципе клиентского компьютера. Как быть в этом случае?



0



Бакалым

3 / 1 / 5

Регистрация: 13.12.2013

Сообщений: 68

23.03.2017, 17:43

 [ТС]

5

Решено. нашел в интернете пакет as_xlsx
Автор пакета: Anton Scheffer
** Date: 19-02-2011

на базе него написал процедуру:

SQL
1
2
3
4
5
6
7
CREATE OR REPLACE PROCEDURE save_log_inexcel( p_datas DATE, p_datae DATE) AS
file_name varchar2(100);
BEGIN
SELECT 'log_work_'||p_datas||'_'||p_datae||'.xlsx' INTO file_name FROM dual;
as_xlsx.query2sheet( 'select  DATA_TIME,   EVENT,   USER_ID,   USER_NAME,   WDATA,   WTIKET_NUMBER,   WCLIENT_COD,   from V_WORKS_LOG where wdata between '''||p_datas||'''  and '''||p_datae||''' ' );
as_xlsx.save( 'DIR1', file_name);
END;



0



2004 г.

Oracle Forms. Экспорт данных в Excel

Павел Лузанов Источник: сайт П.Лузанова, 01 Ноября 2003г.,

http://www.geocities.com/luzanovp/frm2xls.html,
Oracle Magazine RE ,Январь/Февраль 2004

Как и следует из названия, речь пойдёт о создании утилиты для экспорта данных
из приложений Oracle Forms в MS Excel.

  • Введение или Почему пользователя так любят Excel
  • Постановка задачи. Требования и допущения
  • Технические аспекты решения
  • Решение
  • Приложение

Введение или Почему пользователя так любят Excel

Ну почему пользователи так любят Excel!

Почему, после того как им установили новую систему (разработанную
собственными силами или купленную) они непременно хотят получить данные в Excel.

Казалось бы, в системе предусмотрено множество отчетных форм (причем по
количеству и внешнему виду согласованные с этими же пользователями!), но они всё
равно «хотят Excel». Им, видите ли, так привычнее. Издеваются, не иначе :-)

Однако попробуем разобраться, для чего же вот этот Excel может быть
действительно полезен.

В любой системе есть потребность в так называемых нерегламентированных
отчетах. Т.е. в таких отчетах, которые заранее не предусмотрены в системе в
связи с тем, что требования к ним заранее невозможно формализовать (какие данные
требуются, в каком виде, в какое время…)

Каким образом, пользователь может сформулировать, какие данные ему нужны? Да
во многих случаях, он просто будет исходить из того, как он видит эти данные в
экранных формах. Учитывая, что экранные формы Oracle Forms, как правило,
позволяют выполнять нерегламентированные запросы (режим ввода запроса), а
экранных форм в серьёзной системе не мало, то получается, что данные, которые
пользователь может получить в экранных формах, представляют собой солидный
источник данных для нерегламентированных отчетов.

Исходя из этого, экспорт данных из экранной формы в Excel выглядит весьма
перспективно. Ведь чтобы не говорили противники Microsoft, Excel, действительно,
весьма удобный продукт для обработки табличных данных.

Это особенно актуально для такой системы как Oracle Applications (Oracle
E-Business Suite), где подавляющее большинство экранных форм используют
однозаписные блоки данных, где в каждый момент времени на экране находится
только одна запись, а работа с формой начинается с ввода и выполнения запроса.
Желание посмотреть на все отобранные записи в виде списка — весьма законное!

Еще одной распространенной задачей, которую мог бы решить экспорт данных в
Excel, является печать справочных данных (справочников). В любой системе
существует большое количество различного рода справочников. Экранные формы для
ввода в них данных — всегда присутствуют (иначе, как данные будут попадать в
систему), а вот про печать справочных данных частенько забывают.

Итак, возможность экспорта данных из экранных форм в Excel, не такая уж и
плохая идея. Она позволяет решить проблемы с выводом на печать справочников, с
просмотром в виде списка данных из однозаписных блоков Oracle Forms и в какой-то
степени помогла бы решить проблему с нерегламентированными отчетами.

Осталось лишь определиться, как же эту возможность реализовать.

Постановка задачи. Требования и допущения

После того как мы определились с целями и прежде чем бросаться в бой (пардон,
в программирование) необходимо четко сформулировать требования к предстоящей
реализации. Таковыми стали.

Требования
Совместимость с последующими версиями Forms
Разработка велась для версии 6i, но с «прицелом» на возможный переход на
9i(10G)
Независимость от платформы и способа эксплуатации
Утилита должна работать как для режима клиент-сервер, так и для
эксплуатации через Forms Server, причем не важно на какой платформе: будь то
Windows или *nux Единственное условие, это наличие на клиентском компьютере MS
Excel (а иначе куда экспортировать? :-)
Универсальность
Утилита должна работать для всех существующих форм и для всех форм,
которые будут написаны в будущем

Допущения

Речь не идет о разработке генератора отчетов
Говоря об экспорте данных, имеется в виду именно экспорт данных, а не
создание готового отчета с заголовками, формулами, красивым форматированием.
Не нужно думать о пользователях плохо. Получив «сырые» данные в Excel они
смогут в течении пары минут «раскрасить» их до готового отчета.
Передача данных обратно из Excel в Forms — не требуется
В противном случае непонятно зачем же нам нужен Forms, если данные могут
корректироваться в Excel. Forms становится просто не нужной прослойкой между
БД и Excel.

Но, как говорится, лучше один раз увидеть. Так вот, нам нужна утилита которая
позволяет, имея вот это:

В результате получить вот это:

Технические аспекты решения

Итак, теперь нужно выбрать конкретный вариант реализации.

Существует два базовых подхода к созданию файлов Excel из внешних приложений.

Первый подразумевает использование «родных» технологий Microsoft (DDE, OLE,
VBX, ActiveX, …). И Forms6i позволяет их использовать, но этот вариант не
годится из-за требования совместимости с последующими версиями Forms. А, как
известно, в версии 9i пакетов VBX, OLE2, DDE больше нет.

Второй подход заключается в том, чтобы создать файл в формате, который MS
Excel сможет прочитать. Такими форматами являются: html, csv|tsv (comma|tab
separated values).

Вот этим то мы и воспользуемся! Из предложенных форматов выбираем tsv (html
требует больше места, а «,» может встречаться в данных).

Экспортировать будем данные текущего блока формы (в котором находится
курсор). Именно это позволит сделать утилиту универсальной. Ведь всегда можно
программно определить первый, последний, следующий, предыдущий элементы текущего
блока и перемещаться по записям вне зависимости от того, в каком блоке, какой
формы, какого приложения Oracle Forms мы находимся!

Другое дело, что не всегда нужно экспортировать значения всех элементов блока
(например, не нужно экспортировать невидимые пользователю элементы), поэтому,
было принято решение о том, что экспортироваться будут только те элементы, для
которых справедливо:

  • тип элемента один из TEXT ITEM, DISPLAY ITEM, LIST, CHECKBOX, RADIO
    GROUP
  • элемент отображается на какой-либо канве и имеет свойства VISIBLE,
    DISPLAYED
    установленными в TRUE

В качестве первой строки экспортного файла было бы не плохо выгружать
заголовки (PROMPT) элементов, тогда пользователь сможет визуально оценить какому
элементу в блоке формы, соответствует какой столбец в таблице Excel. При таком
подходе, это не всегда очевидное соответствие, ведь элементы могут располагаться
даже в разных окнах одной формы.

Если вы используете общее меню для всего приложения, то для вызова утилиты
удобнее всего создать специальный пункт меню «Экспорт в Excel». При желании его
можно вывести как иконку в панель инструментов (Visible In Horizontal Menu
Toolbar = Yes).

Для формирования файлов будем пользоваться пакетом TEXT_IO. Сами файлы должны
иметь расширение xls для того, чтобы их можно было сразу открыть:

host('cmd /C start excel filename.xls');

Но это будет работать только для клиент-серверной эксплуатации. Как же быть с
Forms Server, да и еще скажем на Solaris? Ведь TEXT_IO будет писать на сервер
приложения, а не на клиентскую машину.

Здесь, опять же, возможны варианты.

Счастливые обладатели Forms9i могут попробовать новую утилиту
WebUtil
для доступа к клиентскому компьютеру. Хотя ни что не мешает вам написать, что-то
подобное на Java и в версии Forms6i.

А можно пойти и другим путем.

Будем исходить из того, что раз мы используем Forms Server, значит здесь же
имеется работающий http сервер (Apache). Мы можем написать небольшой cgi скрипт,
который будет брать выгружаемый из Forms файл и выдавать его содержимое
пользователю, предварительно указав, что это файл в формате Excel:

----- Start script --------------------------------------
#!/bin/sh
echo Content-type: application/vnd.ms-excel
echo
cat /tempdata/$1
rm -f /tempdata/$1
----- End script -------------------------

Если назвать такой скрипт exp2xls и положить в каталог
$ORACLE_HOME/Apache/Apache/cgi-bin , то вызывать его из Forms можно
примерно так:

WEB.SHOW_DOCUMENT('/cgi-bin/exp2xls' || '?' ||
filename.xls, '_blank');

Те, кто используют Windows в качестве OS для сервера приложений, думаю, без
труда смогут переписать этот скрипт в bat или cmd
файл. Нужно лишь заменить cat на type и rm
-f
на del

Имя экспортного файла в такой многопользовательской среде лучше генерировать
произвольно. Для этого можно воспользоваться генератором случайных чисел.

Остается лишь позаботиться, чтобы Forms и cgi-скрипт «договорились» куда
(каталог) один будет выкладывать файл и откуда второй его будет брать.

А пользовательский браузер понимает, что для показа такой страницы нужно
запускать Excel.

Нужно заметить, что у данного способа есть один недостаток. Ведь
теоретически, для того, чтобы получить в браузере excel-файл, и, соответственно,
получить доступ к данным, необязательно заходить в систему. Можно просто открыть
браузер и пытаться подбирать url таким образом, чтобы угадать имя временного
файла. Это, конечно, маловероятно, но, тем не менее, стоит подумать, допустим ли
для вас такой вариант.

Решение

В принципе, у вас есть уже всё, что необходимо для реализации задуманного. И
написать подобную утилиту вы наверняка сможете сами. И возможно даже лучше чем
то, что предложено ниже.

Собственно решением является plsql пакет fp_exp с единственной
public процедурой to_excel, которая вызывается без всяких
параметров, например, в пункте меню «Экспорт в Excel»:

BEGIN
   fp_exp.to_excel;
END;

Перед началом использования нужно выполнить предварительную настройку пакета,
установив значения константам, находящимся в спецификации пакета.

Утилита изначально разрабатывалась для использования с Forms Server,
работающим под Linux. Затем была перенесена в среду Oracle Applications (та же
архитектура, но под SUN Solaris). Поэтому приводится вариант пакета для web
реализации. Для работы в клиент-сервер, тело процедуры to_excel
нужно лишь чуть-чуть подправить, что не должно вызвать у вас затруднений.

PACKAGE fp_exp
/*
-- fp_exp  15-JUL-2003 by pal (Pavel Luzanovv)
-- НАЗНАЧЕНИЕ
--  Экспорт данных текущего блока в MS Excell
-- МОДИФИКАЦИЯ
--  15-JUL-2003 pal  Создание
*/
IS
   -- На что заменять символ табуляции в
значении элемента формы chr9subst_pc CONSTANT VARCHAR2(3) := ' '; -- На что заменять символ перевода строки
в значении элемента формы chr10subst_pc CONSTANT VARCHAR2(1) := ' '; -- В каком формате выводить элементы типа даты date_format_pc CONSTANT VARCHAR2(8) := 'DD.MM.YY'; -- каталог на сервере приложения, где
будут создаваться файлы экспорта tempdir_pc CONSTANT VARCHAR2(255) := '/oraapp/tempdata/'; -- относительный путь до cgi-скрипта,
для использования -- в WEB.SHOW_DOCUMENT cgi_script_pc CONSTANT VARCHAR2(255) :=
'/cgi-bin/exp2xls'; PROCEDURE to_excel; END fp_exp; PACKAGE BODY fp_exp IS TYPE item_rectype IS RECORD ( name VARCHAR2(30), prompt VARCHAR2(255), datatype VARCHAR2(255) ); TYPE item_tabtype IS TABLE OF item_rectype
INDEX BY BINARY_INTEGER; item_tab item_tabtype; /* --------- LOCAL MODULES --------- */ PROCEDURE show_progress (message_in IN VARCHAR2) IS BEGIN MESSAGE(message_in, NO_ACKNOWLEDGE); SYNCHRONIZE; END show_progress; FUNCTION tempfilename RETURN VARCHAR2 IS BEGIN RETURN (USER||DBMS_RANDOM.STRING('U', 10)); END tempfilename; FUNCTION setup_items (blk_in IN VARCHAR2)
RETURN PLS_INTEGER /* Определим находящиеся на экране
столбцы текущего блока */ IS cur_item_v VARCHAR2(30); last_item_v VARCHAR2(30); index_v PLS_INTEGER := 0; BEGIN cur_item_v := GET_BLOCK_PROPERTY (blk_in, FIRST_ITEM); last_item_v := GET_BLOCK_PROPERTY (blk_in, LAST_ITEM); item_tab.DELETE; LOOP IF GET_ITEM_PROPERTY (blk_in||'.'||
cur_item_v, DISPLAYED) = 'TRUE' AND GET_ITEM_PROPERTY (blk_in||'.'||
cur_item_v, VISIBLE) = 'TRUE' AND GET_ITEM_PROPERTY (blk_in||'.'||
cur_item_v, ITEM_CANVAS) IS NOT NULL AND GET_ITEM_PROPERTY (blk_in||'.'||
cur_item_v, ITEM_TYPE) IN ('TEXT ITEM', 'LIST',
'DISPLAY ITEM','CHECKBOX', 'RADIO GROUP') THEN index_v := index_v + 1; item_tab(index_v).name :=
GET_ITEM_PROPERTY(blk_in||'.'||cur_item_v, ITEM_NAME); IF GET_ITEM_PROPERTY (blk_in||
'.'||cur_item_v, ITEM_TYPE) = 'CHECKBOX' THEN item_tab(index_v).prompt :=
GET_ITEM_PROPERTY(blk_in||'.'||cur_item_v, LABEL); ELSE item_tab(index_v).prompt :=
GET_ITEM_PROPERTY(blk_in||'.'||cur_item_v, PROMPT_TEXT); END IF; item_tab(index_v).datatype := GET_ITEM_PROPERTY(blk_in||'.'||
cur_item_v, DATATYPE); END IF; EXIT WHEN cur_item_v = last_item_v; cur_item_v := GET_ITEM_PROPERTY (blk_in||
'.'||cur_item_v, NEXTITEM); END LOOP; RETURN (index_v); END setup_items; FUNCTION format_value ( value_in IN VARCHAR2, datatype_in IN VARCHAR2 DEFAULT NULL ) RETURN VARCHAR2 IS retval VARCHAR2(32767) := value_in; BEGIN IF datatype_in = 'DATE' THEN retval := TO_CHAR(TO_DATE(retval),
date_format_pc); ELSE retval := REPLACE (retval, CHR(9),
chr9subst_pc); retval := REPLACE (retval, CHR(10),
chr10subst_pc); IF SUBSTR(retval, 1, 1) IN
('-', '+', '=') THEN retval := ' ' || retval; END IF; END IF; RETURN (retval); END format_value; /* ---------- PUBLIC MODULES ----------- */ PROCEDURE to_excel IS cur_block_c CONSTANT VARCHAR2(30)
:= NAME_IN ('SYSTEM.CURSOR_BLOCK'); cur_item_c CONSTANT VARCHAR2(61)
:= NAME_IN ('SYSTEM.CURSOR_ITEM'); cur_record_c CONSTANT VARCHAR2(30)
:= NAME_IN ('SYSTEM.CURSOR_RECORD'); outfilename_v VARCHAR2(255); -- := 'c:tmp'||USER||'.xls';
-- test in C-S mode outfile_v TEXT_IO.FILE_TYPE; num_items_v PLS_INTEGER; line_v VARCHAR2(32767); counter_v PLS_INTEGER := 0; uncommited_changes EXCEPTION; BEGIN IF NAME_IN('SYSTEM.FORM_STATUS') <> 'QUERY' THEN RAISE uncommited_changes; END IF; FIRST_RECORD; show_progress ('Формирование
списка полей для экспорта...'); num_items_v := setup_items
(cur_block_c); show_progress ('Открытие временного файла...'); -- for C-S mode make adjustments below outfilename_v := tempfilename; outfile_v := TEXT_IO.FOPEN
(tempdir_pc||outfilename_v, 'w'); show_progress ('Формирование
строки заголовков столбцов...'); line_v := NULL; FOR i IN 1 .. num_items_v LOOP line_v := line_v || format_value
(item_tab(i).prompt); IF i < num_items_v THEN line_v
:= line_v || CHR(9); END IF; END LOOP; TEXT_IO.PUT_LINE(outfile_v, line_v); LOOP IF MOD (counter_v, 100) = 0 THEN show_progress ( 'Форматирование записей блока
('|| TO_CHAR(counter_v,'99990') || ')...' ); END IF; counter_v := counter_v + 1; line_v := NULL; FOR i IN 1 .. num_items_v LOOP line_v := line_v || format_value ( NAME_IN (cur_block_c||'.
'||item_tab(i).name), item_tab(i).datatype ); IF i < num_items_v THEN line_v
:= line_v || CHR(9); END IF; END LOOP; TEXT_IO.PUT_LINE(outfile_v, line_v); EXIT WHEN NAME_IN('SYSTEM.LAST_RECORD')
= 'TRUE'; NEXT_RECORD; END LOOP; TEXT_IO.FCLOSE(outfile_v); item_tab.DELETE; GO_RECORD(TO_NUMBER(cur_record_c)); GO_ITEM (cur_item_c); show_progress ( 'Всего экспортировано ' ||
TO_CHAR(counter_v) || ' записей' ); WEB.SHOW_DOCUMENT(cgi_script_pc ||
'?' || outfilename_v, '_blank'); --host('cmd /C start excel '||
outfilename_v); -- test in C-S mode EXCEPTION WHEN uncommited_changes THEN message('Перед выполнеием экспорта,
сохраните сделанные изменения!'); WHEN OTHERS THEN IF TEXT_IO.IS_OPEN(outfile_v)
THEN TEXT_IO.FCLOSE(outfile_v); END IF; RAISE; END to_excel; END fp_exp;

Не забудьте про cgi скрипт!

Приложение

Здесь описаны некоторые советы по возможному дальнейшему совершенствованию
предложенного механизма экспорта данных в Excel.

Вместо предложенного формата файла, можно попробовать использовать sylk
формат, описание которого можно найти в Интернет. Excel также понимает этот
формат, предоставляющий большие возможности по форматированию данных.

В качестве первой строки в файл можно записывать заголовок окна формы.
Получится своеобразный заголовок отчета в Excel.

Блок формы может содержать десятки, сотни тысяч записей, а то и больше. В
таких случаях выгружать все записи блока — задача не реальная. Для таких случаев
можно реализовать ограничитель на количество выгружаемых записей. Т.е. условием
окончания записи в файл сделать не только достижение последней записи в блоке,
но и дополнительно к этому, достижение максимально возможного количества записей
для выгрузки, если таковое установлено.

В качестве значения ячейки можно передавать формулы. Для этого значение
должно начинаться с символа "=". В формулах можно использовать
арифметические операции и ссылки на ячейки, например вот так
=A2+B2+C2. Но почему-то не получается использовать в формулах
функции, например =СУММ(A2:A10). В приведенной реализации
использование формул запрещено. Если значение начинается с символа
"=", то оно дополняется пробелом слева.

Понравилась статья? Поделить с друзьями:
  • Oracle to excel vba
  • Oracle to excel 2010
  • Oracle sql developer экспорт в excel
  • Oracle sql developer выгрузить результат запроса в excel
  • Oracle sql developer import from excel