使用 UTL_FILE 脚本损坏文件

Posted

技术标签:

【中文标题】使用 UTL_FILE 脚本损坏文件【英文标题】:File corruption with UTL_FILE script 【发布时间】:2015-01-22 13:53:01 【问题描述】:

我肯定已经在 FAR 和 WIDE 搜索过这个问题的答案,但我什么也找不到!我正在使用 UTL_FILE 脚本从 Oracle 表中提取一些文件 BLOBS 并将它们保存到文件目录中。它适用于很多文件,但我已经通过消除过程缩小了范围,即它对于具有“非常规”文件名的文件存在问题,尽管文件名仍然有效,但文件在传输中被损坏。它们原本可能只有 30kb,但导出为 5kb,无法打开。所以我知道这不是一个大文件大小的问题。这些文件可以通过应用程序正常打开,具有有效的 MIME 类型编码,否则会在文件系统上正常打开,但 UTL_FILE 似乎不喜欢它们。它们是具有额外“。”的文件。在它们中,即:john.smith.doc,或井号,即:Smith #12345.doc 或括号等。我无法更改 Oracle 表中的源文件名,但我一直在将 ID 号连接到它们时将它们保存出来,以便稍后将其作为 ETL 加载到 SQL 文件表中的键来引用。也许我还需要编写一个复杂的 REGEXP 来即时重命名文件并删除坏字符,但我不确定这会起作用,因为我不知道 UTL_FILE 在什么时候让它们窒息。如果它在源头上,那将无济于事。有没有其他人遇到过这个问题?这是我的脚本:

DECLARE

CURSOR C1 IS Select FILE_ID || '---' || substr(DOCUMENTLOCATION,1,instr 
(DOCUMENTLOCATION,'.')-1)||'.doc' as FILE_NAME, FILE_BLOB, FILE_ID
From DOCUMENTS d inner join CASEJOURNAL c on d.FILE_ID = c.JOURNALENTRYID 
where (JOURNAL_ENTRY_TYPE = 117 or JOURNAL_ENTRY_TYPE = 3) AND 
c.DOCUMENTLOCATION Is Not Null AND d.MIME_TYPE = 'application/msword' 
AND FILE_ID BETWEEN 785 AND 3380;

  l_file      UTL_FILE.FILE_TYPE;
  l_buffer    RAW(32000);
  l_amount    INTEGER := 32000;
  l_pos       INTEGER := 1;
  l_blob      BLOB;
  l_blob_len  INTEGER;
  l_filename  varchar2(255); 

BEGIN
--Select BLOB file into variables
FOR I in C1 
LOOP
Select FILE_ID || '---' || substr(DOCUMENTLOCATION,1,instr
(DOCUMENTLOCATION,'.')-1) ||'.doc' as FILE_NAME, FILE_BLOB INTO l_filename, 
l_blob From DOCUMENTS d inner join CASEJOURNAL c on d.FILE_ID = 
c.JOURNALENTRYID   where (JOURNAL_ENTRY_TYPE = 117 or JOURNAL_ENTRY_TYPE =  
3) AND c.DOCUMENTLOCATION Is Not Null AND d.MIME_TYPE 
= 'application/msword' and d.FILE_ID = I.FILE_ID;

-- Define the output directory
l_file := UTL_FILE.FOPEN('\\myfiledirectory',l_filename,'wb',32000);
l_pos := 1;
l_amount := 32000;

--Get length of BLOB file and save to variable.
l_blob_len := DBMS_LOB.getlength(l_blob);

-- Write the data to the file
--If small enough for single write:
    IF l_blob_len < 32000 THEN
        UTL_FILE.PUT_RAW (l_file, l_blob);
        UTL_FILE.FFLUSH(l_file);
        --Write in pieces if larger than 32k
        ELSE
        l_pos := 1;
        WHILE l_pos < l_blob_len AND  l_amount > 0
        LOOP
        DBMS_LOB.read(l_blob, l_amount, l_pos, l_buffer);
        UTL_FILE.PUT_RAW(l_file, l_buffer);
        UTL_FILE.FFLUSH(l_file);
        --Set start position for next write
        l_pos := l_pos + l_amount;
        --Set end position if less than 32k.
        l_blob_len := l_blob_len - l_amount;
            IF l_blob_len < 32000 THEN
            l_amount := l_blob_len;
            END IF;
        END LOOP;
  END IF;
UTL_FILE.FCLOSE(l_file);
END LOOP;
END;

【问题讨论】:

【参考方案1】:

文件名不会影响打开文件后字节的写入方式。如果文件超过 32k,您似乎正在截断文件。你的循环这样做:

    WHILE l_pos < l_blob_len AND  l_amount > 0
    LOOP

... 但随后您在循环中同时更改了l_posl_blob_len;一旦调整后的l_pos 低于剩余 l_blob_len,你退出循环,太早了。你不需要调整l_blob_len,甚至不需要调整l_amount——这是要读取的最大字节数,如果它高于剩余的字节数也没关系。

所以把循环改成:

    WHILE l_pos < l_blob_len AND  l_amount > 0
    LOOP
      DBMS_LOB.read(l_blob, l_amount, l_pos, l_buffer);
      UTL_FILE.PUT_RAW(l_file, l_buffer);
      UTL_FILE.FFLUSH(l_file);
      --Set start position for next write
      l_pos := l_pos + l_amount;
    END LOOP;

与您的问题无关,但您无需重新选择光标循环内的数据。您已经在 i 游标变量中获得了所需的值,因此您可以这样做:

FOR I in C1 
LOOP
l_filename := i.file_name;
l_blob := i.file_blob;

-- Define the output directory
...

或者根本不用担心l_filenamel_blob 局部变量;因为无论如何你只在游标循环中引用它们,所以在任何地方直接使用i.file_namei.file_blob,例如

l_file := UTL_FILE.FOPEN('\\myfiledirectory',i.file_name,'wb',32000);
l_blob_len := DBMS_LOB.getlength(i.file_blob);

等等

【讨论】:

更新——我发现提取时损坏的文件与我的脚本无关,也与它们在数据库中的命名约定无关。这是因为前端应用程序在上传到数据库时压缩了一些文件。无法分辨哪些是压缩的,哪些不是(数据库中没有任何标志)。但是,当我将 UTL_FILE_COMPRESS.lz_uncompress() 应用于给我带来问题的文件时,它们完美地打开了——显然。

以上是关于使用 UTL_FILE 脚本损坏文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 oracle 中使用 utl_file 写入文件

utl_file.FCLOSE() 处理大文件时速度很慢

计算由 UTL_FILE 创建的文件中的行数

UTL_FILE

在不使用 UTL_FILE 的情况下从 PL/SQL 中的文件读取/写入数据

如何在使用 utl_file write plsql 将数据写入 .csv 文件时处理中文/日文字符