Oracle PL/SQL - 通过反射迭代对象表

问题描述 投票:0回答:1

这是另一个问题答案的扩展:https://stackoverflow.com/a/64913702/4351488。在提供的解决方案中,处理原始数据类型,但我还需要迭代处理嵌套集合。为了简单起见,该示例仅提供命名集合的单个实例,但其想法是该实现支持深度嵌套集合。

采用以下数据类型:

CREATE TYPE book_page is OBJECT( 
  book_page       number,
  content         varchar2(4000)
);
/

CREATE TYPE table_of_book_pages IS TABLE OF book_page;
/

CREATE TYPE book is OBJECT(
  title           varchar(50),
  author          varchar(50),
  subject         varchar(100),
  book_id         number,
  first_published DATE,
  book_pages      table_of_book_pages
);
/

CREATE TYPE table_of_books IS TABLE OF book;
/

使用原答案中提供的反射包

CREATE PACKAGE reflection IS
  TYPE type_info IS RECORD(
    prec        PLS_INTEGER,
    scale       PLS_INTEGER,
    len         PLS_INTEGER,
    csid        PLS_INTEGER,
    csfrm       PLS_INTEGER,
    schema_name VARCHAR2(30),
    type_name   VARCHAR2(30),
    version     VARCHAR2(100),
    count       PLS_INTEGER
  );

  TYPE attr_info IS RECORD(
    prec           PLS_INTEGER,
    scale          PLS_INTEGER,
    len            PLS_INTEGER,
    csid           PLS_INTEGER,
    csfrm          PLS_INTEGER,
    attr_elt_type  ANYTYPE,
    aname          VARCHAR2(30)
  );

  FUNCTION get_size(
    p_anydata IN ANYDATA
  ) RETURN PLS_INTEGER;

  FUNCTION get_attr_name_at(
    p_anydata IN ANYDATA,
    p_index   IN PLS_INTEGER DEFAULT 1
  ) RETURN VARCHAR2;

  FUNCTION get_attr_value_at(
    p_anydata IN ANYDATA,
    p_index   IN PLS_INTEGER DEFAULT 1
  ) RETURN VARCHAR2;
END;
/

