Thursday, April 09, 2009

Dynamic where conditions in select

I had a problem in work where I would be selecting from tables that are decided on run time and also the where conditions on these tables are to be decided on run time. I found a lot of examples where one could create a dynamic internal table using the RTTS for INTO clause of SELECT. This was only half the solution to my problem. i still have to dynamically create as many where conditions as possible. The syntax of a dynamic select is:

SELECT (columns) INTO CORRESPONDING FIELDS OF TABLE (Internal Table Name)
  FROM (Table Name)
  WHERE (Where String containing conditions)
We have four components in the above SQL query that can be given at run time:
  1. Columns. We don’t worry about this as the clause ‘INTO CORRESPONDING FIELDS’ will only pick the fields that are common in the internal table and the database table.
  2. Internal Table: This is achieved by following the example here.
  3. Table name: The calling service knows what table to select from.
  4. Where string containing conditions: This is the problem that is to be solved!
In SELECT conditions there is a concept of ranges. You can create an internal table of type range that will contain the value to be searched and the operators for searching. I will use this for the solution here.

What I intend to do is let the calling program send me any number of ranges and its respective column name. So there will be an internal table (RANGES_TABLE) of the following structure:
BEGIN OF ranges_tables,
  tablename TYPE tabname16,
  fieldname TYPE fieldname,
  range TYPE REF TO data,
END OF ranges_table.
Taking the example of table T001:

image

Let’s populate RANGES_TABLE with two criteria, ORT01 and LAND1. We want to get table entries where ORT01 = ‘Walldorf’ and LAND1 = ‘DE’.
* 1
* Create range for ORT01
s_ort01-sign = 'I'.
s_ort01-option = 'EQ'.
s_ort01-low = 'Walldorf'.
APPEND s_ort01 TO r_ort01.
* Add entry in the Ranges table
s_ranges_table-tablename = 'T001'.
s_ranges_table-fieldname = 'ORT01'.
CREATE DATA s_ranges_table-range LIKE r_ort01.
ASSIGN s_ranges_table-range->* TO <range>.
<range> = r_ort01.
APPEND s_ranges_table TO t_range_table.
* 2
* Create range for LAND1
s_land1-sign = 'I'.
s_land1-option = 'EQ'.
s_land1-low = 'DE'.
APPEND s_land1 TO r_land1.
* Add entry in the Ranges table
s_ranges_table-tablename = 'T001'.
s_ranges_table-fieldname = 'LAND1'.
CREATE DATA s_ranges_table-range LIKE r_land1.
ASSIGN s_ranges_table-range->* TO <range>.
<range> = r_land1.
APPEND s_ranges_table TO t_range_table.
RANGES_TABLE now has a list of columns that we need to select from with the conditions respectively. But the user can send one column or two columns or (n) columns. This is will be solved by dynamically creating a structure that will have fields referring to different ranges. The structure will look like this:
BEGIN OF select_struc
  field1 TYPE range1
  .
  .
  fieldn TYPE rangen
END OF select_struc
Having creating a structure we create dynamic where conditions. So now the select query will be as follows:
SELECT * INTO TABLE <l_rows>
  FROM (s_ranges_table-tablename)
  WHERE field1 IN select_struc-field1
  .
  .
  AND   fieldn IN select_struc-fieldn
Here is the full list of the method that does all this:

image

METHOD select_from_ranges.
  DATA:
  l_index TYPE i,
  l_t_dyn_field TYPE abap_component_tab,
  l_s_dyn_field LIKE LINE OF l_t_dyn_field,
  l_str_range_name TYPE string,
  l_str_sel_option TYPE string,
  l_o_structdescr TYPE REF TO cl_abap_structdescr,
  l_d_range_str TYPE REF TO data,
  l_s_table_range LIKE LINE OF i_t_range.
  FIELD-SYMBOLS:
    <l_rows> TYPE ANY TABLE,
    <l_dyn_range_str> TYPE ANY,
    <l_range_tbl> TYPE table,
    <l_range_tbl2> TYPE table.
* For all the where conditions sent
* create fields in the select strucuture
  LOOP AT i_t_range INTO l_s_table_range.
    _index = sy-tabix.
    ASSIGN l_s_table_range-range->* TO <l_range_tbl>.
* Create dynamic field
    CLEAR:
      l_s_dyn_field.
l_s_dyn_field-name = l_s_table_range-fieldname.
    l_s_dyn_field-type ?= cl_abap_datadescr=>describe_by_data( <l_range_tbl> ).
    APPEND l_s_dyn_field TO l_t_dyn_field.
    CONCATENATE '<l_dyn_range_str>-' l_s_table_range-fieldname INTO l_str_range_name.
* More than one where requires in AND
    IF l_index > 1.
      CONCATENATE l_str_sel_option 'AND'
       INTO l_str_sel_option SEPARATED BY space.
    ENDIF.
    CONCATENATE l_str_sel_option l_s_table_range-fieldname 'in' l_str_range_name
      INTO l_str_sel_option SEPARATED BY space.
  ENDLOOP.
* Create dynamic structure with fields pointing to different range
  l_o_structdescr = cl_abap_structdescr=>create( p_components = l_t_dyn_field ).
  CREATE DATA l_d_range_str TYPE HANDLE l_o_structdescr.
  ASSIGN l_d_range_str->* TO <l_dyn_range_str>.
* For all the fields in the structure point them to the range tables
  WHILE sy-subrc IS INITIAL.
    ASSIGN COMPONENT sy-index OF STRUCTURE <l_dyn_range_str> TO <l_range_tbl2>.
    IF sy-subrc IS NOT INITIAL.
      EXIT.
    ENDIF.
    READ TABLE i_t_range INTO l_s_table_range INDEX sy-index.
    ASSIGN l_s_table_range-range->* TO <l_range_tbl>.
    <l_range_tbl2> = <l_range_tbl>.
  ENDWHILE.
  ASSIGN c_rows->* TO <l_rows>.
* Select from the table
  SELECT * INTO TABLE <l_rows>
    FROM (l_s_table_range-tablename)
    WHERE (l_str_sel_option).
ENDMETHOD.

No comments: