Как заполнить пользовательскую запись со значениями по умолчанию?

TL; DR:

Как я могу объявить определяемый пользователем тип записи, чтобы, если я не заполнил одно из полей, это поле соответствовало его DEFAULT ?


Подробности:

В спецификации пакета я определяю следующие типы записей и таблиц:

/* set up a custom datatypes that will allow us to pass an array of values into CCD_UI procedures and functions */
TYPE RECORD_OPTION_ATTRIBUTES IS RECORD(
    option_name             VARCHAR2(200)   NOT NULL DEFAULT 'INVALID NAME"', /* default intentionally breaks HTML */
    option_value            VARCHAR2(200)   NOT NULL DEFAULT 'INVALID VALUE"', /* default intentionally breaks HTML */
    option_selected_ind     NUMBER(1)       NOT NULL DEFAULT '0',
    option_class            VARCHAR2(200)   DEFAULT NULL,
    option_attributes       VARCHAR2(200)   DEFAULT NULL
);

TYPE TABLE_OPTION_ATTRIBUTES IS TABLE OF RECORD_OPTION_ATTRIBUTES
    INDEX BY BINARY_INTEGER;

В теле пакета у меня очень похожая функциональность:

PROCEDURE populate_user_defined_table()
AS

    v_criteria_pairs        TABLE_OPTION_ATTRIBUTES;

BEGIN

    SELECT some_column1 AS option_name, some_column2 AS option_value, some_column3 AS selected_ind,
        some_column4 AS option_class
    BULK COLLECT INTO v_criteria_pairs
    FROM Some_Table
    WHERE some_column='whatever';

END;

Зоркий глаз заметит, что я не вставляю никаких значений в option_attributes поле; Я заполняю только 4 из 5 доступных полей.

Когда я пытаюсь скомпилировать этот пакет, я получаю следующую ошибку от тела пакета:

PL / SQL: ORA-00913: слишком много значений

Если я уроню option_attributes поле из RECORD_OPTION_ATTRIBUTES декларация, пакет скомпилируется.

Как я могу объявить тип записи, чтобы, если я не указал значение для option_attributes , это поле будет по умолчанию NULL ?

4 ответа

AFAIK согласно Oracle doc: "Чтобы установить для всех полей в записи значения по умолчанию, присвойте ей неинициализированную запись того же типа", и это их пример:

DECLARE
   TYPE RecordTyp IS RECORD (field1 NUMBER, 
                         field2 VARCHAR2(32) DEFAULT 'something');
   rec1 RecordTyp;
   rec2 RecordTyp;
BEGIN
-- At first, rec1 has the values you assign.
   rec1.field1 := 100; rec1.field2 := 'something else';
-- Assigning an empty record to rec1
-- resets fields to their default values.
-- Field1 is NULL and field2 is 'something'
-- due to the DEFAULT clause
   rec1 := rec2;
   DBMS_OUTPUT.PUT_LINE
     ('Field1 = ' || NVL(TO_CHAR(rec1.field1),'<NULL>') || ',
      field2 = ' || rec1.field2);
END;
/

Вы не можете при использовании select [bulk collect] into синтаксис. В комментарии вы сказали:

Было бы безумием, если оба эти утверждения верны: 1) пользовательские записи позволяют вам определять значения по умолчанию, и 2) вы должны заполнять каждое поле пользовательской записи.

Первое утверждение верно; вторая верна, только если вы назначаете всю запись из запроса.

В документации сказано:

Для переменной записи типа RECORD начальное значение каждого поля равно NULL, если вы не укажете другое начальное значение для него при определении типа.

Поэтому, если вы создаете переменную записи, устанавливаются значения по умолчанию:

declare
  v_rec RECORD_OPTION_ATTRIBUTES;
begin
  dbms_output.put_line(v_rec.option_name ||':'|| v_rec.option_value
    ||':'|| v_rec.option_selected_ind ||':'|| v_rec.option_class
    ||':'|| v_rec.option_attributes);
end;
/

INVALID NAME":INVALID VALUE":0::

PL/SQL procedure successfully completed.

Затем вы можете переопределить значения по умолчанию, индивидуально установив значения полей.

Если вы выберите в переменной записи, то

Для каждого столбца в select_list переменная записи должна иметь соответствующее, совместимое с типом поле. Столбцы в списке select_list должны отображаться в том же порядке, что и поля записи.

Это прямо не говорит о том, что вы не можете иметь меньше значений в списке выбора, чем тип записи, но второе предложение вроде бы подразумевает это; Вы случайно добавили свое дополнительное поле в конец записи, но ничто не мешало вам поставить его в начале, что более явно нарушило бы это. Не существует механизма для указания того, какой столбец в списке выбора соответствует какому полю в записи, поэтому необходимо указывать точно такое же число, того же типа, в том же порядке.

Значения из запроса используются для заполнения записи, всегда перезаписывая значения по умолчанию. Вы не можете не предоставить значение поля. (Даже если ваш запрос оценивает значение столбца в ноль, это все равно переопределяет значение по умолчанию; если ваш запрос сделал SELECT null AS option_name, ... Вы получите ошибку числа или значения ORA-06502, потому что поле не нулевое). Так что ни одно из ваших значений по умолчанию не применяется при использовании select into, с или без bulk collect,

К сожалению, вы либо добавите новые типы записей и таблиц с дополнительным полем (которое вы не сможете передать процедурам, ожидающим исходные типы, так что это, вероятно, непрактично; вы можете добавить функции перевода, но это просто что еще хуже), или, как предложил @MartinSchapendonk, примите удар и измените существующий код.

