How to get column like results to single string with separator - teradata

I have a silly problem which I'm not able to solve in Teradata.
I have n tables and for each of n tables (n>1000) I have to get the table's columns, which can differ from table to table.
My question is: how can I get the result from one query (something like Select columnName from dbc.columns where tablename = table1) in a string, let's say _vColumns, in order to be able to use later on the value in that sorting (_vColumns) in a dynamical SQL syntax?

I did that long ago using dbc.columns
/*** Rows to concatenated string ***/
/*** Nested version instead of hundreds of CASEs.
Returns a single concatenated string consisting of up to 2048
columnnames ***/
SELECT
databasename
,tablename
,max(case when rnk mod 16 = 0 then ColumnName else '' end) ||
max(case when rnk mod 16 = 1 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 2 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 3 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 4 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 5 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 6 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 7 then ',' || ColumnName else '' end)
as Columns
from
(
sel
databasename
,tablename
,rnk / 16 as rnk
,max(case when rnk mod 16 = 0 then ColumnName else '' end) ||
max(case when rnk mod 16 = 1 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 2 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 3 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 4 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 5 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 6 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 7 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 8 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 9 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 10 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 11 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 12 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 13 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 14 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 15 then ',' || ColumnName else '' end) as ColumnName
from
(
select
databasename
,tablename
,rnk / 16 as rnk
,max(case when rnk mod 16 = 0 then ColumnName else '' end) ||
max(case when rnk mod 16 = 1 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 2 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 3 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 4 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 5 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 6 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 7 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 8 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 9 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 10 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 11 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 12 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 13 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 14 then ',' || ColumnName else '' end) ||
max(case when rnk mod 16 = 15 then ',' || ColumnName else '' end) as ColumnName
from
(
select
databasename
,tablename
,trim(columnName) as ColumnName
,rank() over (partition by databasename, tablename
order by columnid) -1 as rnk
from
dbc.columns
) dt
group by 1,2,3
)dt
group by 1,2,3
)dt
group by 1,2
But, since Td14.10 you should use dbc.ColumnV instead (if you got any column name longer than 30 characters) and then 2048*128 characters will hit the maximum limit of 64000 characters...

Related

Dynamic Cursor with parameterised schema name and using for BULK INSERT in target table

