Calculated date in WHERE condition of CDS view - abap

I'm trying to get a list of valid system status for the notification object, in order to not check all the notifications in the table, I want to execute the selection by checking only the last 2 years of data.
Maybe there is a better solution to my problem, but I'm still curious about this technical limitation. To my knowledge, the system status in SAP are kind of hardcoded and can't be determined per object via any table (SAP could add new system status any moment).
I tried to create the below CDS view, but the function dats_add_months can't be used in the where condition, is there a solution to that? Notice that 7.50 doesn't have session parameter for system date so I use an environment variable:
define view ZNOTIF_SYS_STATUS
with parameters sydat : abap.dats #<Environment.systemField: #SYSTEM_DATE
as select distinct from qmel as notif
inner join jest as notif_status on notif_status.objnr = notif.objnr
and notif_status.inact = ''
inner join tj02t as sys_status on sys_status.istat = notif_status.stat
and sys_status.spras = $session.system_language
{
key sys_status.txt04 as statusID,
sys_status.txt30 as description
} where notif.erdat > dats_add_months($parameters.sydat, -12, '') //THIS CAN'T BE USED!!

Putting built-in functions in RHS position of WHERE is supported only since 7.51 and you have 7.50 as you said. That is why it works for Haojie and not for you.
What can be done here? Possible option is CDS table function which consumes AMDP-class. Consider this sample:
Table function
#EndUserText.label: 'table_func months'
define table function ZTF_MONTHS
with parameters
#Environment.systemField : #SYSTEM_DATE
p_datum : syst_datum
returns {
mandt : abap.clnt;
num : qmnum;
type : qmart;
}
implemented by method zcl_cds_qmel=>get_last_two_years;
AMDP
CLASS zcl_cds_qmel DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
TYPES: tt_statuses TYPE STANDARD TABLE OF qmel.
CLASS-METHODS get_last_two_years FOR TABLE FUNCTION ztf_months.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_cds_qmel IMPLEMENTATION.
METHOD get_last_two_years BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY.
twoyrs := add_months (CURRENT_DATE,-12)
RETURN SELECT mandt, qmnum AS num, qmart AS type FROM qmel WHERE erdat > twoyrs;
ENDMETHOD.
ENDCLASS.
It is very simplified compared to your original task but gives you the idea how to do this.

Related

SAP ABAP: Unable to pass ABAP variables to Table Function parameters

I have created a CDS Table Function with 2 parameters as below below:
define table function ZV_TF_TRGT_GRP_ID
with parameters id_date_fr : dats,
id_date_to : dats
Within an ABAP Class, I have defined two variables as per below:
data: ld_date_low type dats,
ld_date_high type dats.
In the class code, I determine the values for ld_date_low & ld_date_high - for which I ultimately want to pass to the Table Function parameters id_date_fr & id_date_to.
When I call the Table Function in the class using the code below, I get the error message:
"LD_DATE_LOW" is invalid here (due to grammar). contains an invalid character or it is a
select 'I' as sign,
'EQ' as opt,
HCMKTGID as low,
' ' as high
from ZV_TF_TRGT_GRP_ID( id_date_fr = ld_date_low, id_date_to = ld_date_high )
However, if I "hardcode" the values for parameters ld_date_fr and ld_date_to as per below, it is accepted and all works as expected.
select 'I' as sign,
'EQ' as opt,
HCMKTGID as low,
' ' as high
from ZV_TF_TRGT_GRP_ID( id_date_fr = '19000101', id_date_to = '20221231' )
Is it possible to use ABAP variables for Table Function parameters? And if so, where am I going wrong?

Checking against value in a STRING_TABLE in a WHERE clause

