Определить длину в байтах CLOB/NCLOB с многобайтовой кодировкой

Я работаю с базой данных Oracle и хочу определить длину NCLOB в байтах, используя многобайтовую кодировку (UTF-8).

LENGTHB() не поддерживает CLOB или NCLOBS с многобайтовой кодировкой. Я мог преобразовать NCLOB в BLOB и затем получить его длину. Но разве нет лучшего способа сделать это?


person Dave    schedule 29.08.2013    source источник


Ответы (1)


Oracle хранит CLOB в UTF-16 (или, возможно, ваш NCHARACTER_SET?). Каждый символ хранится как два байта.

Вот как вы можете увидеть необработанные данные:

SQL> CREATE TABLE test_clob (c CLOB, v VARCHAR2(10 CHAR), nv NVARCHAR2(10));

Table created

SQL> INSERT INTO test_clob VALUES ('0123456789', '0123456789', '0123456789');

1 row inserted

SQL> SELECT dbms_rowid.rowid_relative_fno(ROWID),
  2         dbms_rowid.rowid_block_number(ROWID)
  3    FROM test_clob;

DBMS_ROWID.ROWID_RELATIVE_FNO( DBMS_ROWID.ROWID_BLOCK_NUMBER(
------------------------------ ------------------------------
                            13                          94314

SQL> alter system dump datafile 13 block 94314;

System altered

Перейдите в каталог USER_DUMP_DEST и откройте файл трассировки, вы должны увидеть что-то вроде этого:

col  0: [56]
 00 54 00 01 02 0c 80 00 00 02 00 00 00 01 00 00 03 64 c6 a5 00 24 09 00 00
 00 00 00 00 14 00 00 00 00 00 01 00 30 00 31 00 32 00 33 00 34 00 35 00 36
 00 37 00 38 00 39
LOB
Locator:
  Length:        84(56)
  Version:        1
  Byte Length:    2
  LobID: 00.00.00.01.00.00.03.64.c6.a5
  Flags[ 0x02 0x0c 0x80 0x00 ]:
    Type: CLOB 
    [...]
    Inline data[20]
    [...]
col  1: [10]  30 31 32 33 34 35 36 37 38 39
col  2: [20]  00 30 00 31 00 32 00 33 00 34 00 35 00 36 00 37 00 38 00 39

Как видите, CLOB (столбец 0) состоит из нескольких байтов заголовка и тех же байтов необработанных данных, что и столбец NVARCHAR2 UTF-16.

Таким образом, я думаю, вам придется преобразовать CLOB в UTF-8, чтобы определить его длину в этом наборе символов.

Вот пример, который я использовал, используя DBMS_LOB.converttoblob:

SQL> DECLARE
  2     l_clob         CLOB := 'abcdéfghij'; -- the é will take two bytes!
  3     l_blob         BLOB;
  4     l_dest_offset  NUMBER := 1;
  5     l_src_offset   NUMBER := 1;
  6     l_lang_context NUMBER := 0;
  7     l_warning      NUMBER;
  8  BEGIN
  9     dbms_lob.createtemporary(l_blob, FALSE, dbms_lob.call);
 10     dbms_lob.converttoblob(dest_lob     => l_blob,
 11                            src_clob     => l_clob,
 12                            amount       => dbms_lob.lobmaxsize,
 13                            dest_offset  => l_dest_offset,
 14                            src_offset   => l_src_offset,
 15                            blob_csid    => nls_charset_id('AL32UTF8'),
 16                            lang_context => l_lang_context,
 17                            warning      => l_warning);
 18     dbms_output.put_line('byte length:'||dbms_lob.getlength(l_blob));
 19     dbms_lob.freetemporary(l_blob);
 20  END;
 21  /

byte length:11

PL/SQL procedure successfully completed

Вы можете преобразовать любой набор символов с помощью функции nls_charset_id.

person Vincent Malgrat    schedule 29.08.2013
comment
Это то, что я на самом деле делаю, прежде чем мне нужна длина моего NCLOB UTF-8. У меня есть CLOB в кодировке Windows-1252, и я попытался вызвать CONVERT(my_clob, 'UTF8', 'WE8MSWIN1252'), чтобы преобразовать его в NCLOB в UTF-8. Но оказывается, что CONVERT(CLOB, VARCHAR2, VARCHAR2) возвращает какой-то псевдокод UTF-8 CLOB (а не NCLOB) с каким-то странным дополнением, которое может быть UTF-16... - person Dave; 29.08.2013
comment
@dave Я не думаю, что convert будет делать то, что ты думаешь. CONVERT вернет CLOB с тот же необработанный набор символов, что и первый, хотя данные будут другими. Я добавил пример с dbms_lob.converttoblob, который должен работать со всеми clobs. - person Vincent Malgrat; 29.08.2013
comment
Винсент, не знаю почему, но я получаю длину в байтах: 15 для этого примера, где, если я сбрасываю эту строку, я получаю ожидаемую длину 11. Хотя не уверен, почему несоответствие. - person tbone; 29.08.2013
comment
@tbone Вау, это странно =) Какой инструмент/ОС/сеанс nls_character_set вы использовали? И как вы сбрасывали строку? (Я пробовал сбрасывать на клоб, но это не сработало) - person Vincent Malgrat; 29.08.2013
comment
NLS_NCHAR_CHARACTERSET = AL16UTF16, NLS_CHARACTERSET = US7ASCII. Я использовал дамп в строке напрямую (без использования utf-16), выполнив: выберите дамп ('abcdéfghij', 16) из двойного. Если я назначу эту строку переменной nvarchar2 внутри pl/sql, а затем дамп, я получу длину 22 (удвоение 11, что ожидается). Я допускаю, что значение 15 является неожиданным (это было из вашего кода преобразования в BLOB сначала, а затем с использованием dbms_lob.getlength). - person tbone; 29.08.2013
comment
Если я запускаю тот же код, что и выше, но использую AL16UTF16 в converttoblob, то я получаю длину 22. Таким образом, UTF-8 дает длину: 15, а UTF-16 дает 22. Вы должны были запустить свой код, используя US7ASCII в качестве набора символов в кодировке. Преобразование BLOB, я думаю. - person tbone; 29.08.2013
comment
Что касается приведенного выше кода, я получаю длину 11 с NLS_CHARACTERSET WE8MSWIN12523 и NLS_NCHAR_CHARACTERSET UTF8. - person Dave; 30.08.2013
comment
Кажется, что TO_NCLOB() выполняет преобразование из Windows-1252 в UTF-8 с моими кодировками... - person Dave; 30.08.2013
comment
@dave Я думаю, это потому, что é не находится в кодировке US7ASCII, поэтому данные должны быть преобразованы между вашим экраном и VARCHAR2. Тем не менее я не понимаю, почему он показывает 15. - person Vincent Malgrat; 30.08.2013
comment
Я думаю, что это tbone использовал кодировку US7ASCII. Однако я принимаю ваш ответ, так как код с преобразованием в BLOB у меня работал. - person Dave; 30.08.2013