I have source_table in different 22 schemas and need procedure to create for bulk collect and insert into same target table in oracle stored procedure.
I'm trying and not getting records inserted getting error ORA-00911: invalid character but there is all column from select cursor and traget_table are same in order.
CREATE OR REPLACE PROCEDURE proc_bulk_circle(p_limit IN PLS_INTEGER DEFAULT 10000,
p_activity_date IN DATE,
p_circle IN VARCHAR2) AS
CURSOR act_cur IS
SELECT activity_date,
circle
FROM circle_load_control
WHERE activity_date = p_activity_date
AND circle = circle;
TYPE type_i6 IS TABLE OF act_cur%ROWTYPE INDEX BY BINARY_INTEGER;
i_tab6 type_i6;
v_count NUMBER := 0;
lv_circle VARCHAR2(2);
lv_schema VARCHAR2(20);
TYPE rc IS REF CURSOR;
con_sap_cur rc;
TYPE con_sap_resp IS TABLE OF target_table%ROWTYPE INDEX BY BINARY_INTEGER;
i_tab1 con_sap_resp;
lv_sql_stmt VARCHAR2(32767);
BEGIN
IF p_circle = 'MUM'
THEN
lv_circle := 'MU';
lv_schema := 'MUMBAI';
ELSIF p_circle = 'MAH'
THEN
lv_circle := 'MH';
lv_schema := 'MHRSTR';
ELSE
lv_circle := NULL;
END IF;
FOR myindex IN act_cur
LOOP
i_tab6(v_count) := myindex;
v_count := v_count + 1;
END LOOP;
FOR myindex IN i_tab6.first .. i_tab6.last
LOOP
IF i_tab6(myindex).activity_date = p_activity_date
AND i_tab6(myindex).circle = p_circle
THEN
BEGIN
lv_sql_stmt := 'SELECT acc_id code,
cust_id c_id,
addr_1 address2,
addr_2 address3,
addr_3 address4,
(SELECT SUM(abc) FROM ' || lv_schema || '.details WHERE <some condition with t1> GROUP BY <columns>) main_charges,
(SELECT SUM(extra_charge) FROM ' || lv_schema || '.details WHERE <some condition with t1> GROUP BY <columns>) extra_charges
FROM ' || lv_schema || '.main_source_details t1
WHERE t1.activity_date = ''' || p_activity_date || ''';';
OPEN con_sap_cur FOR lv_sql_stmt;
LOOP
FETCH con_sap_cur BULK COLLECT
INTO i_tab1 LIMIT p_limit;
FORALL i IN 1 .. i_tab1.count
INSERT INTO target_table (column list....)
VALUES(I_TAB1(i).col1,......;
EXIT WHEN con_sap_cur%NOTFOUND;
END LOOP;
COMMIT;
CLOSE con_sap_cur;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERR target_table: ' || SQLCODE || '-' || SQLERRM);
END;
ELSE
dbms_output.put_line(p_activity_date || ' DATE IS NOT MATCH');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
END proc_bulk_circle;
/
I believe this comes down to you having a ; in your definition of the sql (see below line)
WHERE t1.activity_date = ''' || p_activity_date || ''';';
when you are defining SQL for dynamic use (and opening a cursor this way is dynamic) you do not include the ;
To show this I have done a shorter example. The below will error in the same way as yours.
declare
v_sql varchar2(100) default 'select ''X'' from dual;';
TYPE rc IS REF CURSOR;
v_cur rc;
type l_tab_type is table of varchar2(1);
l_tab l_tab_type;
begin
open v_cur for v_sql;
loop
fetch v_cur bulk collect into l_tab;
exit;
end loop;
CLOSE v_cur;
end;
/
but simply remove the ; from the line
v_sql varchar2(100) default 'select ''X'' from dual;';
end it all works fine, fixed example below.
declare
v_sql varchar2(100) default 'select ''X'' from dual';
TYPE rc IS REF CURSOR;
v_cur rc;
type l_tab_type is table of varchar2(1);
l_tab l_tab_type;
begin
open v_cur for v_sql;
loop
fetch v_cur bulk collect into l_tab;
exit;
end loop;
CLOSE v_cur;
end;
/
You're doing an awful lot of work here, if your purpose is to insert some rows.
Instead, you could do the insert and select in one go, something like:
CREATE OR REPLACE PROCEDURE proc_bulk_circle(p_activity_date IN DATE,
p_circle IN VARCHAR2) AS
lv_circle VARCHAR2(2);
lv_schema VARCHAR2(20);
v_query CLOB;
e_table_does_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT(e_table_does_not_exist, -00942);
BEGIN
IF p_circle = 'MUM'
THEN
lv_circle := 'MU';
lv_schema := 'MUMBAI';
ELSIF p_circle = 'MAH'
THEN
lv_circle := 'MH';
lv_schema := 'MHRSTR';
END IF;
IF lv_schema IS NOT NULL
THEN
-- asserting the schema name to avoid sql injection
-- also using a bind variable for the activity_daate predicates
v_query := 'INSERT INTO target_table (<column list>)' || CHR(10) ||
' WITH main_dets AS (SELECT acc_id,' || CHR(10) ||
' cust_id,' || CHR(10) ||
' addr_1,' || CHR(10) ||
' addr_2,' || CHR(10) ||
' addr_3,' || CHR(10) ||
' (SELECT SUM(abc) FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.details WHERE <some condition with t1>) main_charges,' || CHR(10) || -- no need for the group by
' (SELECT SUM(extra_charge) FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.details WHERE <some condition with t1>) extra_charges' || CHR(10) || -- no need for the group by
' FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.main_source_details t1' || CHR(10) ||
' WHERE activity_date = :p_activity_date)' || CHR(10) ||
' circles AS (SELECT activity_date,' || CHR(10) ||
' circle' || CHR(10) ||
' FROM circle_load_control' || CHR(10) ||
' WHERE activity_date = :p_activity_date' || CHR(10) ||
' AND circle = circle)' || CHR(10) || -- did you really mean circle = circle here? This is equivalent to 1=1 (unless circle is null) and is therefore pretty irrelevant! If you want to exclude rows with null values, use "circle is not null" instead
' SELECT md.acc_id,' || CHR(10) ||
' md.cust_id,' || CHR(10) ||
' md.addr_1,' || CHR(10) ||
' md.addr_2,' || CHR(10) ||
' md.addr_3,' || CHR(10) ||
' md.main_charges,' || CHR(10) ||
' md.extra_charges' || CHR(10) ||
' FROM main_dets md' || CHR(10) ||
' CROSS JOIN circles c';
EXECUTE v_query USING p_activity_date, p_activity_date;
COMMIT;
ELSE
raise_application_error(-20001, 'Invalid circle specified: "' || p_circle || '"');
END IF;
END proc_bulk_circle;
/
(N.B. untested.)
I've assumed that activity_date and circle in circle_load_control aren't unique; if they are, you could avoid the cross join and just have an implicit cursor to fetch the row prior to doing the IF p_circle = ... checks.

CASE WHEN THEN resolving to zeros in SQLite

I am using SQLite inside my .NET application and I have the following query:
select (CASE IFNULL(CCODE_1, '') when 'STD' then '_' else '' end) +
(CASE IFNULL(CFRRR_CS, '') when '1' then 'F_' when '2' then 'R_' when '3' then 'FR_' else '' end) +
IFNULL(CCODE_1, '') +
(CASE when (IFNULL(CCODE_2, '') = '') then '' else '_' end) +
IFNULL(CCODE_2, '') +
(CASE when (IFNULL(CCODE_3, '') = '') then '' else '_' end) +
IFNULL(CCODE_3, '') as OptionID
from X20_TIRES
The query above produces a single OptionID column of all 0s. Nothing else.
And here is some sample data from X20_TIRES:
I am expecting results such as the following:
R_F41A
R_F41V
etc...
It's clear to me that the case/when/then is breaking down and simply spitting out 0s, but I cannot understand why. What am I doing wrong?
If you want to concatenate all these results from the CASE statements,
you must use the || operator instead of +:
select
(CASE IFNULL(CCODE_1, '') when 'STD' then '_' else '' end) ||
(CASE IFNULL(CFRRR_CS, '') when '1' then 'F_' when '2' then 'R_' when '3' then 'FR_' else '' end) ||
IFNULL(CCODE_1, '') ||
(CASE when (IFNULL(CCODE_2, '') = '') then '' else '_' end) ||
IFNULL(CCODE_2, '') ||
(CASE when (IFNULL(CCODE_3, '') = '') then '' else '_' end) ||
IFNULL(CCODE_3, '') as OptionID
from X20_TIRES

PL/SQL VARCHAR2 Max Length

I can't seem to use a VARCHAR2 of more than 4000 chars in 11G, but I see that I should be able to get to 32776.
PROCEDURE prcName
(piCoSite IN CHAR
,piRaciId IN NUMBER
,piDept IN VARCHAR2
,poRecordset OUT SYS_REFCURSOR) AS
locSql1 VARCHAR2(32000 CHAR);
BEGIN
locSql1 :=
'SELECT' ||
' H.TABLE_HEAD_ID ' ||
' ,H.TABLE_HEAD_DEPT ' ||
' ,H.TABLE_HEAD_DESCRIPTION ' ||
' ,H.TABLE_HEAD_STATUS ' ||
' ,H.TABLE_HEAD_SCOPE ' ||
' ,H.TABLE_HEAD_ORIGINATOR ' ||
' ,H.TABLE_HEAD_SUB_CATEGORY ' ||
' ,H.TABLE_HEAD_HIGH_RISK ' ||
' ,TRIM(C0.CNTCT_FIRSTNAME) || '' '' || TRIM(C0.CNTCT_SURNAME) wsRaisedBy ' ||
' ,(SELECT COUNT(*) FROM TABLE_EMPLOYEES E WHERE E.TABLE_EMPS_CO_SITE = H.TABLE_HEAD_CO_SITE AND E.TABLE_EMPS_ID = H.TABLE_HEAD_ID) wsNoEmployees ' ||
' ,(SELECT COUNT(*) FROM TABLE_TASKS T WHERE T.TABLE_TASK_CO_SITE = H.TABLE_HEAD_CO_SITE AND T.TABLE_TASK_ID = H.TABLE_HEAD_ID) wsNoActions ' ||
' FROM TABLE_HEADER H ' ||
' LEFT JOIN CONTACTS C0 ' ||
' ON C0.CNTCT_CO_SITE = H.TABLE_HEAD_CO_SITE ' ||
' AND C0.CNTCT_CODE = H.TABLE_HEAD_ORIGINATOR ' ||
' WHERE H.TABLE_HEAD_CO_SITE = ''' || piCoSite || '''' ||
' AND (H.TABLE_HEAD_ID = ''' || piRaciId || '''' || ' OR ''' || piRaciId || ''' = 0) ' ||
' AND (H.TABLE_HEAD_DEPT IN (''' || piDept || ''' ) OR ''' || piDept || ''' IS NULL ) ' ||
' AND (H.TABLE_HEAD_ORIGINATOR IN (' || piUser || ' ) OR ''' || piUser || ''' IS NULL ) ' ||
' AND (H.TABLE_HEAD_STATUS IN (' || piStatus || ' ) OR ''' || piStatus || ''' IS NULL ) ' ||
' ORDER BY TABLE_HEAD_ID ';
dbms_output.put_line(locSql1);
OPEN poRecordset FOR locSql1;
When I copy/paste the locSql1 variable it's nowhere near 32000 chars, but it's getting truncated.
Is there something within the Database to change or am I missing something?
Thanks.

Tuning Oracle SQL having NOT EXIST clause

I want to tune below query eliminating NOT EXIST clause specified in it. Can you please help.
GLT_temp_upload is temporary table where as DA_DUEDATE is partitioned table having huge data in it.
Please help
SELECT DISTINCT
batchid,
store_area,
STORE_AREA
|| ','
|| STORE_ID
|| ','
|| SMS_ID
|| ','
|| SMS_SERVICE
|| ','
|| SYNERGY_MODE_ID
|| ','
|| FREQUENCY
|| ','
|| DUEDATE
|| ','
|| STUDY_ID
|| ','
|| YEAR
|| ''
|| WEEK_ID
||',Not exist in Da_Duedate'
FROM GLT_temp_upload upload
WHERE upload.batchid = 1
AND NOT EXISTS
(SELECT due.week_id,
due.country_id,
due.year,
due.study_id,
due.store_id,
due.store_area,
due.synergy_mode_id,
upload.batchid,
due.due_date,
upload.sms_service
FROM DA_DUEDATE due
WHERE due.store_id = upload.store_id
AND due.study_id = upload.study_id
AND due.store_area = upload.store_area
AND due.frequency = upload.frequency
AND due.sms_service = upload.sms_service
AND due.week_id = upload.week_id
AND due.country_id = upload.country_id
AND due.year = upload.year
AND due.sms_id = upload.sms_id
AND due.synergy_mode_id =
upload.synergy_mode_id)
You may try NOT EXISTS / LEFT JOIN / NOT IN
In your NOT EXISTS it's enough to SELECT 1 instead of the list of columns
Sometimes LEFT JOIN can be more beneficial (depending on indexes, the size of the tables etc)
SELECT DISTINCT
batchid,
store_area,
STORE_AREA
|| ','
|| STORE_ID
|| ','
|| SMS_ID
|| ','
|| SMS_SERVICE
|| ','
|| SYNERGY_MODE_ID
|| ','
|| FREQUENCY
|| ','
|| DUEDATE
|| ','
|| STUDY_ID
|| ','
|| YEAR
|| ''
|| WEEK_ID
||',Not exist in Da_Duedate'
FROM GLT_temp_upload upload left join DA_DUEDATE due
ON due.store_id = upload.store_id
AND due.study_id = upload.study_id
AND due.store_area = upload.store_area
AND due.frequency = upload.frequency
AND due.sms_service = upload.sms_service
AND due.week_id = upload.week_id
AND due.country_id = upload.country_id
AND due.year = upload.year
AND due.sms_id = upload.sms_id
AND due.synergy_mode_id = upload.synergy_mode_id
WHERE upload.batchid = 1 and due.store_id is NULL;
I'd recommend you looking at the execution plan to find an optimal solution for your case.

How to group substrings in Teradata 14?

I have the following table in Teradata 14 , I am not allowed to write procedures and functions myself, but i can use strtok, strtok_split_to_table etc
id property
1 1234X (Yel), 2225Y (Red), 1234X (Gre),
2
3 1222Y (Pin),
4 1134E (Yel), 4565Y (Whi), 1134E (Red), 2222Y (Red),
How can I group the above table so that each object would have all attributes listed in one brackets
id property
1 1234X (Yel Gre), 2225Y (Red),
2
3 1222Y (Pin ),
4 1134E (Yel Red), 4565Y (Whi), 2222Y (Red),
The property code is always a 5 character string, e.g. 1222Y . The color code is always 3 character , e.g. Pin
I tried using this solution but got an error A column or character expression is larger than max size
In addition I tried strtok_split_to_table and was able to create a modified table, but do not how to proceed from that
Why do you store denormalized data in a RDBMS and then process it to create even worse denormalized output?
Modifying my solution from the link you posted to utilize STRTOK_SPLIT_TO_TABLE instead of recursion:
SELECT
id,
MAX(CASE WHEN newpos = 1 AND newgrp <> '(),' THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 2 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 3 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 4 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 5 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 6 THEN newgrp ELSE '' END)
-- add as many CASEs as needed
FROM
(
SELECT
id,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY newgrp) AS newpos,
TRIM(a || ' (' ||
MAX(CASE WHEN tokennum = 1 THEN b || ' ' ELSE '' END) ||
MAX(CASE WHEN tokennum = 2 THEN b || ' ' ELSE '' END) ||
MAX(CASE WHEN tokennum = 3 THEN b || ' ' ELSE '' END) ||
MAX(CASE WHEN tokennum = 4 THEN b || ' ' ELSE '' END) ||
MAX(CASE WHEN tokennum = 5 THEN b || ' ' ELSE '' END) ||
MAX(CASE WHEN tokennum = 6 THEN b || ' ' ELSE '' END)
-- add as many CASEs as needd
) || '), ' AS newgrp
FROM
(
SELECT
id, tokennum,
TRIM(SUBSTRING(token FROM 1 FOR POSITION('(' IN TRIM(token)||'(') - 1)) AS a,
TRIM(TRAILING ')' FROM SUBSTRING(token FROM POSITION('(' IN token) + 1)) AS b
FROM
TABLE( STRTOK_SPLIT_TO_TABLE(vt.id, vt.property, ',')
RETURNS (id INT,
tokennum INT,
token VARCHAR(30) CHARACTER SET UNICODE
)
) AS dt
) AS dt
GROUP BY id, a
) AS dt
GROUP BY id;
If you got access to the TDStats.udfconcat function it can be further simplified (but there's way to control the order of properties:
SELECT id,
CASE
WHEN TRIM(TDStats.udfconcat(' ' || a || ' ' || b)) || ',' <> '(),'
THEN TRIM(TDStats.udfconcat(' ' || a || ' ' || b)) || ','
ELSE ''
END
FROM
(
SELECT
id,
TRIM(SUBSTRING(token FROM 1 FOR POSITION('(' IN TRIM(token)||'(') - 1)) AS a,
'('|| OTRANSLATE(TDStats.udfconcat(TRIM(TRAILING ')' FROM SUBSTRING(token FROM POSITION('(' IN token) + 1))), ',', ' ') || ')'AS b
FROM
TABLE( STRTOK_SPLIT_TO_TABLE(vt.id, vt.property, ',')
RETURNS (id INT,
tokennum INT,
token VARCHAR(30) CHARACTER SET UNICODE
)
) AS dt
GROUP BY id, a
) AS dt
GROUP BY id;
Most of the work was fiddling with the spaces and commas in the right place to get the requested output.
Still i would never store data as such in a RDBMS.
Try this , I had slightly modified dnoeths query from your post
WITH RECURSIVE cte
(id,
len,
remaining,
word,
pos
) AS (
SELECT
id,
POSITION(',' IN property || ',') - 1 AS len,
SUBSTRING(property || ',' FROM len + 2) AS remaining,
TRIM(SUBSTRING(property FROM 1 FOR len)) AS word,
1
FROM TableA
UNION ALL
SELECT
id,
POSITION(',' IN remaining)- 1 AS len_new,
SUBSTRING(remaining FROM len_new + 2),
TRIM(SUBSTRING(remaining FROM 1 FOR len_new)),
pos + 1
FROM cte
WHERE remaining <> ''
)
SELECT
id,
MAX(CASE WHEN newpos = 1 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 2 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 3 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 4 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 5 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 6 THEN newgrp ELSE '' END)
-- add as many CASEs as needed
FROM
(
SELECT
id,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY newgrp) AS newpos,
a ||
MAX(CASE WHEN pos = 1 THEN '(' || b ELSE '' END) ||
MAX(CASE WHEN pos = 2 THEN ' ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 3 THEN ' ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 4 THEN ' ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 5 THEN ' ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 6 THEN ' ' || b ELSE '' END)
-- add as many CASEs as needed
|| '), ' AS newgrp
FROM
(
SELECT
id,
ROW_NUMBER()
OVER (PARTITION BY id, a
ORDER BY pos) AS pos,
SUBSTRING(word FROM 1 FOR POSITION('(' IN word) - 1) AS a,
TRIM(TRAILING ')' FROM SUBSTRING(word FROM POSITION('(' IN word) + 1)) AS b
FROM cte
WHERE word <> ''
) AS dt
GROUP BY id, a
) AS dt
GROUP BY id
UNION ALL
SELECT id,property FROM TableA WHERE property IS NULL OR TRIM(property)=' ';

Resources