I have a procedure with the parameter IT_ATINN:
IMPORTING
REFERENCE(IT_ATINN) TYPE STRING_TABLE
IT_ATINN contains a list of characteristics.
I have the following code:
LOOP AT values_tab INTO DATA(value).
SELECT ( #value-INSTANCE ) AS CUOBJ
FROM IBSYMBOL
WHERE SYMBOL_ID = #value-SYMBOL_ID
AND ATINN ??? "<======== HERE ???
APPENDING TABLE #DATA(ibsymbol_tab).
ENDLOOP.
How can I check if ATINN (in the WHERE clause) is equal to any entry in IT_ATINN?
To achieve what you want (and I assume you want dynamic SELECT fields) you cannot use inline declarations here, both in LOOP and in SELECT:
The structure of the results set must be statically identifiable. The SELECT list and the FROM clause must be specified statically and host variables in the SELECT list must not be generic.
So either you use inline or use dynamics, not both.
Here is the snippet that illustrates Sandra good suggestion:
TYPES: BEGIN OF ty_value_tab,
instance TYPE char18,
symbol_id TYPE id,
END OF ty_value_tab.
DATA: it_atinn TYPE string_table.
DATA: rt_atinn TYPE RANGE OF atinn,
value TYPE ty_value_tab,
values_tab TYPE RANGE OF ty_value_tab,
ibsymbol_tab TYPE TABLE OF ibsymbol.
rt_atinn = VALUE #( FOR value_atinn IN it_atinn ( sign = 'I' option = 'EQ' low = value_atinn ) ).
APPEND VALUE ty_value_tab( instance = 'ATWRT' ) TO values_tab.
LOOP AT values_tab INTO value.
SELECT (value-instance)
FROM ibsymbol
WHERE symbol_id = #value-symbol_id
AND atinn IN #rt_atinn
APPENDING CORRESPONDING FIELDS OF TABLE #ibsymbol_tab.
ENDLOOP.
Overall, it makes no sense select ibsymbol in loop, 'cause it has only 8 fields, so you can easily collect all necessary fields from values_tab and pass them as dynamic fieldstring.
If you wanna use alias CUOBJ for your dynamic field you should add it like this:
LOOP AT values_tab INTO value.
DATA(aliased_value) = value-instance && ` AS cuobj `.
SELECT (aliased_value)
...
Remember, that your alias should exists among ibsymbol fields, otherwise in case of static ibsymbol_tab declaration this statement will throw a short dump.

Dynamic INTO clause in OpenSQL?

I'm attempting to write a program that will grab the content from fields from a table both specified by the user on the selection screen.
For example, the user could specify the fields equnr, b_werk, b_lager from the table eqbs.
I've been able to accomplish this like so:
" Determine list of fields provided by user
DATA(lv_fields) = COND string(
WHEN p_key3 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|{ p_key1 }, { p_key2 }, { p_key3 }, { p_string }|
WHEN p_key2 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|{ p_key1 }, { p_key2 }, { p_string }|
WHEN p_key2 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|{ p_key1 }, { p_string }| ).
DATA: lv_field_tab TYPE TABLE OF line.
APPEND lv_fields TO lv_field_tab.
" Determine table specified by user and prepare for Open SQL query
DATA t_ref TYPE REF TO data.
FIELD-SYMBOLS: <t> TYPE any,
<comp> TYPE any.
CREATE DATA t_ref TYPE (p_table).
ASSIGN t_ref->* TO <t>.
ASSIGN COMPONENT lv_fields OF STRUCTURE <t> TO <comp>.
" Prepare result container
DATA: lt_zca_str_to_char TYPE TABLE OF zca_str_to_char,
ls_zca_str_to_char TYPE zca_str_to_char.
SELECT (lv_field_tab) FROM (p_table) INTO (#ls_zca_str_to_char-key1, #ls_zca_str_to_char-key2, #ls_zca_str_to_char-key3, #ls_zca_str_to_char-string).
APPEND ls_zca_str_to_char TO lt_zca_str_to_char.
ENDSELECT.
This will correctly populate lt_zca_str_to_char with data from the table specified by the user.
However, this implies that the user is always providing p_key1, p_key2, and p_key3. I could perform a different selection statement based on how many key fields the user provides, but what's the fun in that?
I set out to solve this like this:
DATA(lv_results) = COND string(
WHEN p_key3 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|(#ls_zca_str_to_char-key1, #ls_zca_str_to_char-key2, #ls_zca_str_to_char-key3, #ls_zca_str_to_char-string)|
WHEN p_key2 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|(#ls_zca_str_to_char-key1, #ls_zca_str_to_char-key2, #ls_zca_str_to_char-string)|
WHEN p_key2 IS NOT INITIAL AND p_string IS NOT INITIAL THEN
|(#ls_zca_str_to_char-key1, #ls_zca_str_to_char-string)| ).
SELECT (lv_field_tab) FROM (p_table) INTO (#lv_results).
APPEND ls_zca_str_to_char TO lt_zca_str_to_char.
ENDSELECT.
This will activate, and when I get to my Open SQL query (from a Z table, only filling out the first two of three possible key fields), the values are the following:
lv_field_tab = GUID, TEXT_ID, TEXT_DATA (Good)
p_table = ZCR_TRANS_TEXT (Good)
lv_results = (#ls_zca_str_to_char-key1, #ls_zca_str_to_char-key2, #ls_zca_str_to_char-string) (Good, 3 = 3!)
But, since I'm assuming the compiler is seeing (#lv_results) as one single variable, the program dumps with the following error:
The current ABAP program attempted to execute an Open SQL statement
containing a dynamic entry. The parser returned the following error:
"The field list and the INTO list must have the same number of
elements."
Is it possible for me to use the new Open SQL syntax to accomplish my dynamic INTO clause in harmony with my dynamic field list?
The brackets on the INTO do not do what you expect, from the ABAP help:
... INTO (#dobj1, #dobj2, ... )
Effect
If the results set consists of multiple columns or aggregate expressions specified explicitly in the SELECT list, a list of elementary data objects dobj1, dobj2, ... (in parentheses and separated by commas) can be specified after INTO.
In your case you only have one value in there so you can only select one column and the data will be passed in the variable LV_RESULT. Not what you are looking for. Since you want to fill the fields of an existing structure the INTO CORRESPONDING FIELDS OF construct will work here. And you can use TABLE to make your command more efficient as well. This leads to:
SELECT (lv_field_tab) FROM (p_table)
INTO CORRESPONDING FIELDS OF TABLE #lt_zca_str_to_char.
As said previously, you may use INTO CORRESPONDING FIELDS OF ..., but it's not mandatory, it's only for simplifying the code.
So, instead of using CORRESPONDING FIELDS, you may create a structure dynamically (RTTC) with its components corresponding to the columns in LV_FIELD_TAB, and you may then use:
SELECT (lv_field_tab) FROM (p_table) INTO #<structure> ... ENDSELECT.
But of course, as explained by Gert Beukema, you should better do only one SELECT, by creating an internal table dynamically with the same logic as for the structure above, and you may then use:
SELECT (lv_field_tab) FROM (p_table) INTO TABLE #<internal table> ...
Refer to the many examples in the web how to create data objects dynamically with RTTC.
Do not use a fields list for your INTO clause.
Try with
INTO CORRESPONDING FIELDS OF TABLE
must be a FIELD-SYMBOL type any table, and the rest of the logic is up to you (to put the proper information from your generic and almost-empty to your specific destination one).

Handling function module interfaces in bulk?

Is there a way to access the import parameters passed into a function module without addressing them individually? Does ABAP store them in some internal table, so I can work with them by looping through rows in some table, or fields of a structure?
We can use the PATTERN function, knowing only the function module's name, to have ABAP print out the function module's interface for us, so I'm wondering where this information is stored and if I can work with it once the function group has been loaded into memory.
Thanks in advance!
You can use the function module RPY_FUNCTIONMODULE_READ to obtain information about the parameter structure of a function module and then access the parameters dynamically. This has several drawbacks - most noticeably, the user doing so will need (additional) S_DEVELOP authorizations, and logging this way will usually impose a serious performance impact.
I'd rather add the function module parameters to the logging/tracing function manually once - with a sufficiently generic method call, it's not that difficult. I also tend to group individual parameters into structures to facilitate later enhancements.
PARAMETER-TABLE construct exists in ABAP since ancient times, it allows passing params in batch:
One should create two parameter tables of types abap_func_parmbind_tab and abap_func_excpbind_tab and fill them like this:
DATA: ptab TYPE abap_func_parmbind_tab,
etab TYPE abap_func_excpbind_tab,
itab TYPE TABLE OF string.
ptab = VALUE #(
( name = 'FILENAME' kind = abap_func_exporting value = REF #( 'c:\text.txt' ) )
( name = 'FILETYPE' kind = abap_func_exporting value = REF #( 'ASC' ) )
( name = 'DATA_TAB' kind = abap_func_tables value = REF #( itab ) )
( name = 'FILELENGTH' kind = abap_func_importing value = REF #( space ) ) ).
etab = VALUE #( ( name = 'OTHERS' value = 10 ) ) .
CALL FUNCTION 'GUI_DOWNLOAD'
PARAMETER-TABLE ptab
EXCEPTION-TABLE etab.

Parameters in Datalab SQL modules

The parameterization example in the "SQL Parameters" IPython notebook in the datalab github repo (under datalab/tutorials/BigQuery/) shows how to change the value being tested for in a WHERE clause. Is it possible to use a parameter to change the name of a field being SELECT'd on?
eg:
SELECT COUNT(DISTINCT $a) AS n
FROM [...]
After I received the answer below, here is what I have done (with a dummy table name and field name, obviously):
%%sql --module test01
DEFINE QUERY get_counts
SELECT $a AS a, COUNT(*) AS n
FROM [project_id.dataset_id.table_id]
GROUP BY a
ORDER BY n DESC
table = bq.Table('project_id.dataset_id.table_id')
field = table.schema['field_name']
bq.Query(test01.get_counts,a=field).sql
bq.Query(test01.get_counts,a=field).results()
You can use a field from a Schema object (eg. given a table, get a specific field via table.schema[fieldname]).
Or implement a custom object with a _repr_sql_ method. See: https://github.com/GoogleCloudPlatform/datalab/blob/master/sources/lib/api/gcp/bigquery/_schema.py#L49