18.4.8.10 Reading Business Objects with Nested Collections

Define REST Data Source profile columns of type Array to work programmatically with nested collections of BOSS business objects.

When defining an array column, use the relative path from the parent array for the Selector. In the Remote Attribute Name field, enter the full path from the root if that is different from the selector. Then, you can use the APEX_EXEC package to work with the nested arrays by using the open_array() and close_array() functions to set the context of the current array to work with. :

For example, suppose you are working with an Order business object, that has a composed collection of OrderLineItem business objects, each of which has a composed collection of OrderLineFulfillment objects. The following tables shows some example data profile columns from an Orders with Details REST Data Source with static id orders_with_details:

Column Name / Parent Column Data Type Selector / Remote Attribute Name

ORDER_STATUS

VARCHAR2

orderStatus

TOTAL_AMOUNT

NUMBER

totalAmount

ORDERLINES

Array

orderLines.items

ORDERLINES_QUANTITY

ORDERLINES

NUMBER

quantity

orderLines.quantity

ORDERLINES_PRODUCTNAME

ORDERLINES

VARCHAR2

product.productName

orderLines.product.productName

FULFILLMENTS

ORDERLINES

Array

fulfillments.items

orderLines.fulfillments.items

FULLFILLMENTS_QUANTITY

FULFILLMENTS

NUMBER

quantity

orderLines.fulfillments.quantity

FULLFILLMENTS_WAREHOUSE

FULFILLMENTS

VARCHAR2

warehousedProduct.warehouse.name

orderLines.fulfillments.warehousedProduct.warehouse.name

To retrieve the three-level hierarchy of data for an order whose id is passed to a get_order() function, write code like the following example function.

function get_order_exec(
   p_order_id in number)
   return t_order
is
   l_order t_order;
   l_ctx apex_exec.t_context;
   l_additional varchar2(255);
   l_cur_line t_order_line;
   l_ord_line pls_integer;
   l_ful_line pls_integer;
begin
   -- get_order_columns() returns apex_exec.t_columns for all fields
   l_ctx := apex_exec.open_rest_source_query(
      p_static_id               => 'orders_with_details',
      p_columns                 => get_order_columns,
      p_external_filter_expr    => 'orderId = '||p_order_id);
   if apex_exec.next_row(l_ctx) then
      l_order.order_id := apex_exec.get_number(l_ctx, 'ORDERID');
      l_order.tax_amount := apex_exec.get_number(l_ctx, 'TAXAMOUNT');
      l_order.total_amount := apex_exec.get_number(l_ctx, 'TOTALAMOUNT');
      l_order.order_date := apex_exec.get_date(l_ctx, 'ORDERDATE');
      l_order.order_status := apex_exec.get_varchar2(l_ctx, 'ORDERSTATUS');
      l_order.customer_first_name := apex_exec.get_varchar2(l_ctx, 'CUSTOMER_FIRSTNAME');
      l_order.customer_last_name := apex_exec.get_varchar2(l_ctx, 'CUSTOMER_LASTNAME');
      l_order.customer_phone := apex_exec.get_varchar2(l_ctx, 'CUSTOMER_PHONE');
      l_order.customer_email := apex_exec.get_varchar2(l_ctx, 'CUSTOMER_EMAILADDRESS');
      l_order.customer_loyalty_level := apex_exec.get_varchar2(l_ctx, 'CUSTOMER_LOYALTYLEVEL');
      l_additional := apex_exec.get_varchar2(l_ctx, 'SHIPTO_ADDITIONAL');
      l_order.ship_address := apex_exec.get_varchar2(l_ctx, 'SHIPTO_ADDRESS')
                              || case when l_additional is not null then ', ' end
                              || l_additional;
      l_order.ship_city := apex_exec.get_varchar2(l_ctx, 'SHIPTO_CITY');
      l_order.ship_state := apex_exec.get_varchar2(l_ctx, 'SHIPTO_STATE');
      l_order.ship_zip := apex_exec.get_varchar2(l_ctx, 'SHIPTO_ZIP');
      -- open & loop over Order Lines for current order
      apex_exec.open_array(l_ctx,'ORDERLINES');
      while apex_exec.next_array_row(l_ctx) loop
          l_ord_line := l_order.order_lines.count + 1;
          l_order.order_lines(l_ord_line).quantity
               := apex_exec.get_number(l_ctx, 'ORDERLINES_QUANTITY');
          l_order.order_lines(l_ord_line).list_price
               := apex_exec.get_number(l_ctx, 'ORDERLINES_LISTPRICE');
          l_order.order_lines(l_ord_line).amount
               := apex_exec.get_number(l_ctx, 'ORDERLINES_AMOUNT');
          l_order.order_lines(l_ord_line).product_name
               := apex_exec.get_varchar2(l_ctx, 'ORDERLINES_PRODUCTNAME');
          -- open & loop over Order Line Fulfillments for current order line
          apex_exec.open_array(l_ctx,'FULFILLMENTS');
          while apex_exec.next_array_row(l_ctx) loop
              l_ful_line := l_order.order_lines(l_ord_line).fulfillments.count + 1;
              l_order.order_lines(l_ord_line).fulfillments(l_ful_line).quantity
                  := apex_exec.get_number(l_ctx, 'FULFILLMENTS_QUANTITY');
              l_order.order_lines(l_ord_line).fulfillments(l_ful_line).warehouse_name
                  := apex_exec.get_varchar2(l_ctx, 'FULFILLMENTS_WAREHOUSE');
          end loop;
          apex_exec.close_array(l_ctx);
      end loop;
      apex_exec.close_array(l_ctx);
  end if;
  apex_exec.close(l_ctx);
  return l_order;
