Sum values of columns for multiple groups in Oracle table - oracle

I have a table with a list of people, gender, race, & their group. It has 2000 records divided into 8 groups. I am trying to use PL/SQL to get the sum of people per gender per race for each group. I was thinking I should have a cursor populate a variable called v_group with the 8 groups, and then loop through all the records and get the counts for each one. It will put each count into their own variable and then insert the new summed up counts and the corresponding group into a table.
Here is a sample of what I have so far.
OPEN cur_get_group;
LOOP
v_group := '';
FETCH cur_get_group
INTO v_group;
EXIT WHEN cur_get_group%NOTFOUND;
SELECT COUNT(*) INTO v_hisp_m FROM mytable WHERE sex = 'M' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_hisp_f FROM mytable WHERE sex = 'F' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_cauc_m FROM mytable WHERE sex = 'M' AND ethn_code = 'C';
SELECT COUNT(*) INTO v_cauc_f FROM mytable WHERE sex = 'F' AND ethn_code = 'C';
INSERT INTO mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
VALUES
(v_group, v_hisp_m, v_hisp_f, v_cauc_m, v_cauc_f);
COMMIT;
END LOOP;
Am I on the right track here? Do I need to do the loop differently?

Relational data bases work on the set of data matching conditions and are very good at it. They are however very poor single item processing. Learn to think is terms of sets. Your process has to pass the source data table 5 times (1 for group and count process) While loops are sometimes a necessary evil they are a process of last resort. This can be done in 1 statement, passing the source 1 time.
insert into mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
select group_code
, sum(hisp_m)
, sum(hisp_f)
, sum(cauc_m)
, sum(cauc_f)
from
( select group_code,
, case when sex = 'F' AND ethn_code = 'H' then 1 else 0 end hisp_f
, case when sex = 'M' AND ethn_code = 'H' then 1 else 0 end hisp_m
, case when sex = 'F' AND ethn_code = 'C' then 1 else 0 end cauc_f
, case when sex = 'M' AND ethn_code = 'C' then 1 else 0 end cauc_m
from youroldtable
)
group by group_code;
BTW DO NOT use group as a column name. It is a reserved word and doing so will lead to very difficult to find errors.

Looks to me like you want to use pivot:
with rws as (
select 'F' sex, 'H' eth from dual connect by level <= 3
union all
select 'F' sex, 'C' eth from dual connect by level <= 4
union all
select 'M' sex, 'H' eth from dual connect by level <= 2
union all
select 'M' sex, 'C' eth from dual
)
select * from rws
pivot (
count (*) for ( sex, eth ) in (
( 'F', 'H' ) fh,
( 'F', 'C' ) fc,
( 'M', 'H' ) mh,
( 'M', 'C' ) mc
)
);
FH FC MH MC
3 4 2 1
And insert the result of this query to the table.

Related

Return only one row of a query selection

