Wildcards in GROUP BY clause in OpenSQL? - abap

I have a select similar to the one below:
SELECT DISTINCT
SCARR~CARRID,
SCARR~CARRNAME,
MIN( SPFLI~DISTANCE ) AS MIN_DISTANCE
FROM SCARR JOIN SPFLI ON SPFLI~CARRid = SCARR~CARRid
GROUP BY
SCARR~CARRID,
SCARR~CARRNAME
INTO TABLE #DATA(result).
In the real case, these are other tables and I have many more fields in both SELECT and GROUP BY.
Can I simplify the GROUP BY to not have to write again all the fields that are in the SELECT clause from the table SCARR?
I know other options are to use MIN for all the fields of table SCARR except its ID, or not GROUP BY and instead remove duplicates after the select, but I was trying to do something like GROUP BY scarr~*

No, the OpenSQL syntax doesn't support this.

Here is one possible workaround using dynamic approach:
DATA: lcl_struc TYPE REF TO cl_abap_structdescr,
lt_grouped TYPE TABLE OF sflight.
lcl_struc ?= cl_abap_typedescr=>describe_by_name( 'SFLIGHT' ).
DATA(group_by) = REDUCE string( INIT line TYPE char1024 FOR <field> IN lcl_struc->get_components( ) NEXT line = COND #( WHEN line <> space THEN line && `, ` && `SFLIGHT~` && <field>-name ELSE line && `SFLIGHT~` && <field>-name ) ).
SELECT (group_by)
FROM scarr LEFT OUTER JOIN spfli
ON spfli~carrid = scarr~carrid
LEFT OUTER JOIN sflight
ON sflight~carrid = spfli~carrid
AND sflight~connid = spfli~connid
LEFT OUTER JOIN sairport
ON sairport~id = spfli~airpfrom
WHERE scarr~carrid = 'AA'
GROUP BY (group_by)
INTO CORRESPONDING FIELDS OF TABLE #lt_grouped.
Pay attention to several limitations here:
In the above sample I group only by SFLIGHT fields, because when specifying wildcarded fields together with single (SELECT spfli~*, sflight~carrid) the inline result table will have not single fields but sub-structures, so it's more sophisticated for processing or requires explicit declaration. Keep this in mind if you need group by joined tables.
As it stated in help chapter provided by Florian, dynamic group clause requires all-or-nothing:
The columns after SELECT must then be specified either solely as arguments of aggregate functions or only directly. If not, this would raise a handleable exception CX_SY_OPEN_SQL_DB. Invalid syntax raises a handleable exception from the class CX_SY_DYNAMIC_OSQL_ERROR.
So it may be useless in your particular case if you need one-field aggregation, but just in case I put it here.

No is not possible in plain OpenSQL.
You can create a new CDS view for aggregate SPFLI, the view return for each CARRID the minimum distance:
define view Z_CDS_SPFLI_MIN as select from SPFLI
{
carrid,
min( distance ) as min_distance
}
group by carrid
And then you can modify your query like this, without use group-by:
select SCARR~*, Z_CDS_SPFLI_MIN~min_distance
from SCARR
inner join Z_CDS_SPFLI_MIN
on SCARR~carrid = Z_CDS_SPFLI_MIN~carrid.

Related

Outer join for Alias name and column name -Oracle

I had a working sample query earlier in my code as mentioned below.
SELECT DISTINCT
nombre_aplicacion,
APLICACION,
NOMBRE_APLCODE,
DESCRIPCION,
AREAFUNC
FROM (
select **CODAPLICATION nombre_aplicacion**,
APLICACION,
NOMBRE_APLCODE,
DESCRPTION,
AREAFUNC
from admin.VW_APLICACIONES#dblink,
admin.VW_PRODUCTOS#dblink
where **nombre_aplicacion (+) = CODAPLICATION**
)
WHERE 1=1
ORDER BY nombre_aplicacion ASC;
When I try similar type of query with different tables I was getting error as invalid ORA-00904: "NOMBRE_APLICACION": invalid identifier.
If I remove nombre_aplicacion (+) = CODAPLICATION in where condition query is fetching the result. Can any one suggest why I was facing error as its working earlier with sample query and I was getting error? Is this join is valid?
The query is not valid as:
In the inner sub-query you select areafunc and in the outer query you use area which does not appear in the inner sub-query so will not be available.
In the inner sub-query, you define CODAPLICATION to have the alias nombre_aplicacion and then you try to use that alias in the WHERE clause as a join condition; that will not work.
You have not described which column belongs to which table but you want something like:
SELECT DISTINCT
a.codaplication AS nombre_aplicacion,
a.aplicacion,
a.nombre_aplcode,
p.descrption,
p.areafunc
from APLICACIONES a
LEFT OUTER JOIN PRODUCTOS p
ON (a.primary_key_column = p.foreign_key_column)
ORDER BY nombre_aplicacion ASC;
Note: you are going to have to correct the code to give the correct table aliases for each column and give the correct columns for the join condition.

Oracle: can I return a list of lists in query or in procedure?

I try to return list of lists in select query. I can return ODCIVarchar2List by this query:
SELECT sys.ODCIVarchar2List(av.ATTRIBUTE_ID, av.VALUE) l
FROM USERS u
LEFT OUTER JOIN ATTRIBUTES a ON a.CONTRACT_ID = u.CONTRACT_ID
LEFT OUTER JOIN ATTRIBUTE_VALUES av ON (av.USER_ID = u.USER_ID) AND (av.ATTRIBUTE_ID = a.ATTRIBUTE_ID)
WHERE u.USER_ID = '123'
The result is like:
1 {attr1, val1}
2 {attr2, val2}
I want to get:
1 {{attr1, val1},{attr2,val2}}
Is it possible to make something like?
SELECT sys.ODCISomekingOfList(l) ll FROM (query above)
Are there other solutions? I know it's possible in postgresql. Thanks.
Yes, you can declare a user-defined type and nest lists as many times as you want:
CREATE TYPE string_list AS TABLE OF VARCHAR2(100);
CREATE TYPE string_list_list AS TABLE OF string_list;
Then use:
SELECT CAST(
COLLECT(
string_list(av.ATTRIBUTE_ID, av.VALUE)
ORDER BY av.ATTRIBUTE_ID
)
AS string_list_list
) as list_of_lists
FROM USERS u
LEFT OUTER JOIN ATTRIBUTES a
ON a.CONTRACT_ID = u.CONTRACT_ID
LEFT OUTER JOIN ATTRIBUTE_VALUES av
ON av.USER_ID = u.USER_ID
AND av.ATTRIBUTE_ID = a.ATTRIBUTE_ID
WHERE u.USER_ID = '123'
You can also perform the aggregation using JSON or XML functions and return the result in those formats (rather than as nested collections).
Is it possible to make something like?
SELECT sys.ODCISomekingOfList(l) ll FROM (query above)
Theoretically, yes, you could define a user-defined type in the SYS schema as:
CREATE TYPE SYS.ODCISomethingOfList AS VARRAY(32767) OF SYS.ODCIVARCHAR2LIST;
(I made it a VARRAY since the other ODCI types are also VARRAYs rather than nested tables but you could do either.)
Then you can use the query above replacing string_list with SYS.ODCIVARCHAR2LIST and string_list_list with SYS.ODCISomethingOfList.
However, as a general rule, you should NEVER modify the SYS schema as you run the risk of inadvertently breaking the database. Just use user-defined types in your own schema.

How to apply a filter on jsonb array of objects - after aggregating?

I have a the follow select statement:
SELECT
cards.*,
COUNT(cards.*) OVER() AS full_count,
p.printing_information
FROM
cards
LEFT JOIN
(SELECT
pr.card_id, jsonb_agg(to_jsonb(pr)) AS printing_information
FROM
printings pr
GROUP BY
pr.card_id) p ON cards.card_id = p.card_id
WHERE
...
I would like to be able to query on set_id that is within the printings table. I tried to do this within my above select statement by including pr.set_id but it then required a GROUP BY pr.card_id, pr.set_id which then made a row per printing rather than having all printings within the printing_information sub-array.
Unless I can determine how to do above, is it possible to search within the printing_information array of jsonb?
Ideally I would like to be able to do something like:
WHERE p.printing_information->set_id = '123'
Unfortunately I can't do that as it's within an array.
What's the best way to achieve this? I could just do post-processing of the result to strip out unnecessary results, but I feel there must be a better way.
SELECT cards.*
, count(cards.*) over() AS full_count
, p.printing_information
FROM cards
LEFT JOIN (
SELECT pr.card_id, jsonb_agg(to_jsonb(pr)) AS printing_information
FROM printings pr
WHERE pr.set_id = '123' -- HERE!
GROUP BY pr.card_id
) p ON cards.card_id = p.card_id
WHERE ...
This is much cheaper than filtering after the fact. And can be supported with an index on (set_id) - unlike any attempts to filter on the dynamically generated jsonb column.
This is efficient, while we need to aggregate all or most rows from table printings anyway. But your added WHERE ... implies more filters on the outer SELECT. If that results in only few rows from printings being needed, a LATERAL subquery should be more efficient:
SELECT cards.*
, count(cards.*) OVER() AS full_count
, p.printing_information
FROM cards c
CROSS JOIN LATERAL (
SELECT jsonb_agg(to_jsonb(pr)) AS printing_information
FROM printings pr
WHERE pr.card_id = c.card_id
AND pr.set_id = '123' -- here!
) p
WHERE ... -- selective filter here?!?
See:
What is the difference between LATERAL JOIN and a subquery in PostgreSQL?
Aside, there is no "array of jsonb" here. The subquery produces a jsonb containing an array.

Inner join on fields with different domains

I have a query which looks a bit like this:
SELECT a~matnr AS material,
b~aedat AS date,
SUM( c~ormng ) AS dummy
INTO TABLE #gt_dummy
FROM ekpo AS a
INNER JOIN ekko AS b
ON b~ebeln = a~ebeln
INNER JOIN lips AS c
ON c~vgbel = a~ebeln
AND c~vgpos = a~ebelp
INNER JOIN likp AS d
ON d~vbeln = c~vbeln
WHERE a~matnr IN #gr_dummy1
AND a~werks IN #gr_dummy2
GROUP BY a~matnr, b~aedat
ORDER BY a~matnr, b~aedat.
It's not going to work because LIPS-VGPOS and EKPO-EBELP have different domains so '00010' in EBELP would be '000010' in VGPOS. An alternative to this would be to just get the EKPO data, use a conversion exit function and then use a FOR ALL ENTRIES query to get the LIPS entries. Then since you can't use SUM and GROUP BY with FOR ALL ENTRIES I would need to do the summations manually.
Of course it's not a huge amount of work to do all this but I'm interested if there's a quicker way to do this e.g. in a single query? Thanks.
EDIT: We're on 7.40
Unfortunately, I only see two possibilities before ABAP 7.50:
FOR ALL ENTRIES as you suggested
Execute "native" SQL directly on the database connected to your ABAP software. This can be done with EXEC SQL or ADBC (class CL_SQL_STATEMENT and so on), or AMDP if your database is HANA.
It's not your version but for ABAP >= 7.50, there are SQL string functions LIKE for instance SUBSTRING:
SELECT a~ebeln, a~ebelp, c~vbeln, c~posnr
FROM ekpo AS a
INNER JOIN lips AS c
ON c~vgbel = a~ebeln
AND substring( c~vgpos, 2, 5 ) = a~ebelp
INTO TABLE #DATA(gt_dummy).
If you have at least ehp5 on 7.40, you can use CDS views in a workaround for FOR ALL ENTRIES with SUM.
Join EKKO and EKPO in OpenSQL
Create a CDS view on LIPS using fields VGBEL, VGPOS, SUM(ORMNG), with GROUP BY on the first two
Call this CDS view with FOR ALL ENTRIES

How to get row count of database table

I am just new in abap language and I am trying to practice an inner join statement but I don't know how whether I will be able to get the number of rows of my select statement before output.
Here's what I want to achieved.
<--------------------------------------- >
< total number of rows > Record(s) found |
Column Header 1|Column Header 2 ..
< data
....
retrieved >
<--------------------------------------- >
Below is my select statement :
SELECT spfli~carrid scarr~carrname sflight~planetype sflight~fldate sflight~price spfli~cityfrom spfli~cityto
INTO (g_carrid ,g_carrname ,g_planetype,g_fldate ,g_price ,g_cityfrom ,g_cityto) FROM spfli
INNER JOIN sflight
ON spfli~carrid = sflight~carrid AND spfli~connid = sflight~connid
INNER JOIN scarr
ON scarr~carrid = spfli~carrid
WHERE spfli~carrid = s_carrid-low.
WRITE: / g_carrname ,g_planetype,g_fldate ,g_price ,g_cityfrom ,g_cityto.
ENDSELECT.
And if you have any advice and idea on how to do this using internal table, please, show me a sample. I just really want to learn. Thank you and God Bless.
The system variable SY-DBCNT should give you the number of rows selected, but only after the select ends.
The alternative to SELECT-ENDSELECT is to select all the rows at once with SELECT INTO TABLE into an internal table (provided you are not selecting too much at once!).
For example:
data: lt_t000 type table of t000.
select * from t000 into table lt_t000.
This will select everything from that table in one go into the internal table. So what you could do is to declare an internal table with all the fields currently in your INTO clause and then specify INTO TABLE for your internal table.
After the SELECT executes, SY-DBCNT will contain the number of selected rows.
Here is a complete example, built around the SELECT statement in your question, which I have not checked for sanity, so I hope it works!
tables: spfli.
select-options: s_carrid for spfli-carrid.
* Definition of the line/structure
data: begin of ls_dat,
carrid type s_carr_id,
carrname type s_carrname,
planetype type s_planetye,
fldate type s_date,
price type s_price,
cityfrom type s_from_cit,
cityto type s_to_city,
end of ls_dat.
* Definition of the table:
data: lt_dat like table of ls_dat.
* Select data
select spfli~carrid scarr~carrname sflight~planetype sflight~fldate sflight~price spfli~cityfrom spfli~cityto
into table lt_dat
from spfli
inner join sflight
on spfli~carrid = sflight~carrid and spfli~connid = sflight~connid
inner join scarr
on scarr~carrid = spfli~carrid
where spfli~carrid = s_carrid-low.
* Output data
write: 'Total records selected', sy-dbcnt.
loop at lt_dat into ls_dat.
write: / ls_dat-carrid, ls_dat-carrname, ls_dat-planetype, ls_dat-fldate, ls_dat-price, ls_dat-cityfrom, ls_dat-cityto.
endloop.
Note: Report (type 1) programs still support the notion of declaring internal tables with header lines for backward compatibility, but this is not encouraged! Hope it works!
If you only need row count without retrieving data itself the following syntax works as well
SELECT COUNT(*)
FROM spfli
INNER JOIN sflight
...
After execution of this query you will be able to get row count value from SY-DBCNT and DB load will be much less than during usual SELECT ... INTO itab.
This is, however, true only if you don't need actual data. If you need both row count and data itself it is not sensible to split this into separate select statement.