Oracle 10:使用HEXTORAW填充blob数据

时间:2013-08-08 01:41:42

标签: sql oracle blob varbinary

我们在Oracle中有一个表,其中BLOB列需要填充少量任意字节数据 - 我们永远不会输入超过4000字节的数据。

我正在使用现有的基于C ++ OCI的基础结构,这使得在某些上下文中使用绑定变量非常困难,因此我需要仅使用简单查询来填充此BLOB列。 (我们正在努力使其现代化,但今天不是一种选择,)

我们对这样的查询运气不错:

UPDATE MyTable
   SET blobData = HEXTORAW('0EC1D7FA6B411DA5814...lots of hex data...0EC1D7FA6B411DA5814')
 WHERE ID = 123;

起初,这很有效。但是,最近我们遇到了一个需要输入2000多字节数据的情况。此时,我们遇到了Oracle错误ORA-01704: string literal too long,因为传递给HEXTORAW的字符串超过了4000个字符。我尝试拆分字符串,然后与||连接,但这并没有避免错误。

因此,我需要一种方法来更新此列,并使用简单查询为其填充超过2000个字节的数据。可能吗?

(我知道如果我拥有绑定变量,那将是微不足道的 - 实际上与这个表交互的其他应用程序使用这种确切的技术 - 但不幸的是我无法在这里重构数据库内容只需要将数据放入表中。)

修改

一种有效的方法是连接RAW:

UTL_RAW.CONCAT(HEXTORAW('...'), HEXTORAW('...'), HEXTORAW('...'))

这会躲避字符串长度限制,但似乎Oracle在RAW的长度上也有一个匹配的内部2000字节限制。所以我不能用RAW填充blob。也许有一个函数可以将多个RAW连接成BLOB

5 个答案:

答案 0 :(得分:5)

如果使用PL / SQL,显然可以超出这些限制。如果您直接在HEXTORAW语句中执行UPDATE,它也不起作用 - 它需要在单独的语句中完成,如下所示:

DECLARE
  buf RAW(4000); 
BEGIN
  buf := HEXTORAW('C2B97041074...lots of hex...0CC00CD00');
  UPDATE MyTable
     SET blobData = buf
   WHERE ID = 462;
END;

对于我的生活,我永远不会理解甲骨文的一些局限性。它就像一切是它自己的小特例。

答案 1 :(得分:3)

要更新长度超过16383字节的public int GetOrderQuantity(List<int> TransactionNumbers, DateTime FromDate, DateTime ToDate) { List<int> transactions = _context.RP_PART_TRANSACTIONS.Where(trd => TransactionNumbers.Contains(trd.TRANSACTION_NUMBER) && trd.TRANSACTION_DATE >= FromDate && trd.TRANSACTION_DATE <= ToDate).Select(tr => tr.TRANSACTION_NUMBER).ToList(); return _context.RP_PART_TRANSACTION_DETAILS.Where(trd => transactions.Contains(trd.TRANSACTION_NUMBER)).Select(tr => tr.PART_QTY).ToList().Sum(); } ,可能会使用这样的东西(每行的偶数个十六进制数字,最多为32766):

SELECT EMPNO, ENAME, SALARY
  FROM EMP
 WHERE DEPTNO = $P{YOUR_PARAMETER}

现在限制只是语句的大小,这可能是由操作环境强加的(例如SQLPlus,Pro * C,VB,JDBC ......)。对于非常大的语句,PL / SQL也可能因为Diana节点而失败#34;错误。

答案 2 :(得分:0)

这是基于mik的答案,但是当我在每个附加行中使用HEXTORAW时,我发现在他的地方附加了多个十六行的一个洞在每个字符串的开头引入了一个额外的0十六进制字符。当您将该十六进制数据拉回数据库并将其与您认为的内容进行比较时,您会看到这一点。如果十六进制是一个图像,并且您将这些图像字节绑定到Image.Source,如果只有一行附加,则忽略零,但如果您有多行,则为每个块引入此额外字节,破坏您的数据,您无法显示图像。我想对于您要上传的常规文件和其他数据也是如此。

相反,我将所有十六进制附加到CLOB,CLOB将其保持为十六进制字符串,并且具有与BLOB字段相同的4 GB限制。因此,当十六进制字符串大于the 32767 character/byte limit时,只有这个未损坏的字符串才会以RAW写入BLOB:

DECLARE
  buf BLOB; 
  cBuf CLOB;
BEGIN
  dbms_lob.createtemporary(buf, FALSE);
  dbms_lob.createtemporary(cBuf, FALSE);
  dbms_lob.append(cBuf, '0EC1D7FA6B411DA5814');
  --...lots of hex data...
  dbms_lob.append(cBuf, '0EC1D7FA6B411DA5814');
  -- now we append the CLOB of hex to the BLOB as RAW
  dbms_lob.append(buf, HEXTORAW(cBuf));
  UPDATE MyTable
     SET blobData = buf
     WHERE ID = 123;
END;

我的场景是我使用SQLite本质上是一个备份数据库,但我仍然需要一种方法来保持Oracle(我的主数据库)在文件上传时保持同步,同时可以重新建立与它的连接。