I am writing a data export where I need to return one row from a selection where there may be multiple rows. In this case, the second table is the telephone_current table. This table includes a row for several telephone types (CA, MA, PR, etc.), and they are not in any particular order. If the individual has a CA, I need to include that record; if not, then I would use either type MA or PR.
The query below works, technically, but it will run excruciatingly slow (10 minutes or more).
I need advice to fix this query to get one row (record) per individual. The slowdown occurs when I include the self join telephone_current tc. Note. I've also moved the AND into the WHERE clause, which runs with the time delay.
SELECT distinct igp.isu_id PersonnelNumber
, igp.preferred_first_name FirstName
, igp.current_last_name LastName
, NULL Title
, igp.current_mi MiddleInitial
, pd.email_preferred_address
, tc.phone_number_combined
, igp.isu_username networkID
, '0' GroupID
, e.home_organization_desc GroupName
, CASE
WHEN substr(e.employee_class,1,1) in ( 'N', 'C') THEN 'staff'
WHEN substr(e.employee_class,1,1) = 'F' THEN 'faculty'
ELSE 'other'
END GroupType
FROM isu_general_person igp
JOIN person_detail pd ON igp.person_uid = pd.person_uid
JOIN telephone_current tc ON igp.person_uid = tc.entity_uid
AND tc.phone_number = (
SELECT p.phone_number
FROM telephone_current p
WHERE tc.entity_uid = p.entity_uid
ORDER BY phone_type
FETCH FIRST 1 ROW ONLY
)
LEFT JOIN employee e ON igp.person_uid = e.person_uid
-- LEFT JOIN faculty f ON igp.person_uid = f.person_uid
WHERE 1=1
AND e.employee_status = 'A'
AND substr(e.employee_class,1,1) in ( 'N', 'C', 'F')
AND igp.isu_username IS NOT NULL
;
We did identify problem with the index on the telephone_current table. Once that was resolved, both the versions provided by xQbert worked to provide the single-row result for each individual. The version using WITH BaseData ran in approximately 12 seconds. However, this version returned all rows in 2.4 seconds.
SELECT distinct igp.isu_id PersonnelNumber
, igp.preferred_first_name FirstName
, igp.current_last_name LastName
, NULL Title
, igp.current_mi MiddleInitial
, pd.email_preferred_address
, tc.phone_number_combined
, igp.isu_username networkID
, '0' GroupID
, e.home_organization_desc GroupName
, CASE
WHEN substr(e.employee_class,1,1) in ( 'N', 'C') THEN 'staff'
WHEN substr(e.employee_class,1,1) = 'F' THEN 'faculty'
ELSE 'other'
END GroupType
FROM isu_general_person igp
JOIN person_detail pd
ON igp.person_uid = pd.person_uid
CROSS APPLY (SELECT xtc.phone_number_combined
FROM telephone xtc
WHERE igp.person_uid = xtc.entity_uid
ORDER BY case when phone_type = 'CA' then 1
when phone_Type in ('MA','PR') then 2
else 3 end,
phone_Type,
phone_number_combined
FETCH FIRST 1 ROW ONLY) tc
LEFT JOIN employee e ON igp.person_uid = e.person_uid
-- LEFT JOIN faculty f ON igp.person_uid = f.person_uid
WHERE 1=1
AND e.employee_status = 'A'
AND substr(e.employee_class,1,1) in ( 'N', 'C', 'F')
AND igp.isu_username IS NOT NULL
Example using row_number() analytic and a common table expression. This limits to one phone per person by creating a partition/group of numbers under a given Entity_Uid orders this by a case expression and then assigns row number based on that case expression defined order then phone type, then phone number. The row number is then used to limit the results to just 1 phone number.
WITH BaseData as (
SELECT distinct igp.isu_id PersonnelNumber
, igp.preferred_first_name FirstName
, igp.current_last_name LastName
, NULL Title
, igp.current_mi MiddleInitial
, pd.email_preferred_address
, tc.phone_number_combined
, igp.isu_username networkID
, '0' GroupID
, e.home_organization_desc GroupName
, CASE
WHEN substr(e.employee_class,1,1) in ( 'N', 'C') THEN 'staff'
WHEN substr(e.employee_class,1,1) = 'F' THEN 'faculty'
ELSE 'other'
END GroupType,
row_number() over (PARTITION BY Entity_Uid ORDER BY case when phone_type ='CA' then 1
when phone_Type in ('MA','PR') then 2
else 3 end, phone_Type, Phone_number) RN
FROM isu_general_person igp
JOIN person_detail pd ON igp.person_uid = pd.person_uid
JOIN telephone_current tc ON igp.person_uid = tc.entity_uid
LEFT JOIN employee e ON igp.person_uid = e.person_uid
-- LEFT JOIN faculty f ON igp.person_uid = f.person_uid
WHERE 1=1
AND e.employee_status = 'A'
AND substr(e.employee_class,1,1) in ( 'N', 'C', 'F')
AND igp.isu_username IS NOT NULL)
SELECT *
FROM BaseData
WHERE RN = 1
;
Example As cross apply: Cross apply avoids the need of the analytic and basically says; hey; for each matching igp.person_uid = xtc.entity_uid, get the first record based on the order defined in the subquery. quit when you've got the 1st record for each user
SELECT distinct igp.isu_id PersonnelNumber
, igp.preferred_first_name FirstName
, igp.current_last_name LastName
, NULL Title
, igp.current_mi MiddleInitial
, pd.email_preferred_address
, tc.phone_number_combined
, igp.isu_username networkID
, '0' GroupID
, e.home_organization_desc GroupName
, CASE
WHEN substr(e.employee_class,1,1) in ( 'N', 'C') THEN 'staff'
WHEN substr(e.employee_class,1,1) = 'F' THEN 'faculty'
ELSE 'other'
END GroupType,
FROM isu_general_person igp
JOIN person_detail pd
ON igp.person_uid = pd.person_uid
CROSS APPLY (SELECT xtc.phone_number
FROM telephone_current xtc
WHERE igp.person_uid = xtc.entity_uid
ORDER BY case when phone_type = 'CA' then 1
when phone_Type in ('MA','PR') then 2
else 3 end,
phone_Type,
Telephone_current
FETCH FIRST 1 ROW ONLY) tc
LEFT JOIN employee e ON igp.person_uid = e.person_uid
-- LEFT JOIN faculty f ON igp.person_uid = f.person_uid
WHERE 1=1
AND e.employee_status = 'A'
AND substr(e.employee_class,1,1) in ( 'N', 'C', 'F')
AND igp.isu_username IS NOT NULL

