Generally when the data source is known and data can be easily retrieved from the from the persistent layers, then data can be modelled by CDS entity but in some instances it is hard to get the related data directly from the persistent layer in such cases custom entity can be used for data model whose runtime is implemented manually in ABAP. Custom entity do not come with a SELECT on the data source.
As an use case- considering in instance in FICA where the DB tables are generated based on BIT class. This example is a good candidate for the custom entity use case.
In FICA convergent invoicing- For BIT class, in run time the different tables are generated by the framework. The below screenshot shows the DB table for one of the BIT class-


The backend table where all the generated artifacts are store for bit class.

We can consider 1/2 tables for the below case-

Below is a custom entity with no select but annotated that the query implementation ABAP class.

The ABAP class must implement interface- IF_RAP_QUERY_PROVIDER.

Create service definition for the view-

Create service binding for the above service and publish it.

Check the service in gateway client- ( service URL can be seen in the service binding)

Test the service with the entity name- and the query implementation SELECT method is executed and we have the data.


The manual query implementation is going to be difficult as it has to handle all the offset/paging/sorting/grouping, etc.
Below is the ABAP code of the SELECT method-
CLASS zcl_bit_class DEFINITION PUBLIC FINAL CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_rap_query_provider .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_BIT_CLASS IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA: lt_data TYPE TABLE OF zce_bit_details.
- calls for io-request
DATA(lt_sorting) = io_request->get_sort_elements( ).
DATA(lo_paging) = io_request->get_paging( ).
DATA(lo_filter) = io_request->get_filter( ). - Handle paging
DATA(lv_offset) = lo_paging->get_offset( ).
DATA(lv_page_size) = lo_paging->get_page_size( ).
DATA(lv_max_rows) = COND #( WHEN lv_page_size = if_rap_query_paging=>page_size_unlimited THEN 0
ELSE lv_page_size ). - Handle aggregations
DATA(lt_req_elements) = io_request->get_requested_elements( ).
DATA(lt_aggr_elements) = io_request->get_aggregation( )->get_aggregated_elements( ).
IF lt_aggr_elements IS NOT INITIAL.
LOOP AT lt_aggr_elements ASSIGNING FIELD-SYMBOL().
DELETE lt_req_elements WHERE table_line = -result_element.
DATA(lv_aggregation) = |{ -aggregation_method }( { -input_element } ) as { -result_element }|.
APPEND lv_aggregation TO lt_req_elements.
ENDLOOP.
ENDIF. DATA(lv_req_elements) = concat_lines_of( table = lt_req_elements sep =,
).
DATA(lt_grouped_element) = io_request->get_aggregation( )->get_grouped_elements( ).
DATA(lv_grouping) = concat_lines_of( table = lt_grouped_element sep =,
). - Handle sorting
DATA(sort_elements) = io_request->get_sort_elements( ).
DATA(lt_sort_criteria) = VALUE string_table( FOR sort_element IN sort_elements
( sort_element-element_name && COND #( WHEN sort_element-descending = abap_true
THENdescending
ELSEascending
) ) ). - DATA(lv_sort_string) = COND #( WHEN lt_sort_criteria IS INITIAL THEN concat_lines_of( table = io_request->get_requested_elements( ) sep =
,
)
ELSE concat_lines_of( table = lt_sort_criteria sep = ‘, ‘ ) ). - DATA(lv_where) = cl_abap_dyn_prg=>escape_quotes_str( lo_filter->get_as_sql_string( ) ).
- SELECT SINGLE * FROM dfkkbixbit_genob
INTO @DATA(ls_table)
WHERE bitcat = ‘CSAP’ AND pgmid = ‘R3TR’ AND obj_type = ‘TABL’ AND bitrectype = ‘IT’ AND bitstatus = ‘4’
AND bit_obj_type = ‘DB’ AND bit4_tab_suffix = ’00’.
IF sy-subrc = 0. SELECT (lv_req_elements) FROM (ls_table-obj_name)
GROUP BY (lv_grouping)
ORDER BY (lv_sort_string)
INTO CORRESPONDING FIELDS OF TABLE @lt_data.
DATA(lv_count) = sy-dbcnt.
IF lv_count > lv_max_rows.
” Select for correct offset and maximum rows for table
SELECT *
FROM @lt_data AS bitdata
ORDER BY (lv_sort_string)
INTO CORRESPONDING FIELDS OF TABLE @lt_data
OFFSET @lv_offset UP TO @lv_max_rows ROWS.
ENDIF.
ENDIF. TRY.
io_response->set_data( it_data = lt_data ).
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( iv_total_number_of_records = lines( lt_data ) ).
ENDIF.
CATCH cx_rap_query_response_set_twic.
ENDTRY. ENDMETHOD.
ENDCLASS.