Записки жертвы высоких технологий

    Здравствуйте! Мой блог посвящен в большей своей части моей профессиональной деятельности на поприще использования технологий ORACLE для разработки баз данных OLTP и OLAP хранилищ данных. В заметках я периодически размещаю разнообразные SQL, PL/SQL и Java скрипты написанные мной и не только мной, ссылки на интересные источники в сети либо другую полезную информацию, которая каким либо образом касается моей работы с замечательными продуктами компании ORACLE.
    Вы можете связаться со мной по контактному емейлу, если у вас есть какие-либо вопросы, связанные с разработкой баз данных на основе продуктов ORACLE, буду рад помочь вам, если это будет в моих силах.

9 окт. 2009 г.

Встроенные константы VBA

Никогда бы не подумал, что это может мне пригодиться для работы с ORACLE, но вот и пригодилось. Случилось так, что мне пришлось настраивать выгрузку из документов Excel'а в ORACLE с помощью объектов ADO и править код VBA в XLA надстройках. На мое удивление, оказалось, что реализовать механизм выгрузки не так уж и сложно (см. мой пост ниже), но из-за убожества среды разработки VBA писать эти скрипты крайне тяжело. Например системные константы среда показывает в подсказках крайне избирательно.
Список констант, я нашел на SourceForge. Оказалось, что кучу констант я в своем коде переопределил и мне пришлось мои константы заменять на системные.


Key Codes
vbKeyLButton
0x1
Left mouse button
vbKeyRButton
0x2
Right mouse button
vbKeyCancel
0x3
CANCEL key
vbKeyMButton
0x4
Middle mouse button
vbKeyBack
0x8
BACKSPACE key
vbKeyTab
0x9
TAB key
vbKeyClear
0xC
CLEAR key
vbKeyReturn
0xD
ENTER key
vbKeyShift
0x10
SHIFT key
vbKeyControl
0x11
CTRL key
vbKeyMenu
0x12
MENU key
vbKeyPause
0x13
PAUSE key
vbKeyCapital
0x14
CAPS LOCK key
vbKeyEscape
0x1B
ESC key
vbKeySpace
0x20
SPACEBAR key
vbKeyPageUp
0x21
PAGE UP key
vbKeyPageDown
0x22
PAGE DOWN key
vbKeyEnd
0x23
END key
vbKeyHome
0x24
HOME key
vbKeyLeft
0x25
LEFT ARROW key
vbKeyUp
0x26
UP ARROW key
vbKeyRight
0x27
RIGHT ARROW key
vbKeyDown
0x28
DOWN ARROW key
vbKeySelect
0x29
SELECT key
vbKeyPrint
0x2A
PRINT SCREEN key
vbKeyExecute
0x2B
EXECUTE key
vbKeySnapshot
0x2C
SNAPSHOT key
vbKeyInsert
0x2D
INSERT key
vbKeyDelete
0x2E
DELETE key
vbKeyHelp
0x2F
HELP key
vbKeyNumlock
0x90
NUM LOCK key

Form Codes
vbModeless
0
UserForm is modeless.
vbModal
1
UserForm is modal (default).

Colour Codes
vbBlack
0x0
Black
vbRed
0xFF
Red
vbGreen
0xFF00
Green
vbYellow
0xFFFF
Yellow
vbBlue
0xFF0000
Blue
vbMagenta
0xFF00FF
Magenta
vbCyan
0xFFFF00
Cyan
vbWhite
0xFFFFFF
White

Dir etc Codes
vbNormal
0
Normal (default for Dir and SetAttr)
vbReadOnly
1
Read-only
vbHidden
2
Hidden
vbSystem
4
System file
vbVolume
8
Volume label
vbDirectory
16
Directory or folder
vbArchive
32
File has changed since last backup

File Attribute Codes
Normal
0
Normal file. No attributes are set.
ReadOnly
1
Read-only file. Attribute is read/write.
Hidden
2
Hidden file. Attribute is read/write.
System
4
System file. Attribute is read/write.
Volume
8
Disk drive volume label. Attribute is read-only.
Directory
16
Folder or directory. Attribute is read-only.
Archive
32
File has changed since last backup. Attribute is read/write.
Alias
64
Link or shortcut. Attribute is read-only.
Compressed
128
Compressed file. Attribute is read-only.

Miscellaneous Codes
vbCrLf
"\n"
Carriage returnlinefeed combination
vbCr
chr(13)
Carriage return character
vbLf
chr(10)
Linefeed character
vbNewLine
"\n"
Platform-specific new line character; whichever is appropriate for current platform
vbNullChar
chr(0)
Character having value 0
vbNullString
chr(0)
String having value 0 Not the same as a zero-length string (""); used for calling external procedures
vbObjectError
-2147221504
User-defined error numbers should be greater than this value. For example: Err.Raise Number = vbObjectError + 1000
vbTab
chr(9)
Tab character
vbBack
chr(8)
Backspace character
vbFormFeed
chr(12)
Not useful in Microsoft Windows
vbVerticalTab
chr(11)
Not useful in Microsoft Windows

MsgBox Codes
vbOKOnly
0
OK button only (default)
vbOKCancel
1
OK and Cancel buttons
vbAbortRetryIgnore
2
Abort, Retry, and Ignore buttons
vbYesNoCancel
3
Yes, No, and Cancel buttons
vbYesNo
4
Yes and No buttons
vbRetryCancel
5
Retry and Cancel buttons
vbCritical
16
Critical message
vbQuestion
32
Warning query
vbExclamation
48
Warning message
vbInformation
64
Information message
vbDefaultButton1
0
First button is default (default)
vbDefaultButton2
256
Second button is default
vbDefaultButton3
512
Third button is default
vbDefaultButton4
768
Fourth button is default
vbApplicationModal
0
Application modal message box (default)
vbSystemModal
4096
System modal message box
vbMsgBoxHelpButton
16384
Adds Help button to the message box
vbMsgBoxSetForeground
65536
Specifies the message box window as the foreground window
vbMsgBoxRight
524288
Text is right aligned
vbMsgBoxRtlReading
1048576
Specifies text should appear as right-to-left reading on Hebrew and Arabic systems
vbOK
1
OK button pressed
vbCancel
2
Cancel button pressed
vbAbort
3
Abort button pressed
vbRetry
4
Retry button pressed
vbIgnore
5
Ignore button pressed
vbYes
6
Yes button pressed
vbNo
7
No button pressed

Shell Codes
vbHide
0
Window is hidden and focus is passed to the hidden window.
vbNormalFocus
1
Window has focus and is restored to its original size and position.
vbMinimizedFocus
2
Window is displayed as an icon with focus.
vbMaximizedFocus
3
Window is maximized with focus.
vbNormalNoFocus
4
Window is restored to its most recent size and position. The currently active window remains active.
vbMinimizedNoFocus
6
Window is displayed as an icon. The currently active window remains active.

Special Folder Codes
WindowsFolder
0
The Windows folder contains files installed by the Windows operating system.
SystemFolder
1
The System folder contains libraries, fonts, and device drivers.
TemporaryFolder
2
The Temp folder is used to store temporary files. Its path is found in the TMP environment variable.

StrConv Codes
vbUpperCase
1
Converts the string to uppercase characters.
vbLowerCase
2
Converts the string to lowercase characters.
vbProperCase
3
Converts the first letter of every word in string to uppercase.
vbWide
4
Converts narrow (single-byte) characters in string to wide (double-byte) characters. Applies to Far East locales.
vbNarrow
8
Converts wide (double-byte) characters in string to narrow (single-byte) characters. Applies to Far East locales.
vbKatakana
16
Converts Hiragana characters in string to Katakana characters. Applies to Japan only.
vbHiragana
32
Converts Katakana characters in string to Hiragana characters. Applies to Japan only.
vbUnicode
64
Converts the string to Unicode using the default code page of the system.
vbFromUnicode
128
Converts the string from Unicode to the default code page of the system.

System Colour Codes
vbScrollBars
0x80000000
Scroll bar color
vbDesktop
0x80000001
Desktop color
vbActiveTitleBar
0x80000002
Color of the title bar for the active window
vbInactiveTitleBar
0x80000003
Color of the title bar for the inactive window
vbMenuBar
0x80000004
Menu background color
vbWindowBackground
0x80000005
Window background color
vbWindowFrame
0x80000006
Window frame color
vbMenuText
0x80000007
Color of text on menus
vbWindowText
0x80000008
Color of text in windows
vbTitleBarText
0x80000009
Color of text in caption, size box, and scroll arrow
vbActiveBorder
0x8000000A
Border color of active window
vbInactiveBorder
0x8000000B
Border color of inactive window
vbApplicationWorkspace
0x8000000C
Background color of multiple-document interface (MDI) applications
vbHighlight
0x8000000D
Background color of items selected in a control
vbHighlightText
0x8000000E
Text color of items selected in a control
vbButtonFace
0x8000000F
Color of shading on the face of command buttons
vbButtonShadow
0x80000010
Color of shading on the edge of command buttons
vbGrayText
0x80000011
Grayed (disabled) text
vbButtonText
0x80000012
Text color on push buttons
vbInactiveCaptionText
0x80000013
Color of text in an inactive caption
vb3DHighlight
0x80000014
Highlight color for 3-D display elements
vb3DDKShadow
0x80000015
Darkest shadow color for 3-D display elements
vb3DLight
0x80000016
Second lightest 3-D color after vb3DHighlight
vbInfoText
0x80000017
Color of text in ToolTips
vbInfoBackground
0x80000018
Background color of ToolTips

Var Type Codes
vbEmpty
0
Uninitialized (default)
vbNull
1
Contains no valid data
vbInteger
2
Integer
vbLong
3
Long integer
vbSingle
4
Single-precision floating-point number
vbDouble
5
Double-precision floating-point number
vbCurrency
6
Currency
vbDate
7
Date
vbString
8
String
vbObject
9
Object
vbError
10
Error
vbBoolean
11
Boolean
vbVariant
12
Variant (used only for arrays of variants)
vbDataObject
13
Data access object
vbDecimal
14
Decimal
vbByte
17
Byte
vbUserDefinedType
36
Variants that contain user-defined types
vbArray
8192
Array

6 окт. 2009 г.

Экспорт из Excel в ORACLE

Красивое и очень простое решение по сохранению данных в БД ORACLE непосредственно из программы Excel придумано умельцами и подобрано мной. Внешне принцип действия этого механизма очень прост. Пользователь работает в обычном режиме с файлами Excel, без каких-либо ограничений редактируя документы. После окончания правки пользователь нажимает кнопку сохранения файла. Вместо безусловного сохранения файла на диске на экране пользователя отображается диалоговое окно с вопросом о необходимости сохранения данных на сервере. Если пользователь отказывается, то данные сохраняются обычным образом. Если соглашается, то данные помимо сохранения в файловой системе экспортируются на сервер с помощью обычного ADODB.

Для реализации всего этого механизма нужно создать XLA надстройку, реализующую весь необходимый функционал. Перехват кнопки "Save..." осуществляется с помощью следующего кода:

Private Sub xlApp_WorkbookBeforeSave(ByVal Wb As Workbook, ByVal SaveAsUI As Boolean, Cancel As Boolean)   On Error GoTo TerminateSub   If MsgBox(Prompt:="Сохранить в БД?", Buttons:=vbYesNo) = vbYes Then     On Error GoTo -1     MyWorkbookSave   End If TerminateSub: End Sub


Теперь необходимо реализовать функцию MyWorkbookSave. В этой функции нужно сделать следующее:

1. Подключиться к серверу
2. В цикле пройти по строкам листа и выполнить SQL команду с данными
3. Сохранить или откатить изменения
4. Закрыть соединение с сервером


Для простоты описания будем считать, что пользователь работает с первым листом книги Excel и вводит в него таблицу, состоящую из двух столбцов ID и NAME, которая сохраняется в БД как таблица MY_EXCEL_TABLE(ID NUMBER, NAME VARCHAR2).


Sub MyWorkbookSave()   Dim conn As Object   Dim cmd As Object   'Информируем пользователя о начале процесса   Application.StatusBar = "Процесс запущен"   'Открываем соединение   Set conn = CreateObject("ADODB.Connection")   conn.Open "Provider=MSDAORA.1;Password=MyPassword;User ID=MyUser;Data Source=MyDataSource;Persist Security Info=True"   'Явно начинаем транзакцию во избежание режима AutoCommit=ON   conn.BeginTrans   'Создаем команду   Set cmd = CreateObject("ADODB.Command")   cmd.ActiveConnection = conn   cmd.CommandText = "insert into my_excel_table(id, name) values (?,?)"   'Описываем параметры команды. В нашем случае их два (значения столбцов ID и NAME)   cmd.Parameters.Append cmd.CreateParameter("id", 131, 1)   cmd.Parameters.Append cmd.CreateParameter("name", 200, 1, 100)   'Для упрощения примера считаем, что данные находятся в столбцах A1 и B1 первого в книге листа   'Создаем соответствующий Range-объект   Set sh1 = ActiveWorkbook.Sheets(1)   Set r = sh1.Range(sh1.[A1], sh1.[B1].End(xlDown))   'Проходим по всем заполненным строкам   'В реальных документах строки создаются динамически и впоследствии не удаляются   'поэтому значение созданных строк может отличатьcя от количества заполненных   For iRow = 1 To r.Rows.Count     cmd.Parameters("ID").Value = r.Cells(iRow, 1).Value     cmd.Parameters("NAME").Value = NVL(r.Cells(iRow, 2).Value)     cmd.Execute   Next iRow   'Сохраняем данные   conn.CommitTrans   'Удаляем команду и закрываем соединение   Set cmd = Nothing   conn.Close   'Информируем пользователя о завершении процесса   Application.StatusBar = "Процесс завершен" End Sub

Собственно говоря, это все. Остальное - навороты. К ним относятся необходимые проверки на корректность сохраняемых данных, обработчики ошибок и проч. Очень важно обратить внимание на то, что в ADO по умолчанию выключен режим implicit transactions, поэтому каждая выполняемая SQL команда обрамляется командами начала и конца транзакции, то есть фактически после каждой команды принудительно выполняется commit. Чтобы отключить этот навязчивый режим, необходимо включить режим implicit transactions и объявлять и завершать транзакции явно. Для получения подробной информации о том, как это сделать, обратитесь к MSDN.

Представление с последовательностью дат

Все! Достало! Прошло столько лет со дня появления иерархических запросов в ORACLE, столько шикарных примеров их использования лежит в недрах Сети на расстоянии вытянутой ру...Э... Ну, в общем рядом!
Но нет! Мы же не ищем легких путей, мы не ценим набитых кем то другим шишек! Нам нравится ежедневно изобретать велосипеды с квадратными колесами и пытаться заставить их ездить быстро и плавно!
Вот и для генерации последовательности дат нет-нет, но и более-менее опытные разработчики, не подумав начинают целеустремленно создавать таблицы с единственным полем типа DATE, функционал ее наполнения и очистки (это если сделают не temporary table, а обычную heap table) и прочие супер-навороты.
А я давным давно в недрах, кажется, SQL.RU откопал оставленный кем-то пример генерации последовательности дат с помощью обычного иерархического запроса и взял его на заметку. Оговорюсь сразу, что используя схожий подход, можно генерировать не только последовательности дат, но и различные другие последовательности, как числовые, так и строковые.
Ладно, хватит текста, привожу пример, генерирующий даты с 01.12.2008 по 31.12.2008:

create view v_last_month_of_2008 as
select
to_date('01.12.2008','DD.MM.YYYY') + level -1 dt
from
dual
connect by
to_date('01.12.2008','DD.MM.YYYY') + level -1 <= to_date('31.12.2008','DD.MM.YYYY')


Если все еще нужен комментарий, то пожалуйста! В секции запроса connect by ставим граничное условие связывания с помощью выражения (ДатаНачала + LEVEL -1) <= ДатаКонца, в виде столбца запроса выводим (ДатаНачала + LEVEL -1). "-1" в выражениях появляется из-за того, что LEVEL начинается с единицы. Ну а DUAL... ну куда ж без DUAL то! Для генерации граничных дат можно использовать не только константы, но и пользовательские функции. Также похожий запрос часто используется мной для генерации последовательностей дат вида +-месяц от текущей даты.

3 окт. 2009 г.

Отправка уведомлений по почте (plain text)