CREATE OR REPLACE PACKAGE BODY reflection IS
  FUNCTION get_type(
    p_anydata IN ANYDATA
  ) RETURN ANYTYPE
  IS
    v_typeid    PLS_INTEGER;
    v_anytype   ANYTYPE;
    v_type_info REFLECTION.TYPE_INFO;
  BEGIN
    v_typeid := p_anydata.GetType( typ => v_anytype );
    RETURN v_anytype;
  END;

  FUNCTION get_info(
    p_anytype IN ANYTYPE
  ) RETURN type_info
  IS
    v_typeid    PLS_INTEGER;
    v_type_info REFLECTION.TYPE_INFO;
  BEGIN
    v_typeid := p_anytype.GetInfo (
      v_type_info.prec, 
      v_type_info.scale,
      v_type_info.len, 
      v_type_info.csid,
      v_type_info.csfrm,
      v_type_info.schema_name, 
      v_type_info.type_name, 
      v_type_info.version,
      v_type_info.count
    );

    IF v_typeid <> DBMS_TYPES.TYPECODE_OBJECT THEN
      RAISE_APPLICATION_ERROR( -20000, 'Not an object.' );
    END IF;

    RETURN v_type_info;
  END;

  FUNCTION get_size(
    p_anydata IN ANYDATA
  ) RETURN PLS_INTEGER
  IS
  BEGIN
    RETURN Get_Info( Get_Type( p_anydata ) ).COUNT;
  END;
  
  FUNCTION get_attr_name_at(
    p_anydata IN ANYDATA,
    p_index   IN PLS_INTEGER DEFAULT 1
  ) RETURN VARCHAR2
  IS
    v_anydata     ANYDATA := p_anydata;
    v_anytype     ANYTYPE;
    v_type_info   REFLECTION.TYPE_INFO;
    v_output      VARCHAR2(4000);
    v_attr_typeid PLS_INTEGER;
    v_attr_info   REFLECTION.ATTR_INFO;
  BEGIN
    v_anytype := Get_Type( v_anydata );
    v_type_info := Get_Info( v_anytype );
    
    IF p_index < 1 OR p_index > v_type_info.COUNT THEN
      RETURN NULL;
    END IF;
    
    v_anydata.PIECEWISE;
    v_attr_typeid := v_anytype.getAttrElemInfo(
      pos            => p_index,
      prec           => v_attr_info.prec,
      scale          => v_attr_info.scale,
      len            => v_attr_info.len,
      csid           => v_attr_info.csid,
      csfrm          => v_attr_info.csfrm,
      attr_elt_type  => v_attr_info.attr_elt_type,
      aname          => v_attr_info.aname
    );
    RETURN v_attr_info.aname;
  END;
        
  FUNCTION get_attr_value_at(
    p_anydata IN ANYDATA,
    p_index   IN PLS_INTEGER DEFAULT 1
  ) RETURN VARCHAR2
  IS
    v_anydata   ANYDATA := p_anydata;
    v_anytype   ANYTYPE;
    v_type_info REFLECTION.TYPE_INFO;
    v_output    VARCHAR2(4000);
  BEGIN
    v_anytype := Get_Type( v_anydata );
    v_type_info := Get_Info( v_anytype );
        
    IF p_index < 1 OR p_index > v_type_info.COUNT THEN
      RETURN NULL;
    END IF;
    
    v_anydata.PIECEWISE;
    
    FOR i IN 1 .. p_index LOOP
      DECLARE
        v_attr_typeid PLS_INTEGER;
        v_attr_info   REFLECTION.ATTR_INFO;
        v_result_code PLS_INTEGER;
      BEGIN
        v_attr_typeid := v_anytype.getAttrElemInfo(
          pos            => i,
          prec           => v_attr_info.prec,
          scale          => v_attr_info.scale,
          len            => v_attr_info.len,
          csid           => v_attr_info.csid,
          csfrm          => v_attr_info.csfrm,
          attr_elt_type  => v_attr_info.attr_elt_type,
          aname          => v_attr_info.aname
        );

        IF DEBUG THEN
          DBMS_OUTPUT.PUT_LINE(
            'Attribute ' || i || ': '
            || v_attr_info.aname
            || ' (type ' || v_attr_typeid || ')'
          );
        END IF;

        CASE v_attr_typeid
        WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
          DECLARE
            v_value NUMBER;
          BEGIN
            v_result_code := v_anydata.GetNumber( v_value );
            IF i = p_index THEN
              RETURN TO_CHAR( v_value );
            END IF;
          END;
         WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
          DECLARE
            v_value VARCHAR2(4000);
          BEGIN
            v_result_code := v_anydata.GetVarchar2( v_value );
            IF i = p_index THEN
              RETURN v_value;
            END IF;
          END;
         WHEN DBMS_TYPES.TYPECODE_DATE THEN
          DECLARE
            v_value DATE;
          BEGIN
            v_result_code := v_anydata.GetDate( v_value );
            IF i = p_index THEN
              RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
            END IF;
          END;
        ELSE
          RETURN NULL;
        END CASE;
      END;
    END LOOP;
    RETURN NULL;
  END;
END;
/

对于以下输入

DECLARE
   list_of_books table_of_books;
   idx           PLS_INTEGER := 1;
   p_anydata     ANYDATA;
   p_attr_name   VARCHAR2(30);
   p_attr_value  VARCHAR2(4000);
   