end get_order_exec;

The code above references a get_order_columns() function shown below. It returns the apex_exec.t_columns table of apex_exec.t_column records defining the columns participating in the query:

function get_order_columns
return apex_exec.t_columns
is
    l_columns apex_exec.t_columns;
begin
    apex_exec.add_column( /* orderId */
        p_columns         => l_columns,
        p_column_name     => 'ORDERID',
        p_data_type       => apex_exec.c_data_type_number);
    apex_exec.add_column( /* orderDate */
        p_columns         => l_columns,
        p_column_name     => 'ORDERDATE',
        p_data_type       => apex_exec.c_data_type_date);
    -- etc. (other top-level columns here)
    apex_exec.add_column( /* orderLines.items */
        p_columns         => l_columns,
        p_column_name     => 'ORDERLINES',
        p_data_type       => apex_exec.c_data_type_array);
    apex_exec.add_column( /* orderLines.product.productName */
        p_columns         => l_columns,
        p_column_name     => 'ORDERLINES_PRODUCTNAME',
        p_data_type       => apex_exec.c_data_type_varchar2,
        p_parent_column_path => '"ORDERLINES"');
    apex_exec.add_column( /* orderLines.quantity */
        p_columns         => l_columns,
        p_column_name     => 'ORDERLINES_QUANTITY',
        p_data_type       => apex_exec.c_data_type_number,
        p_parent_column_path => '"ORDERLINES"');
    apex_exec.add_column( /* orderLines.listPrice */
        p_columns         => l_columns,
        p_column_name     => 'ORDERLINES_LISTPRICE',
        p_data_type       => apex_exec.c_data_type_number,
        p_parent_column_path => '"ORDERLINES"');
    apex_exec.add_column( /* orderLines.amount */
        p_columns         => l_columns,
        p_column_name     => 'ORDERLINES_AMOUNT',
        p_data_type       => apex_exec.c_data_type_number,
        p_parent_column_path => '"ORDERLINES"');
    apex_exec.add_column( /* orderLines.fulfillments.items */
        p_columns         => l_columns,
        p_column_name     => 'FULFILLMENTS',
        p_data_type       => apex_exec.c_data_type_array,
        p_parent_column_path => '"ORDERLINES"');
    apex_exec.add_column( /* orderLines.fulfillments.warehousedProduct.warehouse.name */
        p_columns         => l_columns,
        p_column_name     => 'FULFILLMENTS_WAREHOUSE',
        p_data_type       => apex_exec.c_data_type_varchar2,
        p_parent_column_path => '"ORDERLINES"."FULFILLMENTS"');
    apex_exec.add_column( /* orderLines.fulfillments.quantity */
        p_columns         => l_columns,
        p_column_name     => 'FULFILLMENTS_QUANTITY',
        p_data_type       => apex_exec.c_data_type_number,
        p_parent_column_path => '"ORDERLINES"."FULFILLMENTS"');
    return l_columns;
end get_order_columns;