Вам может не потребоваться изменять что-либо, что обрабатывает только коллекцию / записи, так как они просто не будут смотреть на новое поле - хотя, вероятно, вы будете вносить некоторые изменения, или не было бы никакого смысла иметь поле вообще. И вам не нужно изменять что-либо, что создает записи напрямую, поскольку они получат значение по умолчанию, равное NULL, даже если это находится в цикле курсора (который не извлекается в переменную записи). Вам только (!) Нужно изменить способ заполнения коллекции / записи из запросов SQL, select into, select bulk collect into, или же fetch into,

TYPE RECORD_OPTION_ATTRIBUTES IS RECORD(
    option_name             VARCHAR2(200)   NOT NULL DEFAULT 'INVALID NAME"', /* default intentionally breaks HTML */
    option_value            VARCHAR2(200)   NOT NULL DEFAULT 'INVALID VALUE"', /* default intentionally breaks HTML */
    option_selected_ind     NUMBER(1)       NOT NULL DEFAULT '0',
    option_class            VARCHAR2(200)   DEFAULT NULL,
    option_attributes       VARCHAR2(200)   DEFAULT NULL
);

TYPE TABLE_OPTION_ATTRIBUTES IS TABLE OF RECORD_OPTION_ATTRIBUTES
    INDEX BY BINARY_INTEGER;



PROCEDURE populate_user_defined_table()
AS
  CURSOS cur IS -- cursor selecting values without last column
  SELECT some_column1 AS option_name, some_column2 AS option_value,some_column3 AS selected_ind, some_column4 AS option_class
   FROM Some_Table
  WHERE some_column='whatever';

 TYPE t_tmp_arr IS TABLE OF cur%rowtype index by pls_integer;

 v_tmp_arr               t_tmp_arr;
 v_criteria_pairs        TABLE_OPTION_ATTRIBUTES;

BEGIN
  open cur;
  fetch cur bulk collect into v_tmp_arr;
  close cur;
  for i in 1..v_tmp_arr.count loop
    -- it's better to wrap it into a function which accepts one type of record and returns another one        
    v_criteria_pairs(i).option_name := v_tmp_arr(i).option_name;
    v_criteria_pairs(i).option_value := v_tmp_arr(i).option_value;
    v_criteria_pairs(i).option_selected_ind := v_tmp_arr(i).option_selected_ind;
    v_criteria_pairs(i).option_class := v_tmp_arr(i).option_class;
  end loop;
END;

Вот еще один вариант с типом объекта, который не является записью PL/SQL, но имеет такое же поведение, плюс дополнительные параметры для инициализации со значениями по умолчанию в конструкторе (используйте выражения и функции PL/SQL):

Определите новый тип с помощью конструктора:

CREATE OR REPLACE TYPE RECORD_OPTION_ATTRIBUTES AS OBJECT(
  option_name             VARCHAR2(200),
  option_value            VARCHAR2(200),
  option_selected_ind     NUMBER(1),
  option_class            VARCHAR2(200),
  option_attributes       VARCHAR2(200),
  constructor function RECORD_OPTION_ATTRIBUTES(
    in_option_name             VARCHAR2    DEFAULT 'INVALID NAME"', /* default intentionally breaks HTML */
    in_option_value            VARCHAR2    DEFAULT 'INVALID VALUE"', /* default intentionally breaks HTML */
    in_option_selected_ind     NUMBER      DEFAULT '0',
    in_option_class            VARCHAR2    DEFAULT NULL,
    in_option_attributes       VARCHAR2    DEFAULT NULL
  )
  return self as result
);

В конструкторе используйте значения по умолчанию и можете использовать сложную логику инициализации. Пожалуйста, имейте в виду, что вы можете иметь несколько конструкторов.

create or replace type body RECORD_OPTION_ATTRIBUTES 
as
  constructor function RECORD_OPTION_ATTRIBUTES(
    in_option_name             VARCHAR2    DEFAULT 'INVALID NAME"', /* default intentionally breaks HTML */
    in_option_value            VARCHAR2    DEFAULT 'INVALID VALUE"', /* default intentionally breaks HTML */
    in_option_selected_ind     NUMBER      DEFAULT '0',
    in_option_class            VARCHAR2    DEFAULT NULL,
    in_option_attributes       VARCHAR2    DEFAULT NULL
  )
  return self as result
  as
  begin
    self.option_name         := in_option_name;
    self.option_value        := in_option_value;
    self.option_selected_ind := in_option_selected_ind;
    self.option_class        := in_option_class;
    self.option_attributes   := in_option_attributes;  
    return;
  end;
end;
/  

Запустим тест sql:

select RECORD_OPTION_ATTRIBUTES(table_name, tablespace_name, ini_trans) 
  from all_tables
 where owner = 'SYS'
   and rownum <= 10;

Результаты проверки:

RECORD_OPTION_ATTRIBUTES(TABLE_NAME,TABLESPACE_NAME,INI_TRANS)(OPTION_NAME, OPTI
--------------------------------------------------------------------------------
RECORD_OPTION_ATTRIBUTES('WRR$_REPLAY_CALL_FILTER', 'SYSAUX', 1, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$EXPRESS', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$AWMD', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$AWCREATE', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$AWCREATE10G', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$AWXML', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('AW$AWREPORT', 'SYSAUX', 4, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('DUAL', 'SYSTEM', 1, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('SYSTEM_PRIVILEGE_MAP', 'SYSTEM', 1, NULL, NULL)
RECORD_OPTION_ATTRIBUTES('TABLE_PRIVILEGE_MAP', 'SYSTEM', 1, NULL, NULL)

10 rows selected.

Как видите, последние 2 столбца имеют значение по умолчанию (в данном случае пустое). По сравнению с исходным SQL-запросом в вашем вопросе все, что вам нужно сделать, - это обернуть выбранные столбцы с помощью RECORD_OPTION_ATTRIBUTES().

Другие вопросы по тегам