BEGIN
  dbms_output.enable;
  
  list_of_books := table_of_books(
    book(
      'First book',
      'Me',
      'Simple Ones',
      94321,
      DATE '1970-01-01',
      table_of_book_pages(
        book_page(
          1,
          'This is the page 1 of the First book'
        ),
        book_page(
          2,
          'This is the page 2 of the First book'
        )
      )
    ),
    book(
      'Second book',
      'You',
      'Intermediate Ones',
      55555,
      DATE '2020-01-01',
      table_of_book_pages(
        book_page(
          1,
          'This is the page 1 of the Second book'
        )
      )
    ),
    book(
      'Third book',
      NULL,
      'Advanced Ones',
      77777,
      DATE '2099-12-31' + INTERVAL '0 23:59:59' DAY TO SECOND,
      table_of_book_pages(
        book_page(
          1,
          'This is the page 1 of the Third book'
        ),
        book_page(
          2,
          'This is the page 2 of the Third book'
        ),
       book_page(
          3,
          'This is the page 3 of the Third book'
        )
      )
    )
  );
  

  FOR book_no IN 1 .. list_of_books.COUNT LOOP
    p_anydata := ANYDATA.ConvertObject( list_of_books(book_no) );
    DBMS_OUTPUT.PUT_LINE( 'Book ' || book_no || ':' );
    FOR attr_no IN 1 .. REFLECTION.get_size( p_anydata ) LOOP      
      p_attr_name  := REFLECTION.get_attr_name_at( p_anydata, attr_no );
      p_attr_value := REFLECTION.get_attr_value_at( p_anydata, attr_no );
      DBMS_OUTPUT.PUT_LINE( '  ' || p_attr_name || ': ' || p_attr_value );

      -- how can I iterate book_pages here without explicitly declaring a table_of_book_pages type variable?
    END LOOP;
  END LOOP;

END;
/

我期望以下输出:

Book 1:
  TITLE: First book
  AUTHOR: Me
  SUBJECT: Simple Ones
  BOOK_ID: 94321
  FIRST_PUBLISHED: 1970-01-01 00:00:00
  BOOK_PAGES:
    PAGE:
      PAGE_ID: 1,
      CONTENT: This is the page 1 of the First book
    PAGE:
      PAGE_ID: 2,
      CONTENT: This is the page 2 of the First book
Book 2:
  TITLE: Second book
  AUTHOR: You
  SUBJECT: Intermediate Ones
  BOOK_ID: 55555
  FIRST_PUBLISHED: 2020-01-01 00:00:00
  BOOK_PAGES:
    PAGE:
      PAGE_ID: 1,
      CONTENT: This is the page 1 of the Second book
Book 3:
  TITLE: Third book
  AUTHOR:
  SUBJECT: Advanced Ones
  BOOK_ID: 77777
  FIRST_PUBLISHED: 2099-12-31 23:59:59
  BOOK_PAGES:
    PAGE:
      PAGE_ID: 1,
      CONTENT: This is the page 1 of the Third book
    PAGE:
      PAGE_ID: 2,
      CONTENT: This is the page 2 of the Third book
    PAGE:
      PAGE_ID: 3,
      CONTENT: This is the page 3 of the Third book

实现的目标是以通用方式处理此问题,支持任何类型的命名集合。如果不显式声明集合数据类型的变量,这可以实现吗?

database oracle-database plsql reflection
1个回答
0
投票

如果不显式声明集合数据类型的变量,这可以实现吗?

不,

ANYDATA
类型有方法:

  • MEMBER FUNCTION GetCollection(
       self         IN ANYDATA,
       col          OUT NOCOPY "<collection_type>")
       RETURN       PLS_INTEGER;
    
  • MEMBER FUNCTION GetObject(
       self         IN ANYDATA,
       obj          OUT NOCOPY "<object_type>")
       RETURN       PLS_INTEGER;
    

它们都要求您传入具有正确数据类型的变量,因此似乎没有任何方法可以通过

ANYDATA
对象执行分段迭代以以通用方式检索对象或集合属性。


这个答案中有一个部分解决方案,但是,正如它所指出的,您需要对数据类型进行硬编码才能使其工作。

© www.soinside.com 2019 - 2024. All rights reserved.