row to column conversion in sql - sql

Suppose I am having 3 set of records like
Actno Sufix Amount
000005 230 101000
000005 535 100000
000005 630 -500000
000009 230 222000
000009 535 120000
000009 635 220000
I need to display it as
000005 230 101000 535 100000 630 -500000
000009 230 222000 535 120000 635 220000
Is that possible in SQL? Can anyone please help me regarding this?

If you know that you have three registrations for each Actno you can do this
;with cte as
(
select *,
row_number() over(partition by Actno order by Sufix) as rn
from [YourTable]
)
select
C.Actno,
min(C1.Sufix),
min(C1.Amount),
min(C2.Sufix),
min(C2.Amount),
min(C3.Sufix),
min(C3.Amount)
from cte as C
inner join cte as C1
on C.Actno = C1.Actno and C1.rn = 1
inner join cte as C2
on C.Actno = C2.Actno and C2.rn = 2
inner join cte as C3
on C.Actno = C3.Actno and C3.rn = 3
group by C.Actno
order by C.Actno

No, it's not possible in SQL.
It's also not what SQL is intended for; presentation is not SQL's job.
If you feel you need to get column data into rows, then there is something about your idea for organising your data that doesn't suit relational databases. That's where you should look.

If you don't mind that your aggregated rows are on string (and you're using Oracle), I used to do something like this:
CREATE OR REPLACE FUNCTION APPEND_FIELD(sqlstr in varchar2,
sep in varchar2)
return varchar2 is
ret varchar2(4000) := '';
TYPE cur_typ IS REF CURSOR;
rec cur_typ;
field varchar2(4000);
begin
OPEN rec FOR sqlstr;
LOOP
FETCH rec
INTO field;
EXIT WHEN rec%NOTFOUND;
ret := ret || field || sep;
END LOOP;
if length(ret) = 0 then
RETURN '';
else
RETURN substr(ret, 1, length(ret) - length(sep));
end if;
end;
/
select
actno,
sufix,
amount,
APPEND_FIELD('select sufix '''||' '||'''amount
from table tt
where tt.actno = '||t.actno||' and sufix != 230 ',' ')
from table t
where sufix=230;
HTH

Related

Using Oracle SQL, how can I concatenate data from multiple rows into one row without using PIVOT, LISTAGG or custom built functions?

I'm using Oracle SQL. This is the table:
doc-nr
code
text
0000010
155
Sample text from code 155
0000010
455
Sample text1 from code 455
0000010
455
Sample text2 from code 455
0000010
455
Sample text3 from code 455
With this query:
SELECT t155.text as "155", t455.text as "455"
FROM myTable t155
JOIN myTable t455 ON t155.doc-nr = t455.doc-nr
WHERE t155.code = '155'
AND t455.code = '455'
I get ugly result:
155
455
Sample text from code 155
Sample text1 from code 455
Sample text from code 155
Sample text2 from code 455
Sample text from code 155
Sample text3 from code 455
I want the output to be one row:
155
455
455
455
Sample text from code 155
Sample text1 from code 455
Sample text2 from code 455
Sample text3 from code 455
Or:
155
455
Sample text from code 155
Sample text1 from code 455; Sample text2 from code 455; Sample text3 from code 455
How can you do this with Oracle SQL without using PIVOT or LISTAGG or using custom built functions?
You can create such a function returning SYS_REFCURSOR in order to pivot your data dynamically
CREATE OR REPLACE FUNCTION Get_Pivoted_Cols RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols1 VARCHAR2(32767);
v_cols2 VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||rn||''' AS "'||rn||'"' , ',' ) WITHIN GROUP ( ORDER BY text ),
LISTAGG( 'MAX("'||rn||'") AS "'||rn||'"' , ',' ) WITHIN GROUP ( ORDER BY text )
INTO v_cols1, v_cols2
FROM
( SELECT code||'_'||ROW_NUMBER() OVER (PARTITION BY doc_nr, code ORDER BY text) AS rn, t.*
FROM tab t );
v_sql :='SELECT '||v_cols2||
' FROM
(
SELECT code||''_''||ROW_NUMBER() OVER (PARTITION BY doc_nr, code ORDER BY text) AS rn, t.*
FROM tab t
)
PIVOT
(
MAX(text) FOR rn IN ( '|| v_cols1 ||' )
)
GROUP BY doc_nr';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
which will generate this output as a SQL statement
SELECT MAX("155_1") AS "155_1",
MAX("455_1") AS "455_1",
MAX("455_2") AS "455_2",
MAX("455_3") AS "455_3"
FROM
(
SELECT code||'_'||ROW_NUMBER() OVER (PARTITION BY doc_nr, code ORDER BY text) AS rn,
t.*
FROM tab t
)
PIVOT
(
MAX(text) FOR rn IN ( '155_1' AS "155_1", '455_1' AS "455_1", '455_2' AS "455_2",
'455_3' AS "455_3" )
)
GROUP BY doc_nr;
Demo
and then run this code :
VAR rc REFCURSOR
EXEC :rc := Get_Pivoted_Cols;
PRINT rc
from SQL Developer's Command Line in order to see the result set.
If the problem is the size, you can use XML aggregation:
select mt.doc_nr, mt.text as text_155,
xmlquery('
for $i in .
return <e>{ fn:string-join($i/text, "; ") }</e>/text()'
passing tmp.xml
returning content
).getclobval() as text_455
from mytable mt
join (
select doc_nr, xmlagg(xmlelement("text", text)) as xml
from mytable
where code = 455
group by doc_nr
) tmp
on tmp.doc_nr = mt.doc_nr
where mt.code = 155
DOC_NR | TEXT_155 | TEXT_455
:------ | :------------------------ | :---------------------------------------------------------------------------------
0000010 | Sample text from code 155 | Sample text1 from code 455; Sample text3 from code 455; Sample text2 from code 455
db<>fiddle including the intermediate steps.
Using xml functions in a such way is not good for performance (when there is more data) but in this case it works fine:
SELECT t155.text as "155",
sys_xmlagg(xmlelement(col, t455.text || ',')).extract('/ROWSET/COL/text()')
.getclobval() as "455"
FROM myTable t155
JOIN myTable t455
ON t155."doc-nr" = t455."doc-nr"
WHERE t155.code = '155'
AND t455.code = '455'
GROUP BY t155.text

Declare function in Oracle SQL temporary script

I have a script that I run in the Toad for Oracle (or in the PL/SQL Developer):
select distinct AC.STATUS, S.DESCRIPTION, count(*) || ' / ' || (
select count(*)
from ACC AC
where AC.STATUS is not null
) "Count"
from ACC AC
join ACC_STATUS S
on AC.STATUS = S.STATUS
group by AC.STATUS, S.DESCRIPTION
order by AC.STATUS;
so it works pretty good and shows me some result like this:
-7 Status 1 30 / 174
-6 Status 2 124 / 174
-3 Status 3 6 / 174
-2 Status 4 4 / 174
-1 Status 5 10 / 174
I want to refactor this and extract the first select (in parentheses) into a function something like:
declare function zz
return number
is
declare res number;
begin
select count(*) into res
from ACC AC
where AC.STATUS is not null;
return res;
end zz;
and then use it somehow in the script.
Is it possible? And which syntax have I use? I do not want to save the function into database – only to run it for my own need in Toad or PL/SQL Developer. (We have Oracle version 12.1.0.2.0.)
In Oracle 12c or above you can utilize WITH factoring clause as
with
function f_test return number is
retval number;
begin
select count(*)
into retval
from acc
where status is not null;
return retval;
end;
select distinct
ac.status,
s.description,
count(*) || ' / ' || f_test
from acc ac join acc_status s on ac.status = s.status
group by ac.status, s.description
order by ac.status;
Otherwise, in lower versions, no - you can't do that and you'll have to create a stored function (the one that exists in the database).
Why? You can just use analytic functions:
select AC.STATUS, S.DESCRIPTION,
count(*) || ' / ' || sum(count(*)) over () as "count"
from ACC AC join
ACC_STATUS S
on AC.STATUS = S.STATUS
group by AC.STATUS, S.DESCRIPTION
order by AC.STATUS;
You don't have to worry about NULL values, because the JOIN filters them out.

Concanate data from one table to another

I want to concanate the data from TABLE1 to TABLE 2
TABLE 1
id grp_name
-----------------------------
1 A#erf,R#erf.in
2 B#go.in,D#st.org/S#rec.uy
3 C#st.org,X#we.in,S#erl.in
4 D#gh.ou#F#rt.ot
5 E#rth.or
TABLE 2
code name
-----------------------------------
1 A#we.ot,D#ref.as
2 B#de.in
3 C#gr.cpm
4 D#yahoo.com
5 E#erf.com
6 F#google.com
I want to join grp_name data with name data like concanate using comma (',')
grp_name data having unwanted symboles like '#', '/', I want to elimate those too.
I created below procedure, but i dont know i effective or not.
If it is possible with simple update statement alone or merge statement alone let me know.
Excepted result
code name
1 A#we.ot,D#ref.as,A#erf,R#erf.in
2 B#de.in,B#go.in,D#st.org,S#rec.uy
3 C#gr.cpm,C#st.org,X#we.in,S#erl.in
4 D#yahoo.com,D#gh.ou,F#rt.ot
5 E#erf.com,E#rth.or
6 F#google.com
CREATE OR REPLACE PROCEDURE procedure1
AS
CURSOR cur
IS
SELECT id, grp_name
FROM TABLE 1;
CURSOR cur2
IS
SELECT code, name
FROM TABLE 2;
v_a VARCHAR2(300);
v_b VARCHAR2(25);
v_c VARCHAR2(4000);
v_d VARCHAR2(250);
BEGIN
FOR i IN cur
LOOP
v_a := ','||i.grp_name;
v_b := i.id;
FOR e IN cur2
LOOP
v_c := e.name || v_a ;
v_d := i.code;
UPDATE schema_name.TABLE 2
SET name = v_c
WHERE v_d = v_b;
END LOOP;
END LOOP;
-- COMMIT;
END;
In the simplest case, like #jarlh said, you can just do an update:
update table2
set name = name
|| (select ',' || regexp_replace(grp_name, '[/#]', ',') -- replace / and # with ,
from table1
where table1.id = table2.code)
where code in (select id from table1); -- only update matching rows
The last line is not strictly necessary, but it's good to avoid unnecessary updates.
If you also have rows in TABLE1 which don't have a match in TABLE2 that you want to add, use an insert:
insert into table2 (code, name)
select id, regexp_replace(grp_name, '[/#]', ',')
from table1
where id not in (select code from table2);
Edit: and like Gordon mentioned, you would have an easier time with the SQL if you stored your data like this:
code name
1 A#we.ot
1 D#ref.as
1 A#erf
1 R#erf.in
2 B#de.in
2 B#go.in
2 D#st.org
2 S#rec.uy
3 C#gr.cpm
3 C#st.org
3 X#we.in
3 S#erl.in
Please try something like this:
With tab1 as
(
select 1 as id,'A#er#f,R#erf.in' as grp_name from dual union all
select 2 as id,'B#go.in,D#st.or#g/S#rec.uy' as grp_name from dual
),
tab2 as (
select 1 as id,'A#we.ot,D#ref.as' as name from dual union all
select 2 as id,'B#de.in' as name from dual
)
select REGEXP_REPLACE (grp_name||','||name,'[^a-zA-Z0-9|\#|\,]','') as name from tab1 inner join tab2 using(id);
Result:
A#erf,R#erfin,A#weot,D#refas
B#goin,D#storgS#recuy,B#dein

Dynamically spilt rows in to mulitple comma-separated lists

I have a table that contains a list of users.
USER_TABLE
USER_ID DEPT
------- ----
USER1 HR
USER2 FINANCE
USER3 IT`
Using a SQL statement, I need to get the list of users as a delimited string returned as a varchar2 - this is the only datatype I can use as dictated by the application I'm using, e.g.
USER1, USER2, USER3
The issue I have is the list will exceed 4000 characters. I have the following which will manually chunk up the users in to lists of 150 users at a time (based on user_id max size being 20 characters plus delimiters safely fitting in to 4000 characters).
SELECT LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID)
FROM (SELECT DISTINCT USER_ID AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME)
WHERE RN <= 150
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1
UNION
SELECT LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID)
FROM (SELECT DISTINCT USER_ID AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME)
WHERE RN > 150 AND RN <= 300
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1
This is manual and would require an additional UNION for each chunk of 150 users and the total number of users could increase at a later date.
Is it possible to do this so the delimited strings of user_ids are generated dynamically so they fit in to multiple chunks of 4000 characters and no user_ids are split over multiple strings?
Ideally, I'd want the output to look like this:
USER1, USER2, USER3 (to) USER149
USER150, USER151, USER152 (to) USER300
USER301, USER302, USER303 (to) USER450`
The solution needs to be a SELECT statement as the schema is read-only and we aren't able to create any objects on the database. We're using Oracle 11g.
You can do this with a pipelined function:
create or replace function get_user_ids
return sys.dbms_debug_vc2coll pipelined
is
rv varchar2(4000) := null;
begin
for r in ( select user_id, length(user_id) as lng
from user_table
order by user_id )
loop
if length(rv) + r.lng + 1 > 4000
then
rv := rtrim(rv, ','); -- remove trailing comma
pipe row (rv);
rv := null;
end if;
rv := rv || r.user_id || ',';
end loop;
return;
end;
/
You would call it like this:
select column_value as user_id_csv
from table(get_user_ids);
Alternate way using below function :
create or replace FUNCTION my_agg_user
RETURN CLOB IS
l_string CLOB;
TYPE t_bulk_collect_test_tab IS TABLE OF VARCHAR2(4000);
l_tab t_bulk_collect_test_tab;
CURSOR user_list IS
SELECT USER_ID
FROM USER_TABLE ;
BEGIN
OPEN user_list;
LOOP
FETCH user_list
BULK COLLECT INTO l_tab LIMIT 1000;
FOR indx IN 1 .. l_tab.COUNT
LOOP
l_string := l_string || l_tab(indx);
l_string := l_string || ',';
END LOOP;
EXIT WHEN user_list%NOTFOUND;
END LOOP;
CLOSE user_list;
RETURN l_string;
END my_agg_user;
After function created ,
select my_agg_user from dual;
I believe the SQL I have below should work in most cases. I've hard-coded the SQL to break the strings up in to 150 entries of user id, but the rest is dynamic.
The middle part produces duplicates, which requires an additional distinct to eliminate, but I'm not sure if there is a better way to do this.
WITH POSITION AS ( SELECT ((LEVEL-1) * 150 + 1) FROM_POS, LEVEL * 150 TO_POS
FROM DUAL
CONNECT BY LEVEL <= (SELECT COUNT(DISTINCT( USER_ID)) / 150 FROM TABLE_NAME)
)
SELECT DISTINCT
LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID) OVER (PARTITION BY FROM_POS, TO_POS)
FROM
(SELECT DISTINCT USER_ID AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME) V0 ,
POSITION
WHERE V0.RN >= POSITION.FROM_POS
AND V0.RN <= POSITION.TO_POS

