Битовый массив в столбце Oracle - PullRequest
0 голосов
/ 30 января 2019

У меня есть несколько источников информации, которые мне нужно хранить в таблице (вместе с другой информацией).На данный момент я не знаю, какие и сколько источников будет.Источник не требуется бизнес-логикой, а хранится только для исследовательских целей.Кроме того, эта таблица будет использоваться в рабочей среде только один раз для переноса данных, поэтому я хотел бы сохранить решение как можно более простым (то есть не делать должным образом нормализованную структуру таблицы).

Я мог бы создать логический столбецдля каждого источника (как в source1 char(1) default '0', source2 char(1) default '0' и т. д.) Однако мне пришлось бы добавить столбец для каждого нового источника.То, что я хотел бы иметь, это столбец, который является битовым массивом, каждый бит представляет один источник.Это очень похоже на столбец order_status, упомянутый в документации для функции BITAND .

Мой вопрос:

  1. Какой тип данных предпочтителен дляэтот столбец при условии, что будет, скажем, макс.16 источников?NUMBER (2)?
  2. Как мне обновить это поле (например, установить бит номер 3)?Я изучал функции UTL_RAW, но все они (удивление, удивление) ожидают ввода RAW, что делает вещи немного громоздкими.

Я открыт для других идей, так как долго добавляюновый источник не требует изменений в структуре таблицы.(Я знаю, что использование битовых массивов в таблице базы данных редко является хорошей идеей, но это особые обстоятельства, поэтому нет необходимости комментировать это.) Наша БД - 12c (12.1).

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Создание типа объекта:

Установка Oracle :

CREATE TYPE bitarray AS OBJECT(
  data   BLOB,
  len    NUMBER(38,0),
  CONSTRUCTOR FUNCTION bitarray( in_length NUMBER ) RETURN SELF AS RESULT,
  CONSTRUCTOR FUNCTION bitarray( in_data VARCHAR2 ) RETURN SELF AS RESULT,
  MEMBER FUNCTION getBit( in_index NUMBER ) RETURN NUMBER,
  MEMBER FUNCTION setBit( in_index NUMBER, in_value NUMBER ) RETURN bitarray,
  MEMBER FUNCTION toString RETURN CLOB,
  STATIC FUNCTION byteToRaw( in_value BINARY_INTEGER ) RETURN RAW
);
/