sql placeholder rows

I have an apex item P_USERS which can have a value higher than the amount of rows returning from the query below.
I have a classic report which has the following query:
select
first_name,
last_name
from accounts
where account_role = 'Author'
order by account_nr;
I want placeholder rows to be added to the query (first_name = null, last_name = null etc.), if the total rows from the query is lesser than the value in the apex_item P_USERS.
Any tips on how to achieve this? Maybe with a LEFT join?
If you have more result than the minima you defined, you must add the rest with union.
Here is what you could try to adapt to your case:
SELECT i,c FROM (
select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)), (Select Rownum r From dual Connect By Rownum <= 3)
where (i(+)= r)
union select i,c from (select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)) where i>3
You may try to use a LEFT JOIN.
First, create a list of number until the limit you want like suggested here:
-- let's say you want 300 records
Select Rownum r From dual Connect By Rownum <= 300
Then you can use this to left join and have empty records:
SELECT C, R FROM
( select rownum i, c from (select 'a' c from dual union all select 'b' from dual) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r order by r
The above gives you an ordered list starting with 'a', 'b', then null until the end.
So you could adapt it to your case so:
SELECT F,L FROM
( select rownum i, f, l from (
select first_name f, last_name l
from accounts where account_role = 'Author'
order by account_nr) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r

Group Query for oracle

I have a scenario where I need to fetch all the records within an ID for the one source along with other source. Given below is my input set of records.
ID SOURCE CURR_FLAG TYPE
1 IBM Y P
1 IBM Y OF
1 IBM Y P
2 IBM Y P
2 TCS Y P
3 IBM NULL P
3 CTS NULL P
3 TCS NULL P
4 IBM NULL OF
4 CTS NULL OF
4 TCS Y ON
5 CTS NULL OF
5 TCS Y ON
From the above records, I need to select all the records with source as IBM within that same ID group and other source should be also there for the same ID along with IBM.Also, we need to fetch only those records where at least one record in that ID group with curr_fl='Y'
In the above scenario even though the ID=1 have a source as IBM, but there is no record in that particular group with other source.So we shouldn't fetch that record with that ID.
For the record ID=3 have a source as IBM along with other sources, but there is no record with CURR_FL='Y', my query should not fetch the value.In the case of ID=4, it can fetch all the records with ID=4, as one of the records have value='Y' and it have a combination of IBM with other source.For ID 5 it should not fetch as we dont have any IBM record source within that set
Also within the group which has satisfied the above condition, I need one more condition for type. if there are records with type='P', then I need to fetch only that record.If there are no records with P, then I will search for type='OF' else type='ON'
My Expected output is given below
ID SOURCE CURR_FLAG TYPE
2 IBM Y P
2 TCS Y P
4 IBM NULL OF
4 CTS NULL OF
4 TCS Y ON
I have written a query as given below.But it's running for long and not fetching any results. Is there any better way to modify this query
select
ID,
SOURCE,
CURR_FL,
TYPE
from TABLE a where
exists(select 1 from TABLE B where a.ID=B.ID and a.rowid<>B.rowid and B.source<>a.source)
and exists(select 1 from TABLE C where a.ID=C.ID and C.source ='IBM')
and exists(select 1 from TABLE D where a.ID=D.ID and D.CURR_FL='Y') and
(TYPE,ID) IN (
select case type when 1 then 'P' when 2 then 'OF' else 'ON' END TYPE,ID from
(select ID,
max(priority) keep (dense_rank first order by priority asc) as type
from ( select ID,TYPE,
case TYPE
when 'P' then 1
when 'OF' then 2
when 'ON' then 3
end as priority
from TABLE where ID
in(select ID from TABLE where CURR_FL='Y') AND SOURCE='IBM'
)
group by ID))
First, look for the ids. I would recommend:
select id
from t
group by id
having min(source) <> max(source) and -- at least two sources
sum(case when curr_flag = 'Y' then 1 else 0 end) > 0 and -- at least one Y
sum(case when source = 'IBM' then 1 else 0 end) > 0 -- IBM
To get the base rows, you can use in, exists, or join:
select t.*
from t
where id in (select id
from t
group by id
having min(source) <> max(source) and -- at least two sources
sum(case when curr_flag = 'Y' then 1 else 0 end) > 0 and -- at least one Y
sum(case when source = 'IBM' then 1 else 0 end) > 0 -- IBM
);
Using analytic functions you can check that there is at least one IBM in the group, at least one other source apart from IBM in the group, and at least one flag with Y in the group:
with t(id, source, curr_flag, type) as
(
select 1, 'IBM', 'Y', 'P' from dual union all
select 1, 'IBM', 'Y', 'OF' from dual union all
select 1, 'IBM', 'Y', 'P' from dual union all
select 2, 'IBM', 'Y', 'P' from dual union all
select 2, 'TCS', 'Y', 'P' from dual union all
select 3, 'IBM', NULL, 'P' from dual union all
select 3, 'CTS', NULL, 'P' from dual union all
select 3, 'TCS', NULL, 'P' from dual union all
select 4, 'IBM', NULL, 'OF' from dual union all
select 4, 'CTS', NULL, 'OF' from dual union all
select 4, 'TCS', 'Y', 'ON' from dual union all
select 5, 'CTS', NULL, 'OF' from dual union all
select 5, 'TCS', 'Y', 'ON' from dual
)
select id, source, curr_flag, type
from (select id, source, curr_flag, type,
max(case when source = 'IBM' then 1 end) over (partition by id) ibm,
max(case when source != 'IBM' then 1 end) over (partition by id) not_ibm,
max(case when curr_flag = 'Y' then 1 end) over (partition by id) flag_y
from t)
where ibm = 1
and not_ibm = 1
and flag_y = 1
order by id, source;

CASE Oracle SQL for State

I have a field that can have one or multiple states listed in it (callcenter.stateimpact). If the callcenter.stateimpact contains "OK","TX","AK","TN","NC","SC","GA","FL","AL","MS" or "LA" I need the output field of the SQL to say "South" and if not those, the output needs to say "North". If the callcenter.stateimpact has both South & North states, it needs to say "BOTH" in the output. How do I do this in the Select statement? The fields in this table are callcenter.callid, callcenter.stateimpact, callcenter.callstart and callcenter.callstop. You help is greatly appreciated.
This is tough to explain, so there's a SQL Fiddle here that lays out the values involved.
The best approach I could come up with (other than normalizing the StateImpact value) was to use REGEXP_REPLACE to suck all the "South" states out of the string and then look at the length of what was left. First, here's what REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)') will do to a few sample values:
StateImpact REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)')
----------------------------- -----------------------------------------------------------------
OK,TX,AK,TN,NC,SC,GA,FL,AL,MS ,,,,,,,,,
MI,MA MI,MA
TX null
TX,MI,MA ,MI,MA
So if you're left with all commas or with a null, all the states were South. If you're left with the original string, all states were North. Anything else and it's Both. That makes for a pretty big and confusing CASE statement no matter how you write it. I went with comparing lengths before and after, like so:
Length after replace = 0 (or null): South
Length after replace = (length before + 1) * 3 - 1: South
Length after replace = length before replace: North
Anything else: Both
The second one above is just some math to account for the fact that if (for example) there are five states in StateImpact and they're all South, you'll be left with four commas. Hard to explain but it works :)
Here's the query:
SELECT
StateImpact,
CASE NVL(LENGTH(REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)')), 0)
WHEN LENGTH(StateImpact) THEN 'North'
WHEN (LENGTH(StateImpact) + 1) / 3 - 1 THEN 'South'
ELSE 'Both'
END AS RegionImpact
FROM CallCenter
The SQL Fiddle referenced above also shows the length before and after the REGEXP_REPLACE, which will hopefully help explain the calculations.
One of the ways to reach desired result is to use multiset operators.
But first we need to break string separated by , into rows. One of the way to do that is trick with connect by :
-- Trick with building resultset from tokenized string
with dtest_string as (
select 'OK,TX,AK,TN,NC,SC,GA,FL,AL,MS' StateImpact from dual
)
select
level lvl,
substr( -- Extract part of source string
StateImpact,
-- from N-th occurence of separator
decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 ),
-- with length of substring from N-th to (N+1)-th occurence of separator or to the end.
decode( instr(StateImpact,',',1,level), 0, length(StateImpact)+1, instr(StateImpact,',',1,level) )
-
decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 )
) code
from test_string
start with
StateImpact is not null -- no entries for empty string
connect by
instr(StateImpact,',',1,level-1) > 0 -- continue if separator found on previous step
Just for fun: same trick with ANSI syntax on SQLFiddle
Next, we need to declare type which we can use to store collections:
create or replace type TCodeList as table of varchar2(100);
After that it's possible to build a query:
with all_south_list as (
-- prepare list of south states
select 'OK' as code from dual union all
select 'TX' as code from dual union all
select 'AK' as code from dual union all
select 'TN' as code from dual union all
select 'NC' as code from dual union all
select 'SC' as code from dual union all
select 'GA' as code from dual union all
select 'FL' as code from dual union all
select 'AL' as code from dual union all
select 'MS' as code from dual union all
select 'LA' as code from dual
)
select
StateImpact,
-- Make decision based on counts
case
when total_count = 0 then 'None'
when total_count = south_count then 'South'
when south_count = 0 then 'North'
else 'Both'
end RegionImpact,
total_count,
south_count,
north_count
from (
select
StateImpact,
-- count total number of states in StateImpact
cardinality(code_list) total_count,
-- count number of south states in StateImpact
cardinality(code_list multiset intersect south_list) south_count,
-- count number of non-south states in StateImpact
cardinality(code_list multiset except south_list) north_count
from (
select
StateImpact,
(
cast(multiset( -- Convert set of values into collection which acts like a nested table
select -- same trick as above
substr(
StateImpact,
decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 ),
decode( instr(StateImpact,',',1,level), 0, length(StateImpact)+1, instr(StateImpact,',',1,level) )
-
decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 )
) code
from dual
start with StateImpact is not null
connect by instr(StateImpact,',',1,level-1) > 0
) as TCodeList
)
) code_list,
-- Build collection from south states list
cast(multiset(select code from all_south_list) as TCodeList) south_list
from
CallCenter
)
)
Link to SQLFiddle