How to use Varray or cursor or any other array in decode function to compare the values

I want to compare the last_name values in emp_x table, name column for first 3 character if match any of the records then i want to return that value from emp_x table.
Below is my tables and records:
select substr(x.last_name,1,3) from employee x;
Mathews
Smith
Rice
Black
Green
Larry
Cat
select * from emp_x;
Mataaa
Mmitta
Smitta
Riceeeee
Expected Result:
select decode(substr(x.last_name,1,3), substr(x.last_name,1,3), (select name from emp_x y where y.name like substr(x.last_name,1,3)||'%'),x.last_name) from employee x;
Mataaa
Smitta
Riceeeee
NULL
NULL
NULL
NULL
I am getting the exact result but is there any other best way to use it in pl/sql procedure.
say for example, I am taking the 'Mathews' last_name value from employee table and read the first 3 digit and comparing in emp_x table and getting the Mataa value as result in the decode function above.
Instead of selecting values from table can we use any array or cursor to compare and get the values from varaible in pl/SQL Procedure...
Please help to resolve this..
I know this code is not the best solution, it's sort of work(ish) in nature. Anyways I wrote it to just pass some time at work, and I hope even if its not the complete solution, you get an idea regarding what you wanna achieve
DECLARE
CURSOR c1 IS select substr(x.last_name,1,3) from employee x;
c1_rec c1%ROWTYPE;
CURSOR c2 IS select * from emp_x;
c2_rec c2%ROWTYPE;
l_name employee.last_name%TYPE;
BEGIN
FOR c1_rec IN c1
LOOP
FOR c2_rec IN c2
LOOP
IF(c1_rec.last_name == c2_rec.name) THEN
SELECT last_name into l_name from EMPLOYEE where substr(last_name,1,3) = c1_rec.last_name;
dbms_output.put_line(l_name);
ELSE
dbms_output.put_line("NULL");
END IF;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line("exception occured");
END;
I would rather stick with this approach:
SQL> with employee as
2 (select 'Mathews' name from dual
3 union all
4 select 'Smith' from dual
5 union all
6 select 'Rice' from dual
7 union all
8 select 'Black' from dual
9 union all
10 select 'Green' from dual
11 union all
12 select 'Larry' from dual
13 union all
14 select 'Cat' from dual),
15 emp_x as
16 (select 'Mataaa' pattern from dual
17 union all
18 select 'Mmitta' from dual
19 union all
20 select 'Smitta' from dual
21 union all
22 select 'Riceeeee' from dual)
23 select nvl(ex.pattern, 'NULL') result
24 from employee e
25 left outer join emp_x ex
26 on substr(ex.pattern, 1, 3) = substr(e.name, 1, 3);
RESULT
--------
Mataaa
Smitta
Riceeeee
NULL
NULL
NULL
NULL
7 rows selected
You didn't provide any information about how big are your tables, but anyway hash join in this query would be much faster then any procedural code. Of course if you need to use it in some procedure you could wrap it in cursor:
for c1 in (select ...)
loop
dbms_output.put_line(c1.result);
end loop;
This can be used for comparing collections using cursors:
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;