6

我们在 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 字节的数据填充它。是否可以?

(我知道如果我可以使用绑定变量,这将是微不足道的——事实上,与该表交互的其他应用程序使用该精确技术——但不幸的是,我无法在这里重构 DB 胆量。只需要将数据放入表中。)

编辑:

一种行不通的有前途的方法是连接 RAW:

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

这避开了字符串长度限制,但似乎 Oracle 对 a 的长度也有一个匹配的内部 2000 字节限制RAW。所以我不能用RAW. 也许有一个函数可以将多个RAWs 连接成一个BLOB.

4

5 回答 5

6

要更新BLOB超过 16383 个字节,可以使用类似这样的方法(每行有偶数个十六进制数字,最多 32766):

DECLARE
  buf BLOB; 
BEGIN
  dbms_lob.createtemporary(buf, FALSE);
  dbms_lob.append(buf, HEXTORAW('0EC1D7FA6B411DA58149'));
  --...lots of hex data...
  dbms_lob.append(buf, HEXTORAW('0EC1D7FA6B411DA58149'));
  UPDATE MyTable
     SET blobData = buf
   WHERE ID = 123;
END;

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

于 2016-01-19T11:56:54.683 回答
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;

在我的一生中,我永远不会理解 Oracle 的一些限制。就好像一切都是它自己的小特例。

于 2013-08-08T16:51:36.717 回答
1

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

相反,我将我所有的十六进制附加到一个 CLOB 中,它将它保存为一个十六进制字符串,并且也具有与 BLOB 字段相同的 4 GB 限制。RAW因此,当十六进制字符串大于32767 个字符/字节限制时,只有这个未损坏的字符串才会写入 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;";
}

sqlForOracleFileStream稍后使用and将其写入文件StreamWriter,服务会查看该文件是否存在,将其读入并使用它更新 Oracle。

更新

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

因此,关于这些标签,我还阅读了 Oracle.com上的“AskTom”论坛,他们说在处理大于 2000(或 2000 * 32766 = 65.532 MB),它需要几乎两倍的时间(178.19%)才能完成,并且从那里变得更糟:当然,这取决于正在处理的文件大小,这对您是否真的有用。我在上面添加了它们。dbms_lob.open().close()

于 2016-12-10T19:53:01.507 回答
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;

这种方法对于自动生成 sql 也很方便,如我对 SQL/JDBC 中的内联 BLOB/BINARY 数据类型的其他回答所示。

另请参阅如何连接 BLOB 字段 (Oracle) 中的多个 BLOB 连接?

于 2020-06-02T21:59:42.013 回答
0

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

这会将大小从 2000 扩展VARCHAR2RAW32767。

请注意,这需要 sys 权限、数据库重新启动并涉及一些问题:Oracle 12c 扩展为支持 varchar2 > 4000 字节不适用于非 sysdba 的用户

于 2020-06-02T22:11:47.587 回答