作为如何以编程方式构建此SQL的更完整答案,我想我应该展示这一点,因为我使用我的应用程序。我的C#应用​​程序中的代码会将文件的字节放入十六进制,然后我有一个带有上述SQL的字符串变量,我将写入一个文件,一个服务稍后会在连接返回时使用它来更新Oracle。所以这就是我如何分析我如何将我的十六进制输入这个SQL字符串和文件(以及后来的Oracle):

// This is all staged so someone can see how you might go from file
// to bytes to hex
string filePath = txtFilePath.Text; // example of getting file path after
    // OpenFileDialog places ofd.FileName in a textbox called txtFilePath
byte[] byteArray = File.ReadAllBytes(filePath);
string hexString = getHexFromBytes(byteArray); // Google: bytes to hex

// Here is the meat...
if (hexString.Length > 0)
{
    string sqlForOracle = "DECLARE buf BLOB; " + 
        "cBuf CLOB; " +
        "BEGIN " + 
            "dbms_lob.createtemporary(buf, FALSE); " + 
            "dbms_lob.createtemporary(cBuf, FALSE); "; + 
            "dbms_lob.open(buf, dbms_lob.lob_readwrite); ";

    int chunkSize = 32766;
    if (hexString.Length > chunkSize)
    {
        sqlForOracle += "dbms_lob.open(cBuf, dbms_lob.lob_readwrite); ";

        int startIdx = 0;
        decimal hexChunks = decimal.Divide(hexString.Length / chunkSize);
        for (int i = 0; i < hexChunks; i++)
        {
            int remainingHex = hexString.Length - (i * chunkSize);
            if (remainingHex > chunkSize)
                sqlForOracle += "dbms_lob.append(cBuf, '" + hexString.Substring(startIdx, chunkSize + "'); ";
            else
                sqlForOracle += "dbms_lob.append(cBuf, '" + hexString.Substring(startIdx, remainingHex) + "'); ";

            startIdx = startIdx + chunkSize;
        }

        sqlForOracle += "dbms_lob.close(cBuf); ";

        // Now we append the CLOB to the BLOB
        sqlForOracle += "dbms_lob.append(buf, HEXTORAW(cBuf)); ";
    }
    else  // write it straight to BLOB as we are below our chunk limit
        sqlForOracle += "dbms_lob.append(buf, HEXTORAW('" + hexString + "')); ";

    sqlForOracle += "dbms_lob.close(buf); ";
    sqlForOracle += "UPDATE MyTable SET blobDate = buf WHERE ID = 123; END;";
}

sqlForOracle稍后使用FileStreamStreamWriter写入文件,服务会查看该文件是否存在,读取该文件并使用它更新Oracle。

<强>更新

Mik的回答实际上很好,如果你在你的块中使用偶数,那么如果你不需要使用奇数块,我实际上会不必要地引入一个额外的步骤。一个更大的文件(它必须与你的RAM相媲美)因此会不必要地影响性能,因为它在转换之前被写入内存两次(CLOB,然后是BLOB),所以请注意,但我确实想要显示在C#只是如何分解块以及SQL实际如何以编程方式编写。如果您只想使用buf,只需将所有cBuf变量替换为buf,除了您只需要一个dbms_lob.createtemporary()语句,而且显然只需要一组.open() 1}}和.close()标记。

所以,关于这些标签,我还阅读了an "AskTom" forum on Oracle.com,他们说在你的吊杆上添加dbms_lob.open().close()是可选的,在交易时对性能更有利附加数量&gt; 2000(或2000 * 32766 = 65.532 MB),它需要几乎两倍的时间(178.19%)完成,并且从那里变得更糟:当然,它取决于正在处理的文件大小,如果这实际上对你或不。我在上面添加了它们。

答案 3 :(得分:0)

这是我的解决方案,可使用辅助表类型和存储函数将多个RAW压缩为单个BLOB:

create or replace type raws as table of raw(2000);

create or replace function concat_raws(parts in raws) return blob
is
    temp blob;
begin
    if parts is null or parts.count = 0 then
       return null;
    end if;
    dbms_lob.createtemporary(temp, false, dbms_lob.CALL);
    for i in parts.first .. parts.last
    loop
        dbms_lob.append(temp, to_blob(parts(i)));
    end loop;
    return temp;
end;

-- usage example:
select concat_raws(raws(hextoraw('CAFE'), hextoraw('BABE'))) from dual;

如我对Inline BLOB / BINARY data types in SQL / JDBC的其他回答所示,这种方法对于自动生成SQL也很方便。

另请参阅How to concatenate BLOB fields (Oracle)?中的多个BLOB串联

答案 4 :(得分:0)

自Oracle 12c以来的另一种选择是使用https://docs.oracle.com/database/121/REFRN/GUID-D424D23B-0933-425F-BC69-9C0E6724693C.htm#REFRN10321中所述的ALTER SYSTEM SET max_string_size=extended SCOPE=SPFILE;

这会将VARCHAR2RAW的最大大小从2000扩展到32767。

请注意,这需要系统权限,数据库重新启动并涉及一些陷阱:Oracle 12c extended to support varchar2 > 4000 bytes doesn't work for user who is not sysdba