ABAP String Templates - Embedded Expressions with GROUP BY - group-by

I am refering to this question: LOOP AT... GROUP BY with dynamic group key
I also want to do some dynamic GROUP-BY-Aggregations.
I want to dynamize a GROUP-BY-Statement like the following snippet:
WHEN 'vkorg'.
LOOP AT ls_output-merged_data INTO ls_tmp
GROUP BY ( sales_org = ls_tmp-sales_org
group_quantity = GROUP SIZE )
ASCENDING ASSIGNING FIELD-SYMBOL(<fs_tmp_2>).
lt_tmp_dim1 = VALUE #( BASE lt_tmp_dim1 (
sales_org = <fs_tmp_2>-sales_org
group_quantity = <fs_tmp_2>-group_quantity
) ).
ENDLOOP.
Curently I have some smiliar to this:
LOOP AT ls_output-merged_data INTO ls_tmp
GROUP BY COND #( WHEN iv_dim1 EQ 'vkorg' THEN |{ ls_tmp-sales_org }| )
ASCENDING ASSIGNING FIELD-SYMBOL(<total_group>).
LOOP AT GROUP <total_group> ASSIGNING FIELD-SYMBOL(<line_data>).
ASSIGN COMPONENT 'vkorg' OF STRUCTURE <line_data> TO FIELD-SYMBOL(<vkorg>).
EXIT.
ENDLOOP.
ENDLOOP.
The issue here is, that I also need the GROUP SIZE field. However I am not able to put it into the embedded expression like: |{ ls_tmp-sales_org GROUP SIZE }|
I tried several positions and syntax but I was not able to make it work.
Does someone did something similiar before and can help me out?

Thanks to Sandra it works now:
LOOP AT ls_output-merged_data INTO ls_tmp
GROUP BY ( sales_org = COND #( WHEN iv_dim1 EQ 'vkorg' THEN |{ ls_tmp-sales_org }| )
group_quantity = GROUP SIZE )
ASCENDING ASSIGNING FIELD-SYMBOL(<total_group>).
ENDLOOP.

Related

Infoset query SELECT SUM issue