CREATE TYPE BODY bitarray AS
  CONSTRUCTOR FUNCTION bitarray( in_length NUMBER ) RETURN SELF AS RESULT
  AS
    p_raw RAW(1) := BITARRAY.BYTETORAW( 0 );
  BEGIN
    DBMS_LOB.CREATETEMPORARY( SELF.DATA, FALSE );
    SELF.LEN  := in_length;
    FOR i IN 1 .. CEIL( in_length / 8 ) LOOP
      DBMS_LOB.WRITEAPPEND( SELF.DATA, 1, p_raw );
    END LOOP;
    RETURN;
  END;

  CONSTRUCTOR FUNCTION bitarray( in_data VARCHAR2 ) RETURN SELF AS RESULT
  AS
    p_value  BINARY_INTEGER := 0;
    p_power  BINARY_INTEGER := 1;
  BEGIN
    SELF.LEN  := LENGTH( in_data );
    DBMS_LOB.CREATETEMPORARY( SELF.DATA, FALSE );
    FOR i IN 1 .. SELF.LEN LOOP
      IF SUBSTR( in_data, i, 1 ) = '1' THEN
        p_value := p_value + p_power;
      END IF;
      IF MOD( i, 8 ) = 0 OR i = SELF.LEN THEN
        DBMS_LOB.WRITEAPPEND( SELF.DATA, 1, BITARRAY.BYTETORAW( p_value ) );
        p_value := 0;
        p_power := 1;
      ELSE
        p_power := p_power * 2;
      END IF;
    END LOOP;
    RETURN;
  END;

  MEMBER FUNCTION getBit( in_index NUMBER ) RETURN NUMBER
  AS
    p_amount     BINARY_INTEGER := 1;
    p_raw        RAW(1);
    p_bit_index  BINARY_INTEGER := MOD( in_index - 1, 8 );
    p_byte_index BINARY_INTEGER := ( in_index - 1 - p_bit_index ) / 8 + 1;
    p_bit_value  BINARY_INTEGER := POWER( 2, p_bit_index );
  BEGIN
    IF in_index IS NULL OR in_index < 1 OR in_index > SELF.LEN THEN
      RETURN NULL;
    END IF;
    DBMS_LOB.READ( SELF.DATA, p_amount, p_byte_index, p_raw );
    RETURN BITAND( UTL_RAW.CAST_TO_BINARY_INTEGER( p_raw ), p_bit_value ) / p_bit_value;
  END;

  MEMBER FUNCTION setBit( in_index NUMBER, in_value NUMBER ) RETURN bitarray
  AS
    p_amount     BINARY_INTEGER := 1;
    p_raw        RAW(1);
    p_bit_index  BINARY_INTEGER := MOD( in_index - 1, 8 );
    p_byte_index BINARY_INTEGER := ( in_index - 1 - p_bit_index ) / 8 + 1;
    p_bit_value  RAW(1)         := BITARRAY.BYTETORAW( POWER( 2, p_bit_index ) );
    p_array      bitarray       := SELF;
  BEGIN
    IF in_index IS NULL OR in_value NOT IN ( 0, 1 ) OR in_index < 1 OR in_index > SELF.LEN THEN
      RETURN p_array;
    END IF;
    DBMS_LOB.READ( SELF.DATA, p_amount, p_byte_index, p_raw );
    IF in_value = 1 THEN
      p_raw := UTL_RAW.BIT_OR( p_raw, p_bit_value );
    ELSE
      p_raw := UTL_RAW.BIT_AND( p_raw, UTL_RAW.BIT_COMPLEMENT( p_bit_value ) );
    END IF;
    DBMS_LOB.WRITE( p_array.DATA, p_amount, p_byte_index, p_raw );
    RETURN p_array;
  END;

  MEMBER FUNCTION toString RETURN CLOB
  AS
    p_string CLOB := EMPTY_CLOB();
  BEGIN
    FOR i IN 1 .. SELF.LEN LOOP
      IF SELF.getBit(i) = 0 THEN
        p_string := p_string || '0';
      ELSIF SELF.getBit(i) = 1 THEN
        p_string := p_string || '1';
      ELSE
        p_string := p_string || '-';
      END IF;
    END LOOP;
    RETURN p_string;
  END;

  STATIC FUNCTION byteToRaw( in_value BINARY_INTEGER ) RETURN RAW
  AS
  BEGIN
    RETURN UTL_RAW.SUBSTR( UTL_RAW.CAST_FROM_BINARY_INTEGER( in_value ), 4, 1 );
  END;
END;
/

Запрос :

Затем вы можете использоватьэто в SQL:

SELECT BITARRAY(5).toString() AS default_value,
       BITARRAY('10110').toString() AS with_values,
       BITARRAY('10110').setBit(3,0).toString() AS set_values
FROM   DUAL;

Вывод :

DEFAULT_VALUE | WITH_VALUES | SET_VALUES
:------------ | :---------- | :---------
00000         | 10110       | 10010     

Хранение в таблице :

CREATE TABLE table_name ( id INT, bits BITARRAY );
INSERT INTO table_name
SELECT 1, bitarray( 4 ).setBit( 1, 1 ).setBit( 4, 1 ) FROM DUAL UNION ALL
SELECT 1, bitarray( '1011001' ) FROM DUAL;

Затем запросите его, используя:

SELECT id, t.bits.toString() FROM table_name t;

, который выдает:

ID | T.BITS.TOSTRING()
-: | :----------------
 1 | 1001             
 1 | 1011001          

db <> fiddle здесь

0 голосов
/ 30 января 2019

Вместо создания столбца , как насчет создания таблицы ?Он будет содержать два столбца:

  • имя источника
  • логическая информация

Например:

SQL> create table that_table
  2    (source_name varchar2(30),
  3     cb_bool     number(1) default 0 not null
  4    );

Table created.

SQL> insert into that_table
  2    select 'source 1', 0 from dual union all
  3    select 'source 2', 1 from dual union all
  4    select 'source 9', 1 from dual;

3 rows created.

SQL> select * From that_table;

SOURCE_NAME                       CB_BOOL
------------------------------ ----------
source 1                                0
source 2                                1
source 9                                1

SQL>

В отличие от вашегоПо идее, это масштабируется , и на самом деле не имеет значения, сколько существует источников - вы просто INSERT новую (или UPDATE существующую) строку.

...