Для давнишнего проекта нужно было отсылать технические уведомления по почте. Помню, нашел какой то стандартный простейший пример на сайте ORACLE, попробовал. Вроде бы все проходит, а кодировка косячная. Немножко изменил код - все заработало как надо. Здесь привожу тестовый исходник, который правильно заработал после правки. Единственный минус этого примера - то, что письма можно слать только в plain text'е. Эта проблема впоследствии тоже была решена, но это уже совсем другая история.
declare  -- Объявляем переменные   mailhost VARCHAR2(64) := 'smtp.server.name.at.domain.name';   sender VARCHAR2(64) := 'sender@domain.name';   recipient VARCHAR2(32000) := 'recipient@domain.name';   subject VARCHAR2(254) := 'Тема письма';   message VARCHAR2(32000) := 'Привет, я - отправленное письмо';   mail_conn utl_smtp.connection; -- Отправка заголовков procedure out_header(   p_Header IN VARCHAR2,   p_Data IN VARCHAR2 ) is begin   utl_smtp.write_raw_data(mail_conn, utl_raw.CONVERT(utl_raw.cast_to_raw(p_Header || ': ' || p_Data), 'RUSSIAN_CIS.CL8MSWIN1251', 'RUSSIAN_CIS.CL8MSWIN1251'));   utl_smtp.write_data(mail_conn, utl_tcp.crlf); end; -- Отправка данных procedure out_data(   p_Data IN VARCHAR2 ) is begin   utl_smtp.write_raw_data(mail_conn, utl_raw.CONVERT(utl_raw.cast_to_raw(p_Data), 'RUSSIAN_CIS.CL8KOI8R', 'RUSSIAN_CIS.CL8MSWIN1251'));   utl_smtp.write_data(mail_conn, utl_tcp.crlf); end; begin   -- соединяемся, шлем информацию о соединении   mail_conn := utl_smtp.open_connection(mailhost, 25);   utl_smtp.helo(mail_conn, mailhost);   utl_smtp.mail(mail_conn, sender );   utl_smtp.rcpt(mail_conn, recipient);   -- начинаем отправлять заголовок   utl_smtp.open_data(mail_conn);   out_header('MIME-Version', '1.0');   out_header('Content-Type', 'text/plain;');   out_header('From', sender);   out_header('To', recipient);   out_header( 'Subject', subject || utl_tcp.crlf);   -- Шлем данные (plain text)   out_data (message );   -- Закрываем соединение   utl_smtp.close_data(mail_conn);   utl_smtp.quit(mail_conn); end;

2 окт. 2009 г.

Контроль частоты переключения LOG файлов

К сожалению, не помню, где утянул, но оказалось очень удобно! Особенно удобно использовать этот запрос при настройке размера LOG файлов. Знающие люди советуют, чтобы в час переключалось не более двух раз, лучше один при рабочей нагрузке.

select to_char(first_time,'DD.MM.YYYY') day, to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'00',1,0)),'999') "00", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'01',1,0)),'999') "01", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'02',1,0)),'999') "02", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'03',1,0)),'999') "03", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'04',1,0)),'999') "04", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'05',1,0)),'999') "05", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'06',1,0)),'999') "06", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'07',1,0)),'999') "07", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'08',1,0)),'999') "08", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'09',1,0)),'999') "09", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'10',1,0)),'999') "10", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'11',1,0)),'999') "11", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'12',1,0)),'999') "12", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'13',1,0)),'999') "13", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'14',1,0)),'999') "14", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'15',1,0)),'999') "15", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'16',1,0)),'999') "16", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'17',1,0)),'999') "17", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'18',1,0)),'999') "18", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'19',1,0)),'999') "19", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'20',1,0)),'999') "20", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'21',1,0)),'999') "21", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'22',1,0)),'999') "22", to_char(sum(decode(substr(to_char(first_time,'HH24'),1,2),'23',1,0)),'999') "23" from v$log_history group by to_char(first_time,'DD.MM.YYYY') order by to_date(day, 'DD.MM.YYYY')

Пакет DBMS_METADATA

