ListAgg Over ListAgg - Oracle - sql

I wanted to get some data from multiple rows under same column (SRAV.XYZ) and concat it with other col hence used the listagg query.
SELECT LISTAGG (
REGEXP_SUBSTR (SRAV.XYZ,
'[^:]+$'),
';')
WITHIN GROUP (ORDER BY
REGEXP_SUBSTR (
SRAV.XYZ,
'[^:]+$')) ||';'||SRA.ABC
/*(CASE
WHEN SRA.ABC like 'PROF.TMP' THEN SRA.ABC = 'TMP'
WHEN SRA.ABC like 'PROF' THEN SRA.ABC ='PROF'
ELSE SRA.ABC='EMPLOYEES' END) */
FROM TEST1 SPAEM,
TEST2 SRAV,
TEST3 srm,
TEST4 SRA
WHERE SRAV.RID = srm.RGID
AND SRAV.PID IN
('123RTU23',
'456U43',
'AB4577Y')
AND SRAV.XYZ IS NOT NULL
AND SPAEM.EMPID = srm.SEC_UUID
AND SRAV.PID = SRA.PRID
AND SPAEM.EMPID = 139806
group by ABC
I am able to get the output in the below format:
physics;PROF.TMP
bio;EMPLOYEES
Now, I am having 2 issues that I am unable to handle.
I want the output in the below format:
physics;PROF.TMP,bio;EMPLOYEES
My case when is not working ( hence commented ) when I am trying to concat.
The ideal output would be:
physics;TMP,bio;EMPLOYEES
Any help in this regard.
Regards,

CASE probably doesn't work because of LIKE; the way you put it, it acts as if it was =, actually. Wildcards are missing. Also, syntax you used seems to be wrong (from my point of view). Perhaps you meant to say something like this:
CASE
WHEN SRA.ABC like '%PROF.TMP%' THEN 'TMP'
WHEN SRA.ABC like '%PROF%' THEN 'PROF'
ELSE 'EMPLOYEES'
END
As of listagg over listagg: use your current query as a subquery or as a CTE, and then apply yet another listagg:
with your_current_query as
(select listagg(...) within group over (...) as result_1
from ...
where ...
)
-- apply listagg to result_1
select listagg(result_1, ', ') over (...) as final_result
from your_current_query
That's theory. If you want something more, provide a simple test case.

Related

How to Pass list of words into SQL 'LIKE' operator

Iam trying to pass a list of words into SQL Like operator.
The query is to return column called Customer Issue where Customer Issue matches any word in the above list.
my_list =['air con','no cold air','hot air','blowing hot air']
SELECT customer_comments
FROM table
where customer_comments like ('%air con%') #for single search
How do i pass my_list above?
Regular expression can help here. Other solution is using unnest. Which is given already.
SELECT customer_comments
FROM table
where REGEXP_CONTAINS(lower(customer_comments), r'air con|no cold air|hot air|blowing hot air');
A similiar question was answered on the following, works for SQL Server:
Combining "LIKE" and "IN" for SQL Server
Basically you'll have to chain a bunch of 'OR' conditions.
Based on the post #Jordi shared, I think below query can be an option in BigQuery.
query:
SELECT DISTINCT customer_comments
FROM sample,
UNNEST(['air con','no cold air','hot air','blowing hot air']) keyword
WHERE INSTR(customer_comments, keyword) <> 0;
output:
with sample:
CREATE TEMP TABLE sample AS
SELECT * FROM UNNEST(['air conditioner', 'cold air', 'too hot air']) customer_comments;
Consider below
with temp as (
select ['air con','no cold air','hot air','blowing hot air'] my_list
)
select customer_comments
from your_table, (
select string_agg(item, '|') list
from temp t, t.my_list item
)
where regexp_contains(customer_comments, r'' || list)
There are myriad ways to refactor above based on your specific use case - for example
select customer_comments
from your_table
where regexp_contains(customer_comments, r'' ||
array_to_string(['air con','no cold air','hot air','blowing hot air'], '|')
)

SQL find '%' between %s

I need to find (exclude in fact) any results that contain '%' sign, wherever in a string field. That would mean ... WHERE string LIKE '%%%'. Googling about escaping gave me the following ideas. The first throws syntax error, the second returns rows but there are records actually contain '%'.
1st:
SELECT * FROM table
WHERE string NOT LIKE '%!%%' ESCAPE '!'
///tried with different escape characters
2nd:
SELECT * FROM table
WHERE string NOT LIKE '%[%]%'
Trying on GCP BigQuery.
Try:
SELECT *
FROM table
WHERE string NOT LIKE '%!%%' {ESCAPE '!'}
With curly braces as shown in microsoft sql server docs
Or also:
WITH indata(s) AS (
SELECT 'not excluded'
UNION ALL SELECT '%excluded'
UNION ALL SELECT 'Ex%cluded'
UNION ALL SELECT 'Excluded%'
)
SELECT * FROM indata WHERE INSTR(s,'%') = 0;
-- out s
-- out --------------
-- out not excluded
find (exclude in fact) any results that contain '%'
Consider below simple approach
select *
from your_table
where not regexp_contains(string , '%')

Listagg delimeter is geting printed is there are no values selected

