How to convert my email ids into horizontal comma separated - sql

I have written a query where I am passing multiple values from my front end via POP LOV (Oracle APEX 20.x)
select column_value as val from table(apex_split(:MYIDS));
It will be like this from above query
select column_value as val from table('3456,89000,8976,5678');
My Main query :
SELECT email
FROM student_details
WHERE studid IN (SELECT column_value AS val
FROM TABLE(apex_split(:MYIDS));
My main query gives me below details as an output
xyz#gmail.com
vbh#gmail.com
ghj#gmail.com
uj1#gmail.com
xy2#gmail.com
vb4#gmail.com
g2j#gmail.com
u8i#gmail.com
x3z#gmail.com
v9h#gmail.com
g00j#gmail.com
uj01#gmail.com
But I want this above output as comma seperated in one line like below
xyz#gmail.com,vbh#gmail.com,ghj#gmail.com,uj1#gmail.com,xy2#gmail.com,vb4#gmail.com,g2j#gmail.com,u8i#gmail.com,x3z#gmail.com,v9h#gmail.com,g00j#gmail.com,uj01#gmail.com
I want it by using xmlelement cast method as listagg as some 4000 char length issue

Slightly adjust your "main query":
SELECT listagg(email, ',') within group (order by null)
FROM student_details
WHERE studid IN (SELECT column_value AS val
FROM TABLE(apex_split(:MYIDS));
If you hit listagg's 4000-character in length restriction, switch to xmlagg:
with your_main_query as
(SELECT email
FROM student_details
WHERE studid IN (SELECT column_value AS val
FROM TABLE(apex_split(:MYIDS))
)
SELECT RTRIM (
XMLAGG (XMLELEMENT (e, email || ',') ORDER BY NULL).EXTRACT (
'//text()'),
',') AS result
FROM your_main_query;

Related

How do I select columns based on a string pattern in BigQuery

I have a table in BigQuery with hundreds of columns, and it just happens that I want to select all of them except for those that begin with an underscore. I know how to do a query to select the columns beginning with an underscore using the INFORAMTION_SCHEMA.COLUMNS table, but I can't figure out how I would use this query to select the columns I want. I know BigQuery has EXCEPT but I want to avoid writing out each column that begins with an underscore, and I can't seem to pass to it a subquery or even something like a._*.
Consider below approach
execute immediate (select '''
select * except(''' || string_agg(col) || ''') from your_table
'''
from (
select col
from (select * from your_table limit 1) t,
unnest([struct(translate(to_json_string(t), '{}"', '') as kvs)]),
unnest(split(kvs)) kv,
unnest([struct(split(kv, ':')[offset(0)] as col)])
where starts_with(col, '_')
));
if apply to table like below
it generates below statement
select * except(_c,_e) from your_table
and produces below output

How to convert fields to JSON in Postgresql

I have a table with the following schema (postgresql 14):
message sentiment classification
any text positive mobile, communication
message are only string, phrases.
sentiment is a string, only one word
classification are string but can have 1 to many word comma separated
I would like to create a json field with these columns, like this:
{"msg":"any text", "sentiment":"positive","classification":["mobile,"communication"]}
Also, if possible, is there a way to consider the classification this way:
{"msg":"any text", "sentiment":"positive","classification 1":"mobile","classification 2" communication"}
The first part of question is easy - Postgres provides functions for splitting string and converting to json:
with t(message, sentiment, classification) as (values
('any text','positive','mobile, communication')
)
select row_to_json(x.*)
from (
select t.message
, t.sentiment
, array_to_json(string_to_array(t.classification, ', ')) as classification
from t
) x
The second part is harder - your want json to have variable number of attributes, mixed of grouped and nongrouped data. I suggest to unwind all attributes and then assemble them back (note the numbered CTE is actually not needed if your real table has id - I just needed some column to group by):
with t(message, sentiment, classification) as (values
('any text','positive','mobile, communication')
)
, numbered (id, message, sentiment, classification) as (
select row_number() over (order by null)
, t.*
from t
)
, extracted (id,message,sentiment,classification,index) as (
select n.id
, n.message
, n.sentiment
, l.c
, l.i
from numbered n
join lateral unnest(string_to_array(n.classification, ', ')) with ordinality l(c,i) on true
), unioned (id, attribute, value) as (
select id, concat('classification ', index::text), classification
from extracted
union all
select id, 'message', message
from numbered
union all
select id, 'sentiment', sentiment
from numbered
)
select json_object_agg(attribute, value)
from unioned
group by id;
DB fiddle
Use jsonb_build_object and concatenate the columns you want
SELECT
jsonb_build_object(
'msg',message,
'sentiment',sentiment,
'classification',
string_to_array(classification,','))
FROM mytable;
Demo: db<>fiddle
The second output is definitely not trivial. The SQL code would be much larger and harder to maintain - not to mention that parsing such file also requires a little more effort.
You can use a cte to handle the flattening of the classification attributes and then perform the necessary grouping in the main queries for each problem component:
with cte(r, m, s, k) as (
select row_number() over (order by t.message), t.message, t.sentiment, v.* from tbl t
cross join json_array_elements(array_to_json(string_to_array(t.classification, ', '))) v
)
-- first part --
select json_build_object('msg', t1.message, 'sentiment', t1.sentiment, 'classification', string_to_array(t1.classification, ', ')) from tbl t1
-- second part --
select jsonb_build_object('msg', t1.m, 'sentiment', t1.s)||('{'||t1.g||'}')::jsonb
from (select c.m, c.s, array_to_string(array_agg('"classification '||c.r||'":'||c.k), ', ') g
from cte c group by c.m, c.s) t1

Remove duplicate values from comma separated variable in Oracle

I have a variable (called: all_email_list) which contains 3 email address lists altogether. (I found some similar question but not the same with a proper solution)
Example: test#asd.com, test2#asd.com,test#asd.com,test3#asd.com, test4#asd.com,test2#asd.com (it can contain spaces between comas but not all the time)
The desired output: test#asd.com, test2#asd.com,test3#asd.com,test4#asd.com
declare
first_email_list varchar2(4000);
second_email_list varchar2(4000);
third_email_list varchar2(4000);
all_email_list varchar2(4000);
begin
select listagg(EMAIL,',') into first_email_list from UM_USER a left join UM_USERROLLE b on (a.mynetuser=b.NT_NAME) left join UM_RULES c on (c.id=b.RULEID) where RULEID = 902;
select listagg(EMAIL,',') into second_email_list from table2 where CFT_ID =:P25_CFT_TEAM;
select EMAIL into third_email_list from table3 WHERE :P25_ID = ID;
all_email_list:= first_email_list || ',' || second_email_list || ',' || third_email_list;
dbms_output.put_line(all_email_list);
end;
Any solution to solve this in a simple way? By regex maybe.
Solution description. Use CTE to first split up the list of emails into rows with 1 email address per row (testd_rows). Then select distinct rows (testd_rows_unique) from testd_rows and finally put them back together with listagg. From 19c onwards you can use LISTAGG with the DISTINCT keyword.
set serveroutput on size 999999
clear screen
declare
all_email_list varchar2(4000);
l_unique_email_list varchar2(4000);
begin
all_email_list := 'test#asd.com, test2#asd.com,test#asd.com,test3#asd.com, test4#asd.com,test2#asd.com';
WITH testd_rows(email) AS
(
select regexp_substr (all_email_list, '[^, ]+', 1, rownum) split
from dual
connect by level <= length (regexp_replace (all_email_list, '[^, ]+')) + 1
), testd_rows_unique(email) AS
(
SELECT distinct email FROM testd_rows
)
SELECT listagg(email, ',') WITHIN GROUP (ORDER BY email)
INTO l_unique_email_list
FROM testd_rows_unique;
dbms_output.put_line(l_unique_email_list);
end;
/
test2#asd.com,test3#asd.com,test4#asd.com,test#asd.com
But ... why are you converting rows to a comma separated string and then de-duping it ? Use UNION to take out the duplicate values in a single SELECT statement and do LISTAGG on the values. No regexp needed then. UNION will skip duplicates as opposed to UNION ALL which returns all the rows.
DECLARE
all_email_list varchar2(4000);
BEGIN
WITH all_email (email) AS
(
select email from UM_USER a left join UM_USERROLLE b on (a.mynetuser=b.NT_NAME) left join UM_RULES c on (c.id=b.RULEID) where RULEID = 902
UNION
select email from table2 where CFT_ID =:P25_CFT_TEAM
UNION
select email from table3 WHERE :P25_ID = ID
)
SELECT listagg(email, ',') WITHIN GROUP (ORDER BY email)
INTO all_email_list
FROM all_email;
dbms_output.put_line(all_email_list);
END;
/
You could leverage the apex_string.split table function to simplify the code.
12c+ makes it real clean
select listagg(distinct column_value,',') within group (order by null)
from apex_String.split(replace('test#asd.com, test2#asd.com,test#asd.com,test3#asd.com, test4#asd.com,test2#asd.com'
,' ')
,',')
11g needs a wrapping table() and listagg doesn't support distinct.
select listagg(email,',') within group (order by null)
from
(select distinct column_value email
from table(apex_String.split(replace('test#asd.com, test2#asd.com,test#asd.com,test3#asd.com, test4#asd.com,test2#asd.com',' '),','))
);

Can you order the listagg return value in DB2 SQL?

I would like the listagg return value to be ordered so it's like:
name#email.com | 200000#500000#
Not:
name#email.com | 500000#200000#
Below is some sample code.
I noticed if I put the ('200000', 'name#email.com') line first, it does what I want it to do, but I can't ORDER in a CTE so am a bit lost on how to get the same result.
WITH TAB (client, email) AS
(
VALUES
('500000', 'name#email.com'),
('200000', 'name#email.com')
)
SELECT email, listagg(trim(client) || '#', '')
FROM TAB a
GROUP BY email;
Try the following using within group
WITH TAB (client, email) AS
(
VALUES
('500000', 'name#email.com'),
('200000', 'name#email.com')
)
SELECT email, listagg(trim(client) || '#', '') within group (ORDER BY client)
FROM TAB a
GROUP BY email;

Listagg Overflow function implementation (Oracle SQL)

I am using LISTAGG function for my query, however, it returned an ORA-01489: result of string concatenation is too long error. So I googled that error and found out I can use ON OVERFLOW TRUNCATE and I implemented that into my SQL but now it generates missing right parenthesis error and I can't seem to figure out why?
My query
SELECT DISTINCT cust_id, acct_no, state, language_indicator, billing_system, market_code,
EMAIL_ADDR, DATE_OF_CHANGE, TO_CHAR(DATE_LOADED, 'DD-MM-YYYY') DATE_LOADED,
(SELECT LISTAGG( SUBSTR(mtn, 7, 4),'<br>' ON OVERFLOW TRUNCATE '***' )
WITHIN GROUP (ORDER BY cust_id || acct_no) mtnlist
FROM process.feature WHERE date_loaded BETWEEN TO_DATE('02-08-2018','MM-dd-yyyy')
AND TO_DATE('02-09-2018', 'MM-dd-yyyy') AND cust_id = ffsr.cust_id
AND acct_no = ffsr.acct_no AND filename = 'FEATURE.VB2B.201802090040'
GROUP BY cust_id||acct_no) mtnlist
FROM process.feature ffsr WHERE date_loaded BETWEEN TO_DATE('02-08-2018','MM-dd-yyyy')
AND TO_DATE('02-09-2018','MM-dd-yyyy') AND cust_id BETWEEN 0542185146 AND 0942025571
AND src_ind = 'B' AND filename = 'FEATURE.VB2B.201802090040'
AND letter_type = 'FA' ORDER BY cust_id;
With a little bit of help by XML, you might get it work. Example is based on HR schema.
SQL> select
2 listagg(s.department_name, ',') within group (order by null) result
3 from departments s, departments d;
from departments s, departments d
*
ERROR at line 3:
ORA-01489: result of string concatenation is too long
SQL>
SQL> select
2 rtrim(xmlagg(xmlelement (e, s.department_name || ',')).extract
3 ('//text()').getclobval(), ',') result
4 from departments s, departments d;
RESULT
--------------------------------------------------------------------------------
Administration,Administration,Administration,Administration,Administration,Admin
SQL>
This demo sourced from livesql.oracle.com
-- Create table with 93 strings of different lengths, plus one NULL string. Notice the only ASCII character not used is '!', so I will use it as a delimiter in LISTAGG.
create table strings as
with letters as (
select level num,
chr(ascii('!')+level) let
from dual
connect by level <= 126 - ascii('!')
union all
select 1, null from dual
)
select rpad(let,num,let) str from letters;
-- Note the use of LENGTHB to get the length in bytes, not characters.
select str,
sum(lengthb(str)+1) over(order by str rows unbounded preceding) - 1 cumul_lengthb,
sum(lengthb(str)+1) over() - 1 total_lengthb,
count(*) over() num_values
from strings
where str is not null;
-- This statement implements the ON OVERFLOW TRUNCATE WITH COUNT option of LISTAGG in 12.2. If there is no overflow, the result is the same as a normal LISTAGG.
select listagg(str, '!') within group(order by str) ||
case when max(total_lengthb) > 4000 then
'! ... (' || (max(num_values) - count(*)) || ')'
end str_list
from (
select str,
sum(lengthb(str)+1) over(order by str) - 1 cumul_lengthb,
sum(lengthb(str)+1) over() - 1 total_lengthb,
count(*) over() num_values
from strings
where str is not null
)
where total_lengthb <= 4000
or cumul_lengthb <= 4000 - length('! ... (' || num_values || ')');