DBMS_METADATA - очень удобная штука, рекомендую всем пользоваться этим средством, так как удобные, генераторы скриптов в TOAD и PL/SQL Developer'e не всегда корректно генерируют SQL скрипты. Кстати, отдаю должное разработчикам PL/SQL Developer'a, которые признали этот факт и приняли простое и эффективное решение - использовать DBMS_METADATA как альтернативый генератор скриптов. Советую немножко предварительно покурить док, дабы понимать, что к чему. Вот например привожу скрипт, который генерирует скрипт, создающий все индексы текущей схемы. Скрипт выводится в стандартный поток вывода с помощью DBMS_OUTPUT.
declare
l_n number;
l_t number;
l_data clob;
begin
l_n := dbms_metadata.open(object_type => 'INDEX');
l_t := dbms_metadata.add_transform(handle => l_n,name => 'DDL');
dbms_metadata.set_transform_param(transform_handle => DBMS_METADATA.SESSION_TRANSFORM,name => 'SQLTERMINATOR',value => TRUE);
for hCur in ( select * from user_indexes ) loop
l_data := dbms_metadata.get_ddl(object_type => 'INDEX', name => hCur.Index_Name);
dbms_output.put_line('drop index ' || hCur.Index_Name || ';');
dbms_output.put_line(dbms_lob.substr(l_data, 1024, 1));
end loop;
dbms_metadata.close(l_n);
end;

Простой Java CSV парсер для ORACLE

В проекте для ХД МосОблГаз'а пришлось делать парсер для загрузки CSV файлов, размещенных в определенной папке на диске сервера (10.2.0.4, Win32). Папка заполняется извне инструментами Cognos'а в виде набора *.csv файлов. Данные из файлов парсятся и грузятся в определенную буферную таблицу, предварительно очищаемую. После загрузки файлы удаляются. Папка определена в виде объекта DIRECTORY, поэтому считывается из DBA_DIRECTORIES. В качестве разделителя используется ";", в первой строке каждого файла находятся названия столбцов. Они тоже парсятся, чтобы потом использовать их порядок при INSERT'е в целевую таблицу

Сначала сделал на PL/SQL'е, потом решил переделать под встроенную Java. В приведенном коде много несуразностей, но он был написан за час-полтора работы, поэтому прошу строго не судить. Вот что получилось:

