如何填充具有默认值的用户定义记录?

Posted

技术标签:

【中文标题】如何填充具有默认值的用户定义记录?【英文标题】:How do I populate a user-defined record that has default values? 【发布时间】:2016-10-27 15:17:43 【问题描述】:

TL;博士:

如何声明用户定义的记录类型,以便如果我不填充其中一个字段,该字段将使用其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 字段中插入任​​何值;我只填充了 5 个可用字段中的 4 个。

当我尝试编译这个包时,我从包体中收到以下错误:

PL/SQL:ORA-00913:值太多

如果我从 RECORD_OPTION_ATTRIBUTES 声明中删除 option_attributes 字段,包将编译。

如何声明记录类型,以便如果我不为 option_attributes 指定值,该字段将默认为 NULL

【问题讨论】:

为什么你不能在你的选择列表中放一个NULL?它不会增加任何实际开销,并且会满足拥有匹配字段列表的需求。 @HepC:特别是,我在一个已经被广泛使用的包中添加了option_attributes;我需要此更改向后兼容(并且不需要对引用 TABLE_OPTION_ATTRIBUTES 的所有其他包进行编辑)。*通常*,使用默认值更方便。 那是可以理解的。但是,我不相信有一种方法可以批量收集简单的记录类型,而不会在选择中的列数和类型中的字段数完全相同。您可能需要声明一个新的记录类型,而不是使用现有的。 我认为你应该接受打击并重构你现有的包。 根据记录类型创建新记录是完全可以接受的,它将具有默认值。只是用select-statement填充记录类型时不能省略列。 【参考方案1】:

根据Oracle doc AFAIK,“要将记录中的所有字段设置为默认值,请为其分配相同类型的未初始化记录”,这是他们的示例:

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;
/

【讨论】:

【参考方案2】:

使用select [bulk collect] into 语法时不能。你在评论中说:

如果这两种说法都是正确的,那就太疯狂了:1) 用户定义的记录允许您定义默认值,以及 2) 您必须填充用户定义记录的每个字段。

第一个陈述是正确的;仅当您从查询中分配整个记录时,第二个才是正确的。

The documentation says:

对于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 into the record variable那么

对于select_list 中的每一列,记录变量必须有一个对应的、类型兼容的字段。 select_list 中的列必须以与记录字段相同的顺序出现。

它没有明确说明选择列表中的值不能比记录类型,但第二句话暗示了这一点;您碰巧在记录的末尾添加了额外的字段,但没有什么能阻止您将它放在开头,这更明显违反了这一点。没有机制可以指定选择列表中的哪一列映射到记录中的哪个字段,因此您必须以相同的顺序提供完全相同的数字、相同的类型。

查询中的值用于填充记录,始终覆盖默认值。您不能不提供字段值。 (即使您的查询将列值计算为空,它仍然会覆盖默认值;如果您的查询执行SELECT null AS option_name, ...,您将收到 ORA-06502 数字或值错误,因为该字段不为空)。因此,无论有无bulk collect,在使用select into 时,您的默认设置都不适用。

不幸的是,您将添加带有额外字段的新记录和表类型(您将无法将其传递给期望原始类型的过程,因此这可能不实用;您可以添加翻译功能,但这只会让事情变得更糟),或者按照@MartinSchapendonk 的建议,接受打击并修改您现有的代码。

您可能不需要更改仅处理集合/记录的任何内容,因为它们不会查看新字段 - 尽管您可能会进行一些修改,或者根本没有任何意义。而且您不需要更改直接构造记录的任何内容,因为它们将获得默认的空值,即使这是在游标循环中(不会提取到记录变量中)。您只需 (!) 更改从 SQL 查询中填充集合/记录的方式,使用 select intoselect bulk collect intofetch into

【讨论】:

【参考方案3】:
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;

【讨论】:

所以问题不在于我的声明,而在于我如何填充(关联?)数组? 其中一个选项是我演示的方式 - 使用 2 种记录类型并一一分配公共字段。其余字段将具有空值/默认值。但是,如果您不喜欢这种方法,我可以用对象类型演示示例(这在技术上不是记录)【参考方案4】:

这是另一个对象类型的选项,它不是 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.

如您所见,最后两列具有默认值(在这种情况下为 null)。 与您问题中的原始 sql 查询相比,您需要做的就是使用 RECORD_OPTION_ATTRIBUTES() 包装选定的列。

【讨论】:

以上是关于如何填充具有默认值的用户定义记录?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Access VBA 将具有默认值的未绑定文本框的值设置为空字符串

将 NULL 插入具有默认值的 NOT NULL 列

如何将具有默认值的两列添加到配置单元中的现有表?

如何创建具有默认值的对象数组?

如何在表中添加具有默认值的列

使用 Dapper 插入具有默认值的表