I am using LISTAGG() function in my select statement in a procedure such as
SELECT DISTINCT REPLACE(LISTAGG(a.student1||':'||a.score||'+')
WITHIN GROUP ( ORDER BY a.roll_no) OVER (PARTITION BY a.class)
If my select statement has not null values then i get
for eg: ABC:100
But if the select is empty then i get
for eg: :
I wanted it to be null if there are no rows selected.
The problem is that the expression you're aggregating returns a non-NULL value whether or not the values you're concatenating are NULL. My guess is you want something like
listagg(case when a.student1 is not null
then a.student1||':'||a.score||'+'
else null
end)
If you can have a null value for student1 but a non-NULL value for score, you'd need to adjust the case statement to specify what you want to happen in that case (do you want the ":" and the "+"? Just the "+"?)
Well, your REPLACE is incomplete. Also, is '+' really supposed to be concatenated? What string separates values, then?
REPLACE (
LISTAGG (a.student1 || ':' || a.score, '+') --> removed || for the + sign
WITHIN GROUP (ORDER BY a.roll_no)
OVER (PARTITION BY a.class),
':', '') --> missing arguments for REPLACE
If that's still not what you asked, please, provide sample test data and desired output.

How to easily remove count=1 on aliased field in SQL?

I have the following data in a table:
GROUP1|FIELD
Z_12TXT|111
Z_2TXT|222
Z_31TBT|333
Z_4TXT|444
Z_52TNT|555
Z_6TNT|666
And I engineer in a field that removes the leading numbers after the '_'
GROUP1|GROUP_ALIAS|FIELD
Z_12TXT|Z_TXT|111
Z_2TXT|Z_TXT|222
Z_31TBT|Z_TBT|333 <- to be removed
Z_4TXT|Z_TXT|444
Z_52TNT|Z_TNT|555
Z_6TNT|Z_TNT|666
How can I easily query the original table for only GROUP's that correspond to GROUP_ALIASES with only one Distinct FIELD in it?
Desired result:
GROUP1|GROUP_ALIAS|FIELD
Z_12TXT|Z_TXT|111
Z_2TXT|Z_TXT|222
Z_4TXT|Z_TXT|444
Z_52TNT|Z_TNT|555
Z_6TNT|Z_TNT|666
This is how I get all the GROUP_ALIAS's I don't want:
SELECT GROUP_ALIAS
FROM
(SELECT
GROUP1,FIELD,
case when instr(GROUP1, '_') = 2
then
substr(GROUP1, 1, 2) ||
ltrim(substr(GROUP1, 3), '0123456789')
else
substr(GROUP1 , 1, 1) ||
ltrim(substr(GROUP1, 2), '0123456789')
end GROUP_ALIAS
FROM MY_TABLE
GROUP BY GROUP_ALIAS
HAVING COUNT(FIELD)=1
Probably I could make the engineered field a second time simply on the original table and check that it isn't in the result from the latter, but want to avoid so much nesting. I don't know how to partition or do anything more sophisticated on my case statement making this engineered field, though.
UPDATE
Thanks for all the great replies below. Something about the SQL used must differ from what I thought because I'm getting info like:
GROUP1|GROUP_ALIAS|FIELD
111,222|,111|111
111,222|,222|222
etc.
Not sure why since the solutions work on my unabstracted data in db-fiddle. If anyone can spot what db it's actually using that would help but I'll also check on my end.
Here is one way, using analytic count. If you are not familiar with the with clause, read up on it - it's a very neat way to make your code readable. The way I declare column names in the with clause works since Oracle 11.2; if your version is older than that, the code needs to be re-written just slightly.
I also computed the "engineered field" in a more compact way. Use whatever you need to.
I used sample_data for the table name; adapt as needed.
with
add_alias (group1, group_alias, field) as (
select group1,
substr(group1, 1, instr(group1, '_')) ||
ltrim(substr(group1, instr(group1, '_') + 1), '0123456789'),
field
from sample_data
)
, add_counts (group1, group_alias, field, ct) as (
select group1, group_alias, field, count(*) over (partition by group_alias)
from add_alias
)
select group1, group_alias, field
from add_counts
where ct > 1
;
With Oracle you can use REGEXP_REPLACE and analytic functions:
select Group1, group_alias, field
from (select group1, REGEXP_REPLACE(group1,'_\d+','_') group_alias, field,
count(*) over (PARTITION BY REGEXP_REPLACE(group1,'_\d+','_')) as count from test) a
where count > 1
db-fiddle

How to use cursor with LISTAGG in PL/SQL?

I have used LISTAGG to concatenate data from two different tables to form the following output:
How do I display the above output neatly like this:
I am using ORACLE PL/SQL. I am thinking if this can be done by implementing cursor, but I am not sure how to do it. Or maybe is there any other way to achieve this? Thanks.
Looks like NATION.N_NAME column's datatype is CHAR as those names are blank-padded. I'd switch to VARCAHR2 (if possible) or try with TRIM, e.g.
select ...
listagg(trim(n.n_name), ', ') within group ...
----
this
WITH CTE AS
(SELECT r.REGION_KEY
,r.R_NAME
,LIST_AGG(trim(n.N_NAME),',') WITHIN GROUP (ORDER BY R_NAME) AS REGION_NATION
FROM REGION r
INNER JOIN NATION n
ON r.R_REGION_KEY = n.N_REGIONKEY
GROUP BY r.R_REGION_KEY
,r.R_NAME
)
SELECT REGION_KEY
,R_NAME || ':' || REGION_NATION as REGION_TEXT
FROM CTE