how to replace multiple strings together in Oracle

I have a string coming from a table like "can no pay{1},as your payment{2}due on {3}". I want to replace {1} with some value , {2} with some value and {3} with some value .
Is it Possible to replace all 3 in one replace function ? or is there any way I can directly write query and get replaced value ? I want to replace these strings in Oracle stored procedure the original string is coming from one of my table I am just doing select on that table
and then I want to replace {1},{2},{3} values from that string to the other value that I have from another table
Although it is not one call, you can nest the replace() calls:
SET mycol = replace( replace(mycol, '{1}', 'myoneval'), '{2}', mytwoval)
If there are many variables to replace and you have them in another table and if the number of variables is variable you can use a recursive CTE to replace them.
An example below. In table fg_rulez you put the strings with their replacement. In table fg_data you have your input strings.
set define off;
drop table fg_rulez
create table fg_rulez as
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'great than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '&', 'and' from dual;
drop table fg_data;
create table fg_Data AS(
SELECT 'amount $ must be < 1 & > 2' str FROM dual
union all
SELECT 'John is > Peter & has many $' str FROM dual
union all
SELECT 'Eliana is < mary & do not has many $' str FROM dual
);
WITH q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
So, a single replace.
Result:
amount dollars must be less than 1 and great than 2
John is great than Peter and has many dollars
Eliana is less than mary and do not has many dollars
The terminology symbol instead of variable comes from this duplicated question.
Oracle 11gR2
Let's write the same sample as a CTE only:
with fg_rulez as (
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'greater than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '+', 'and' from dual
), fg_Data AS (
SELECT 'amount $ must be < 1 + > 2' str FROM dual
union all
SELECT 'John is > Peter + has many $' str FROM dual
union all
SELECT 'Eliana is < mary + do not has many $' str FROM dual
), q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
If the number of values to replace is too big or you need to be able to easily maintain it, you could also split the string, use a dictionary table and finally aggregate the results
In the example below I'm assuming that the words in your string are separated with blankspaces and the wordcount in the string will not be bigger than 100 (pivot table cardinality)
with Dict as
(select '{1}' String, 'myfirstval' Repl from dual
union all
select '{2}' String, 'mysecondval' Repl from dual
union all
select '{3}' String, 'mythirdval' Repl from dual
union all
select '{Nth}' String, 'myNthval' Repl from dual
)
,MyStrings as
(select 'This is the first example {1} ' Str, 1 strnum from dual
union all
select 'In the Second example all values are shown {1} {2} {3} {Nth} ', 2 from dual
union all
select '{3} Is the value for the third', 3 from dual
union all
select '{Nth} Is the value for the Nth', 4 from dual
)
-- pivot is used to split the stings from MyStrings. We use a cartesian join for this
,pivot as (
Select Rownum Pnum
From dual
Connect By Rownum <= 100
)
-- StrtoRow is basically a cartesian join between MyStings and Pivot.
-- There as many rows as individual string elements in the Mystring Table
-- (Max = Numnber of rows Mystring table * 100).
,StrtoRow as
(
SELECT rownum rn
,ms.strnum
,REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) is not null
)
-- This is the main Select.
-- With the listagg function we group the string together in lines using the key strnum (group by)
-- The NVL gets the translations:
-- if there is a Repl (Replacement from the dict table) then provide it,
-- Otherwise TXT (string without translation)
Select Listagg(NVL(Repl,TXT),' ') within group (order by rn)
from
(
-- outher join between strings and the translations (not all strings have translations)
Select sr.TXT, d.Repl, sr.strnum, sr.rn
from StrtoRow sr
,dict d
where sr.TXT = d.String(+)
order by strnum, rn
) group by strnum
If you are doing this inside of a select, you can just piece it together, if your replacement values are columns, using string concatenation.

Resources