create or replace and compile java source named pump_csv as
import java.sql.*;
import java.text.*;
import java.lang.*;
import java.util.*;
import java.io.*;
import oracle.jdbc.*; // Подгружаем драйвер ORACLE
import c_pump_tools.*;
public class c_pump_csv
{
  static boolean DEBUG = true; // Включает режим пассивной отладки (вывод диагностических сообщений)
  static String CSV_DIR_NAME = "CSV_DIR";
  static String ORA_URL = "jdbc:default:connection"; // URL соединения с MSSQL
  public static void run() throws Exception
  {
    // Грузим драйвер ORACLE
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    OracleConnection conORA = null;
    try
    {
      // Соединяемся с базой данных ORACLE по внутреннему соединению
      try
      {
        conORA = (OracleConnection)DriverManager.getConnection(ORA_URL);
      }
      catch(Exception e)
      {
        e.printStackTrace();
        throw new Exception("Невозможно соединиться с сервером-приемником данных по URL=" + ORA_URL);
      }
      conORA.setAutoCommit(false);// Устанавливаем отказ от autocommit
      log("Connected ORACLE on " + ORA_URL);
      String csvDirectoryName = null;
      OraclePreparedStatement stmtORA_InsertData = null;
      OracleStatement stmtORA_CheckStageClearance = null;
      try
      {
        // Получаем имя директории с CSV файлами
        try
        {
          OracleStatement stmtORA_GetDirectory = null;
          OracleResultSet ors=null;
          try
          {
            stmtORA_GetDirectory = (OracleStatement)conORA.createStatement();
            ors = (OracleResultSet)stmtORA_GetDirectory.executeQuery("select directory_path from dba_directories where directory_name = '"+CSV_DIR_NAME+"'");
            if(ors.next()) csvDirectoryName = ors.getString(1);
            if (csvDirectoryName == null)
            {
              log("Unable to resolve directory name " + CSV_DIR_NAME + ". Pumping cannot be initiated");
              throw new Exception ("Невозможно разрешить имя директории '" + CSV_DIR_NAME + "'");
            }
            log("Path to \"" + csvDirectoryName + "\" has been found to pump from");
          }
          finally
          {
            if (ors instanceof OracleResultSet)ors.close();
            if (stmtORA_GetDirectory instanceof OracleStatement) stmtORA_GetDirectory.close();
            log("Resources for resolving the CSV directory name has been released");
          }
        }
        catch(Exception e)
        {
          e.printStackTrace();
          throw new Exception("Невозможно получить путь к папке по имени директории " + CSV_DIR_NAME);
        }
        // Проверяем готовность целевой таблицы к приему новых данных
        try
        {
          stmtORA_CheckStageClearance = (OracleStatement)conORA.createStatement();
          OracleResultSet rstCheck = null;
          try
          {
            rstCheck = (OracleResultSet)stmtORA_CheckStageClearance.executeQuery("select count(*) cnt from target_table");
            int iCountRows = 0;
            if (rstCheck.next()) iCountRows = rstCheck.getInt(1);
            if (iCountRows>0)
            {
              log("WARNING! Source table TARGET_TABLE is not empty. Data cannot be loaded into non-empty table. Terminating data import");
              return;
            }
            log("Source table TARGET_TABLE is empty and ready to receive data");
          }
          finally
          {
            if (rstCheck != null) rstCheck.close();
          }
        }
        catch(Exception e)
        {
          e.printStackTrace();
          throw new Exception("Невозможно выполнить команду для проверки готовности целевой таблицы TARGET_TABLE сервера-приемника");
        }
        // Просматриваем содержимое папки на предмет наличия в ней CSV файлов
        File sourceFolder = new File(csvDirectoryName);
        FileFilter sourceFolderFilter = new FileFilter()
        {
          public boolean accept(File file)
          {
            return !file.isDirectory()&& file.getName().endsWith(".csv");
          }
        };
        File[]csvFiles = sourceFolder.listFiles(sourceFolderFilter);
        if (csvFiles == null || csvFiles.length == 0)
        {
          log("Source CSV files not found in directory " + CSV_DIR_NAME + ". Exiting process");
          return;
        }
        else
        {
          for (int iterFile=0; iterFile
          {
            // Открываем и построчно читаем текстовый файл
            File csvFile = csvFiles[iterFile];
            log("File " + csvFile.getCanonicalPath() + " has been found in source SSV folder");
            BufferedReader input = new BufferedReader(new FileReader(csvFile));
            try
            {
              // Инициализируем переменные для разбора строк
              StringBuffer rowColumnNames = new StringBuffer();
              StringBuffer rowColumnBinds = new StringBuffer();
              int columnCount = 0;
              String line = null;
              long rowIndex = 0;
              try
              {
                while (( line = input.readLine()) != null)
                {
                  rowIndex++;
                  if(rowIndex == 1)
                  {
                    // В первой строке находятся столбцы, для вставки данных
                    StringTokenizer st = new StringTokenizer(line, ";");
                    while(st.hasMoreTokens())
                    {
                      String fieldName = st.nextToken().trim().toUpperCase();
                      if (rowColumnNames.length() > 0) rowColumnNames.append(",");
                      rowColumnNames.append(fieldName);
                      if (rowColumnBinds.length() > 0) rowColumnBinds.append(",");
                      rowColumnBinds.append("?");
                      columnCount++;
                    }
                    if (columnCount==0) throw new Exception("В первой строке CSV файла не обнаружены названия столбцов. Импорт данных невозможен.");
                    log("Found " + new Integer(columnCount).toString() +" columns: " + rowColumnNames.toString() );
                    // Готовим команду для вставки данных
                    try
                    {
                      stmtORA_InsertData = (OraclePreparedStatement)conORA.prepareStatement("insert into target_table("+rowColumnNames.toString()+") values("+rowColumnBinds.toString()+")");
                    }
                    catch(Exception e)
                    {
                      e.printStackTrace();
                      throw new Exception("Невозможно подготовить команду для вставки данных из CSV источника");
                    }
                  }
                  else
                  {
                    // Разбор данных
                    StringTokenizer st = new StringTokenizer(line, ";");
                    ArrayList columnValues = new ArrayList(columnCount);
                    while(st.hasMoreTokens())
                    {
                      columnValues.add(st.nextToken());
                    }
                    if (columnValues.size() != columnCount) throw new Exception("Количество значений (" + columnValues.size()+ ") не соответствует количеству столбцов ("+new Integer(columnCount).toString()+")");
                    // Вставка данных в БД                   
                     if (columnCount>=1)
                    {
                      try{
                        stmtORA_InsertData.setString(1, (String)columnValues.get(0));
                      }catch(Exception e) {log("Error parsing data cell #1");throw e;}
                    }
                    if (columnCount>=2)
                    {
                      try{
                        stmtORA_InsertData.setString(2, (String)columnValues.get(1));
                      }catch(Exception e) {log("Error parsing data cell #2");throw e;}
                    }
                    if (columnCount>=3)
                    {
                      try{
                        stmtORA_InsertData.setString(3, (String)columnValues.get(2));
                      }catch(Exception e) {log("Error parsing data cell #3");throw e;}
                    }
                    if (columnCount>=4)
                    {
                      try{
                        stmtORA_InsertData.setString(4, (String)columnValues.get(3));
                      }catch(Exception e) {log("Error parsing data cell #4");throw e;}
                    }
                    if (columnCount>=5)
                    {
                      try{
                        stmtORA_InsertData.setString(5, (String)columnValues.get(4));
                      }catch(Exception e) {log("Error parsing data cell #5");throw e;}
                    }
                    if (columnCount>=6)
                    {
                      try{
                        stmtORA_InsertData.setString(6, (String)columnValues.get(5));
                      }catch(Exception e) {log("Error parsing data cell #6");throw e;}
                    }
                    if (columnCount>=7)
                    {
                      try{
                        stmtORA_InsertData.setString(7, (String)columnValues.get(6));
                      }catch(Exception e) {log("Error parsing data cell #7");throw e;}
                    }
                    if (columnCount>=8)
                    {
                      try{
                        stmtORA_InsertData.setString(8, (String)columnValues.get(7));
                      }catch(Exception e) {log("Error parsing data cell #8");throw e;}
                    }
                    stmtORA_InsertData.execute();
                  }
                } //while (read file)
                stmtORA_InsertData.execute("commit");
                log("Processed " + new Long(rowIndex).toString() + " rows. Pumping finished");
              }
              catch(Exception e)
              {
                e.printStackTrace();
                throw new Exception("\n\nФайл "+csvFile.getName()+"\nСбой закачки в строке " + rowIndex + ":\n"+e.getMessage()+"\n\n");
              }
            }
            finally
            {
              if (stmtORA_InsertData instanceof OraclePreparedStatement) stmtORA_InsertData.close();
              input.close();
              log("Input CSV stream closed");
              csvFile.delete();
              log("Source CSV file deleted");
            }
          } //iterate csvFiles
        } // csvFiles is not null
      }
      finally
      {
        // Освобождаем все используемые ресурсы
        if (stmtORA_InsertData != null) stmtORA_InsertData.close();
        if (stmtORA_CheckStageClearance != null) stmtORA_CheckStageClearance.close();
        log("Resources released");
      }
    }
    catch(Exception e)
    {
      log(e.getMessage());
      throw e;
    }
    finally
    {
      // Закрываем соединения с базами данных
      if (conORA != null && !conORA.isClosed()) conORA.close();
      log(Connections closed");
    }
  }
  // Пишем в LOG сервера
  public static void log(String msg)
  {
    if (DEBUG) c_pump_tools.log(msg);
  }
}

для корректной работы после компиляции необходимо будет с помощью DBMS_JAVA.GrantPermission(..) дать права на удаление файлов из папки-источника CSV файлов (java.io.FilePermission). Точный текст PL/SQL команды ORACLE напишет прямо в тексте сообщения об ошибке, если при попытке удалении файла такого гранта не обнаружит. Текст примерно такой:
call dbms_java.grant_permission('<пользователь,которому дать права>','SYS:java.io.FilePermission', '<объект для раздачи прав>', 'read,write,execute,delete')
Для обеспечению удаления любых файлов в папке вместо имени файла нужно указать "*", например C:\\CSVFOLDER\\*