I am trying to sum all quantities (MENGE) lines where PO number (EBELN) AND item number in the corresponding PO (EBELP) and Movement type (BWART) "101" then subtract an equivalent table with Movement type "102" to get the final quantity as the result.
Currently I added 2 custom fields, one for 101 and one for 102 movement, to break it down and see the results. With my current code, the report shows the correct data for 101 column, but returns a lot of rubbish for 102: it has the correct data, but it returns numbers/data where there should be none and I can't figure out why or where is it pulling the numbers from.
The code below:
*DATA TAB
DATA: itab1 like table of mseg,
wa1 like mseg,
wa2 like mseg.
DATA: *mseg like table of mseg.
DATA: itab3 like table of ekbe,
wa3 like ekbe.
Data: *ekbe like table of ekbe.
data: QNT101_menge like mseg-menge,
QNT102_menge like mseg-menge,
QNT103_MENGE LIKE EKBE-MENGE.`
*Record Processing TAB
if sy-subrc eq 0.
wa1-ebeln = mseg-ebeln.
wa1-menge = mseg-menge.
wa1-ebelp = mseg-ebelp.
wa1-bwart = mseg-bwart.
wa2-ebeln = mseg-ebeln.
wa2-ebelp = mseg-ebelp.
wa2-menge = mseg-menge.
wa2-bwart = mseg-bwart.
Select: sum( menge ) as menge into QNT101_menge
from mseg
where ebeln = wa1-ebeln
and ebelp = wa1-ebelp
and bwart = 101
group by ebeln ebelp.
endselect.
clear *mseg.
Select sum( menge ) as menge into QNT102_menge
from mseg
where ebeln = wa1-ebeln
and ebelp = wa1-ebelp
and bwart = 102
group by ebeln ebelp.
endselect.
append wa1 to itab1.
clear wa1.
endif.
And my custom fields only have some basic code like
ACTUALQNT2 = QNT102_MENGE.
There are more variables than I use, because I plan on building the report further.
The problem is in the select statements.
You're accessing MSEG with single values of EBELN and EBELP at a time, taken the values from wa1 structure and using an aggregate sum.
You don't need to group by, since you are not accessing with multiple EBELN, EBELP values.
Also the ENDSELECT statement produces a sort of "loop" in MSEG DB table that is not needed.
Try using the following:
Select sum( menge ) as menge into QNT101_menge
from mseg
where ebeln = wa1-ebeln
and ebelp = wa1-ebelp
and bwart = '101'.
Select sum( menge ) as menge into QNT102_menge
from mseg
where ebeln = wa1-ebeln
and ebelp = wa1-ebelp
and bwart = '102'.
Also for performance reasons you should try to not access DB when not needed (you were selecting twice with almost the same criteria).
Here's a better version
Gets a table of quantities summed by movement type (101 or 102)
select bwart, sum( menge ) as menge
from mseg
into table #data(lt_quantity)
where ebeln = #wa1-ebeln
and ebelp = #wa1-ebelp
and bwart in ( '101' , '102' )
group by bwart.
Then reads from internal table the values you want. (here the optional is needed to avoid an execption if the mov.type is not present)
qnt101_menge = value #( lt_quantity[ bwart = '101']-menge optional ).
qnt102_menge = value #( lt_quantity[ bwart = '102']-menge optional ).
EDIT: inline declarations
starting from ABAP 7.40 you can use inline declaration directly into the SELECT statement. (see documentation)
Table lt_quantity is declared on the fly, in the moment the select statement is performed. You don't need to declare the table before, but instead the system creates it with the proper structure dynamically.
To use those new functionalities you must use the # symbol before every variable used (so also when using #wa1)
If your system version does not support this syntax yet, here's the classic version of the statement. Please note the classic SELECT syntax also differs from the new one (no commas between fields, no #, INTO part declared at the bottom of the statement)
types: begin of qty_type,
bwart type mseg-bwart,
menge type mseg-menge,
end of qty_type.
data: lt_quantity type table of qty_type.
select bwart sum( menge ) as menge
from mseg
into table lt_quantity
where ebeln = wa1-ebeln
and ebelp = wa1-ebelp
and bwart in ( '101' , '102' )
group by bwart.

SELECT with substring in ON clause?

I have the following select statement in ABAP:
SELECT munic~mandt VREFER BIS AB ZZELECDATE ZZCERTDATE CONSYEAR ZDIMO ZZONE_M ZZONE_T USAGE_M USAGE_T M2MC M2MT M2RET EXEMPTMCMT EXEMPRET CHARGEMCMT
INTO corresponding fields of table GT_INSTMUNIC_F
FROM ZCI00_INSTMUNIC AS MUNIC
INNER JOIN EVER AS EV on
MUNIC~POD = EV~VREFER(9).
"where EV~BSTATUS = '14' or EV~BSTATUS = '32'.
My problem with the above statement is that does not recognize the substring/offset operation on the 'ON' clause. If i remove the '(9) then
it recognizes the field, otherwise it gives error:
Field ev~refer is unknown. It is neither in one of the specified tables
nor defined by a "DATA" statement. I have also tried doing something similar in the 'Where' clause, receiving a similar error:
LOOP AT gt_instmunic.
clear wa_gt_instmunic_f.
wa_gt_instmunic_f-mandt = gt_instmunic-mandt.
wa_gt_instmunic_f-bis = gt_instmunic-bis.
wa_gt_instmunic_f-ab = gt_instmunic-ab.
wa_gt_instmunic_f-zzelecdate = gt_instmunic-zzelecdate.
wa_gt_instmunic_f-ZZCERTDATE = gt_instmunic-ZZCERTDATE.
wa_gt_instmunic_f-CONSYEAR = gt_instmunic-CONSYEAR.
wa_gt_instmunic_f-ZDIMO = gt_instmunic-ZDIMO.
wa_gt_instmunic_f-ZZONE_M = gt_instmunic-ZZONE_M.
wa_gt_instmunic_f-ZZONE_T = gt_instmunic-ZZONE_T.
wa_gt_instmunic_f-USAGE_M = gt_instmunic-USAGE_M.
wa_gt_instmunic_f-USAGE_T = gt_instmunic-USAGE_T.
temp_pod = gt_instmunic-pod.
SELECT vrefer
FROM ever
INTO wa_gt_instmunic_f-vrefer
WHERE ( vrefer(9) LIKE temp_pod ). " PROBLEM WITH SUBSTRING
"AND ( BSTATUS = '14' OR BSTATUS = '32' ).
ENDSELECT.
WRITE: / sy-dbcnt.
WRITE: / 'wa is: ', wa_gt_instmunic_f.
WRITE: / 'wa-ever is: ', wa_gt_instmunic_f-vrefer.
APPEND wa_gt_instmunic_f TO gt_instmunic_f.
WRITE: / wa_gt_instmunic_f-vrefer.
ENDLOOP.
itab_size = lines( gt_instmunic_f ).
WRITE: / 'Internal table populated with', itab_size, ' lines'.
The basic task i want to implement is to modify a specific field on one table,
pulling values from another. They have a common field ( pod = vrefer(9) ). Thanks in advance for your time.
If you are on a late enough NetWeaver version, it works on 7.51, you can use the OpenSQL function LEFT or SUBSTRING. Your query would look something like:
SELECT munic~mandt VREFER BIS AB ZZELECDATE ZZCERTDATE CONSYEAR ZDIMO ZZONE_M ZZONE_T USAGE_M USAGE_T M2MC M2MT M2RET EXEMPTMCMT EXEMPRET CHARGEMCMT
FROM ZCI00_INSTMUNIC AS MUNIC
INNER JOIN ever AS ev
ON MUNIC~POD EQ LEFT( EV~VREFER, 9 )
INTO corresponding fields of table GT_INSTMUNIC_F.
Note that the INTO clause needs to move to the end of the command as well.
field(9) is a subset operation that is processed by the ABAP environment and can not be translated into a database-level SQL statement (at least not at the moment, but I'd be surprised if it ever will be). Your best bet is either to select the datasets separately and merge them manually (if both are approximately equally large) or pre-select one and use a FAE/IN clause.
They have a common field ( pod = vrefer(9) )
This is a wrong assumption, because they both are not fields, but a field an other thing.
If you really need to do that task through SQL, I'll suggest you to check native SQL sentences like SUBSTRING and check if you can manage to use them within an EXEC_SQL or (better) the CL_SQL* classes.

How to write a query that filters traversed elements

I'm struggling with the following query. For a family tree database, I have a vertex 'Person' and a lightweight edge 'Child', so the edge would go out of a parent and into a child (ie 'child-of'). From a person, I need to get their siblings who share the exact same parents.
I can get all of a persons siblings fairly easy, as follows;
SELECT
FROM (
TRAVERSE out_Child
FROM (
SELECT expand(in_Child)
FROM #11:3
)
WHILE $depth <= 1
)
WHERE $depth = 1
So this gets the parents of the person in question, then gets all the children of the parents. The results might look like the following
#rid in_Child
#11:2 #11:0
#11:3 #11:0, #11:1
#11:4 #11:0, #11:1
#11:5 #11:1
I need to filter these results though, as I only want records that have the exact same parents as #11:3. So in this instance, the query should only return #11:3 and #11:4. If the query were for #11:5, it should return #11:5 only. So basically, the in_Child fields must be the same.
I've tried all sorts of queries such as the following, but the query either doesnt run or doesnt filter.
SELECT
FROM (
SELECT
FROM (
TRAVERSE out_Child
FROM (
SELECT expand(in_Child)
FROM #11:3
)
WHILE $depth <= 1
)
WHERE $depth = 1
)
LET $testinChild = (SELECT expand(in_Child) FROM #11:3)
WHERE in_Child CONTAINSALL $testinChild
Ultimately I would prefer to not do any sub-queries, but if it's required then so be it. I Also tried to use traversedElement(0) function, but it only returns the first record traversed (ie #11:0, but not #11:1), so it can't be used.
Update;
If you copy-paste the following into orientdb console (change the password etc to suit your setup), you will have the same dataset described above.
create database remote:localhost/persondb root pass memory graph
alter database custom useLightweightEdges=true
create class Person extends V
create property Person.name string
create class Child extends E
create vertex Person set name = "Father"
create vertex Person set name = "Mother"
create vertex Person set name = "Child of father only"
create edge Child from #11:0 to #11:2
create vertex Person set name = "Child of father+mother #1"
create edge Child from #11:0 to #11:3
create edge Child from #11:1 to #11:3
create vertex Person set name = "Child of father+mother #2"
create edge Child from #11:0 to #11:4
create edge Child from #11:1 to #11:4
create vertex Person set name = "Child of mother only"
create edge Child from #11:1 to #11:5
Okay, I've found some solutions.
First of all, the way I used CONTAINSALL in the question is not correct, as pointed out to me here. CONTAINSALL does not check that all the items on the 'right' are in the 'left', but actually loops over each item in the 'left' and uses that item in the expression on the 'right'. SO WHERE in_Child CONTAINSALL (sex = 'Male) will filter for records where all of the in_Child records are only Male (ie no females). It's basically checking that in_Child[0:n].sex = 'Male'.
So I tried this query;
SELECT
FROM (
SELECT
FROM (
TRAVERSE
out('Child')
FROM (
SELECT
expand(in('Child'))
FROM
#11:3
)
WHILE
$depth <= 1
)
WHERE
$depth = 1
)
WHERE
(SELECT expand(in('Child')) from #11:3) CONTAINSALL (#rid in $current.in_Child)
I think OrientDB might have a bug here. The above query return #11:2, #11:3 and #11:4, which doesn't make sense to me. I changed this query slightly...
SELECT
FROM (
SELECT
FROM (
TRAVERSE
out('Child')
FROM (
SELECT
expand(in('Child'))
FROM
#11:3
)
WHILE
$depth <= 1
)
WHERE
$depth = 1
)
LET
$parents = (SELECT expand(in('Child')) from #11:3)
WHERE
$parents CONTAINSALL (#rid in $current.in_Child)
This works better. The above query correctly returns #11:3 and #11:4, but a query on #11:2 or #11:5 also incorrectly includes both #11:3 and #11:4. This makes sense, because it checking the parent rids of eg #11:2 (which is only 1) is in the parents of the rest, which they are. So I added a check to ensure they had the same amount of parents.
SELECT
FROM (
SELECT
FROM (
TRAVERSE
out('Child')
FROM (
SELECT
expand(in('Child'))
FROM
#11:3
)
WHILE
$depth <= 1
)
WHERE
$depth = 1
)
LET
$parents = (SELECT expand(in('Child')) from #11:3)
WHERE
$parents CONTAINSALL (#rid in $current.in_Child)
AND
$parents.size() = in('Child').size()
Now the query is working correctly for all instances. However, I still wasn't happy with this query. I abandonned the use of CONTAINSALL and eventually came up with the following...
SELECT
FROM (
SELECT
FROM (
TRAVERSE
out('Child')
FROM (
SELECT
expand(in('Child'))
FROM
#11:3
)
WHILE
$depth <= 1
)
WHERE
$depth = 1
)
LET
$parents = (SELECT expand(in('Child')) from #11:3)
WHERE
in_Child.asSet() = $parents.asSet()
This appears the best/safest, and is the one I will use.
UPDATE for dynamic number of parents :
SELECT
distinct(#rid)
FROM
(SELECT
expand(intersect)
FROM
(SELECT
in('Child').out('Child') as intersect
FROM
#17:2))
WHERE
in('Child').size() = $parentCount.size[0]
LET $parentCount = (SELECT
in('Child').size() as size
FROM
#17:2)

OrientDB Removing one result set from another using the difference() function

We are using version v.1.7-rc2 of OrientDB, embedded in our application, and I'm struggling to figure out a query for removing one set of results from another set of results.
For a simplified example, we have a class of type "A" which is organized in a directional hierarchy. The class has a "name" attribute defined as a string (referring to areas, regions, counties, cities, etc), and a "parent" edge defining a relationship from the child instances to the parent instances.
I was able to find the intersection of the result sets from the two sub-queries of my hierarchy using the instance() function:
select expand( $1 ) LET $2 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%a%' ), $3 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%o%' ), $1 = intersect( $2, $3 )
I thought I could accomplish the opposite effect if I used the difference() function:
select expand( $1 ) LET $2 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%a%' ), $3 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%o%' ), $1 = difference( $2, $3 )
but it returns zero records, when the sub queries for $2 and $3 run separately return record sets that overlap. What am I failing to understand? I've searched the forums and documentation, but haven't figured it out.
In the end, I want to take vertices found in one result set, and remove from it any vertices found in a second result set. I essentially want the analogous behavior of the SQL EXCEPT operator (https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#EXCEPT_operator).
Any ideas or directions would be extremely helpful!
Regards,
Andrew

Crystal Reports filtering using optional parameters

I'm working on a report in Crystal Reports XI that allows someone to filter help desk tickets using a number of optional dynamic parameters. If I make a selection for each parameter it returns the expected results, but if I leave out any of the parameters it doesn't return anything and when I look at the SQL query it says, "No SQL Query is used because the record selection formula returns no records." I currently have the following code for Record Selection:
{Incident.State:} = "C" and
{Incident.Close Date & Time} in {?BDate} to {?EDate} and
If HasValue({?Group}) Then (
{Groups.Code} = {?Group}
)
and
If HasValue({?Category}) Then (
{Incident.Subject Description} = {?Category}
)
and
If HasValue({?Staff}) Then (
{Incident_Details.Login ID} = {?Staff}
)
and
If HasValue({?Community}) Then (
{Incident.Company Name} = {?Community}
)
To me, this seems like it should work and if I leave out the If statement to verify the parameters have values I get an error so it seems like the HasValue is working properly. Any ideas?
Your problem stems from not explicitly handling the optional parameter configurations. It's easy to run into this problem when using if-statements in the record selection formula. Instead, explicitly handle the cases where the parameters DO and DO NOT have values. Something like this:
...
(not(hasvalue({?Group})) or {Groups.Code}={?Group})
and
(not(hasvalue({?Category})) or {Incident.Subject Description} = {?Category})
and
(not(hasvalue({?Staff})) or {Incident_Details.Login ID} = {?Staff} )
and
(not(hasvalue({?Community})) or {Incident.Company Name} = {?Community})
Doing it this way effectively tells CR to just ignore the parameter if is doesn't have a value, otherwise select records based on what was entered in those parameters.
The Record Selection formula indicates whether a record must be used, or not, by returning true or false. With that in mind, if some value is informed, the formula must return True if it meets some criteria. If nothing is informed on the filter, the formula must always return True.
Try something like this:
{Incident.State:} = "C" and
{Incident.Close Date & Time} in {?BDate} to {?EDate} and
(If HasValue({?Group}) Then (
{Groups.Code} = {?Group}
) else
(True))
and
(If HasValue({?Category}) Then (
{Incident.Subject Description} = {?Category}
) else
(True))
and (
If HasValue({?Staff}) Then (
{Incident_Details.Login ID} = {?Staff}
) else
(True))
and (
If HasValue({?Community}) Then (
{Incident.Company Name} = {?Community}
) else
(True))