问题
I asked Oracle 11 SQL : Is there a way to split 1 row into x rows -- this question is very close to that but has a small twist ...
Customer asked to Split 1 row from the Oracle DB SQL into 6 rows.
Let's say, originally the SQL (complex sql with multiple joins , etc) is pulling in 9 columns:
select A, B, C, D, E, F, G, H, I
from X, Y, Z . . .
(but quite complex query)
1) A, B, C, D, E, F, G, H, I.
Now, customer is asking for every row returning above pattern, the new output should be like below :
1) A, B, C, 'Name for D : ', D
2) A, B, C, 'Name for E : ', E
3) A, B, C, 'Name for F : ', F
4) A, B, C, 'Name for G : ', G
5) A, B, C, 'Name for H : ', H
6) A, B, C, 'Name for I : ', I
Basically, the 1st 3 column values will be repeated in all the 6 New Rows.
The 4th column in the new row will be a string that says what the 5th column is about.
The procedure repeats for every row in the original query.
From the earlier answer, I know unpivot can do this -- just not able to wrangle this out myself.
UPDATE:
Actually, I wasn't clear in my question that the output for Column 4 that I wanted was not a straightaway concatenation. If that was the case, I could have done it myself. These values will not be a literal concatenation of D,E,F,G,H,I.
How about the 4th column values for D,E,F,G,H,I are the follows ? :
Lennon,
paul McCartney,
Ringo Starr,
George Harrison,
Pete Best,
Tommy Moore
So, the output will now look like :
1) A, B, C, 'Lennon : ', D
2) A, B, C, 'paul McCartney : ', E
3) A, B, C, 'Ringo Starr : ', F
4) A, B, C, 'George Harrison : ', G
5) A, B, C, 'Pete Best : ', H
6) A, B, C, 'Tommy Moore : ', I
I hope you get the idea that the values for the 4th column can be any string, not a derivative of the 5th column.
UPDATE2:
Suppose, my complex query, for illustration purpose, can be simplified to the classic Oracle Tables
Suppose, I run this SQL on those Emp and Dept tables :
select emp.empno, emp.ename, emp.job, emp.mgr, emp.hiredate, emp.sal , dept.deptno, dept.dname, dept.loc from emp, dept where emp.deptno = dept.deptno;
In my case, column 4 will be "mgr", "hiredate", "sal", "deptno", "dname" and "loc".
So, for example, for the following (original) result Row from the above query :
empno, ename, job, mgr, hiredate, sal , deptno, dname, loc
7698, BLAKE, MANAGER, 7839, 1981-05-01, 2850.00, 30, SALES, CHICAGO
The new 6 rows would be
7698 BLAKE MANAGER mgr 7839
7698 BLAKE MANAGER hiredate 1981-05-01
7698 BLAKE MANAGER sal 2850.00
7698 BLAKE MANAGER deptno 30
7698 BLAKE MANAGER dname SALES
7698 BLAKE MANAGER loc CHICAGO
What should I do to convert the above SQL to get the above new 6 Rows ?
回答1:
You can use unpivot clause. I think this code will help you:
select a,b,c,'Name for ' || name_code || ' : '|| name_code as value from
(select 'A' a ,'B' b ,'C' c ,'D' d,'E' e,'F' f,'G' g,'H' h,'I' i from dual)
unpivot include nulls
(
name_for for name_code in (d as 'D', e as 'E' ,f as 'F',g as 'G',h as 'H',i as 'I')
);
After the update of the question. The answer is changed to this:
select A,B,C,'Name for ' || name_for as value, name_code from
(select 1 A,2 B,3 C,'Lennon' D,'Paul McCartney' E, 'Ringo Starr' F,
null G, 'Pete Best'H, 'Tommy Moore' I from dual )
unpivot include nulls
(
name_for for name_code in (d,e,f,g,h,i)
)
回答2:
You can use unpivot
with concat()
function concat( concat('Name for ',val),' : ')
or concatenation operators (||
) as 'Name for '||val||' : '
:
with t(a,b,c,d,e,f,g,h,i) as
(
select 1,2,3,'Lennon','Paul McCartney', 'Ringo Starr',
null, 'Pete Best', 'Tommy Moore'
from dual
)
select a,b,c, concat( concat('Name for ',val),' : ') as explanation,
col
from
(
select nvl(to_char(a),' ') as a, nvl(to_char(b),' ') as b,
nvl(to_char(c),' ') as c, nvl(to_char(d),' ') as d,
nvl(to_char(e),' ') as e, nvl(to_char(f),' ') as f,
nvl(to_char(g),' ') as g, nvl(to_char(h),' ') as h,
nvl(to_char(i),' ') as i
from t
)
unpivot
( val for col in (d,e,f,g,h,i) )
order by col
Demo
Update 2 : Depending on your new case, the query can be rearranged as :
with t as
(
select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal ,
d.deptno, d.dname, d.loc
from emp e
join dept d
on e.deptno = d.deptno
)
select empno,ename,job, lower(col) as col, val
from
(
select to_char(empno) as empno,
ename,
job, to_char(mgr) as mgr,
to_char(hiredate,'yyyy-mm-dd') as hiredate,
to_char(sal,'fm999G990D00','NLS_NUMERIC_CHARACTERS = ''.,''') as sal,
to_char(deptno) as deptno, dname, loc
from t
)
unpivot include nulls
( val for col in (mgr,hiredate,sal,deptno,dname,loc) );
Demo2
回答3:
Just for something different, this can also be done "the old fashioned way" (i.e., without UNPIVOT
).
In this approach, cross join your complex query to a row generator (SELECT rownum FROM DUAL CONNECT BY rownum <= ...
). Then DECODE
on the rownum from the generator. Like this:
select your_complex_query.empno, your_complex_query.ename, your_complex_query.job,
DECODE(rn, 1, 'mgr',
2, 'hiredate',
3, 'sal',
4, 'deptno',
5, 'dname',
6, 'loc',
'*error: unmapped*') row_description,
DECODE(rn, 1, to_char(your_complex_query.mgr),
2, to_char(your_complex_query.hiredate, 'DD-MON-YYYY'),
3, to_char(your_complex_query.sal, 'FM999,990.00'),
4, to_char(your_complex_query.deptno),
5, your_complex_query.dname,
6, your_complex_query.loc,
'*error: unmapped*') row_value
from ( select emp.empno, emp.ename, emp.job, emp.mgr, emp.hiredate, emp.sal , dept.deptno, dept.dname, dept.loc from scott.emp, scott.dept where emp.deptno = dept.deptno) your_complex_query,
( SELECT rownum rn FROM dual CONNECT BY rownum <= 6 ) rn
;
Hopefully that's clear as to where your complex query goes.
Results:
+-------+-------+-----------+-----------------+------------+ | EMPNO | ENAME | JOB | ROW_DESCRIPTION | ROW_VALUE | +-------+-------+-----------+-----------------+------------+ | 7839 | KING | PRESIDENT | mgr | - | | 7839 | KING | PRESIDENT | hiredate | 17-Nov-81 | | 7839 | KING | PRESIDENT | sal | 5,000.00 | | 7839 | KING | PRESIDENT | deptno | 10 | | 7839 | KING | PRESIDENT | dname | ACCOUNTING | | 7839 | KING | PRESIDENT | loc | NEW YORK | | 7698 | BLAKE | MANAGER | mgr | 7839 | | 7698 | BLAKE | MANAGER | hiredate | 1-May-81 | | 7698 | BLAKE | MANAGER | sal | 2,850.00 | | 7698 | BLAKE | MANAGER | deptno | 30 | | 7698 | BLAKE | MANAGER | dname | SALES | | 7698 | BLAKE | MANAGER | loc | CHICAGO | | 7782 | CLARK | MANAGER | mgr | 7839 | | 7782 | CLARK | MANAGER | hiredate | 9-Jun-81 | | 7782 | CLARK | MANAGER | sal | 2,450.00 | | 7782 | CLARK | MANAGER | deptno | 10 | | 7782 | CLARK | MANAGER | dname | ACCOUNTING | | 7782 | CLARK | MANAGER | loc | NEW YORK | | 7566 | JONES | MANAGER | mgr | 7839 | | 7566 | JONES | MANAGER | hiredate | 2-Apr-81 | | 7566 | JONES | MANAGER | sal | 2,975.00 | | 7566 | JONES | MANAGER | deptno | 20 | | 7566 | JONES | MANAGER | dname | RESEARCH | | 7566 | JONES | MANAGER | loc | DALLAS | | 7788 | SCOTT | ANALYST | mgr | 7566 | | 7788 | SCOTT | ANALYST | hiredate | 19-Apr-87 | | 7788 | SCOTT | ANALYST | sal | 3,000.00 | | 7788 | SCOTT | ANALYST | deptno | 20 | | 7788 | SCOTT | ANALYST | dname | RESEARCH | | 7788 | SCOTT | ANALYST | loc | DALLAS | | 7902 | FORD | ANALYST | mgr | 7566 | | 7902 | FORD | ANALYST | hiredate | 3-Dec-81 | | 7902 | FORD | ANALYST | sal | 3,000.00 | | 7902 | FORD | ANALYST | deptno | 20 | | 7902 | FORD | ANALYST | dname | RESEARCH | | 7902 | FORD | ANALYST | loc | DALLAS | | 7369 | SMITH | CLERK | mgr | 7902 | | 7369 | SMITH | CLERK | hiredate | 17-Dec-80 | | 7369 | SMITH | CLERK | sal | 800 | | 7369 | SMITH | CLERK | deptno | 20 | | 7369 | SMITH | CLERK | dname | RESEARCH | | 7369 | SMITH | CLERK | loc | DALLAS | | 7499 | ALLEN | SALESMAN | mgr | 7698 | | 7499 | ALLEN | SALESMAN | hiredate | 20-Feb-81 | | 7499 | ALLEN | SALESMAN | sal | 1,600.00 | | 7499 | ALLEN | SALESMAN | deptno | 30 | | 7499 | ALLEN | SALESMAN | dname | SALES | | 7499 | ALLEN | SALESMAN | loc | CHICAGO | | 7521 | WARD | SALESMAN | mgr | 7698 | | 7521 | WARD | SALESMAN | hiredate | 22-Feb-81 | +-------+-------+-----------+-----------------+------------+
回答4:
If results has to be materialized the easiest way is to use INSERT ALL
:
INSERT ALL
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'mgr', mgr)
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'hiredate', hiredate)
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'sal', sal)
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'deptno', deptno)
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'dname', dname)
INTO t(empno, ename, job, "key", "value") VALUES (empno, ename, job, 'loc', loc)
select emp.empno, emp.ename, emp.job, emp.mgr, emp.hiredate, emp.sal
,dept.deptno, dept.dname, dept.loc
from emp
join dept
on emp.deptno = dept.deptno;
SELECT * FROM t;
db<>fiddle demo
回答5:
Regarding to UPDATE2, this seems to do return the desired output:
with o as (select emp.empno, emp.ename, emp.job, emp.mgr, emp.hiredate, emp.sal , dept.deptno, dept.dname, dept.loc from emp, dept where emp.deptno = dept.deptno)
select o.empno, o.ename, o.job, j.column_name, j.value FROM o inner join (
select o.empno, 'MGR' as column_name, TO_CHAR(o.mgr) as value FROM o UNION ALL
select o.empno, 'HIREDATE', TO_CHAR(o.hiredate) FROM o UNION ALL
select o.empno, 'SAL', TO_CHAR(o.sal) FROM o UNION ALL
select o.empno, 'DEPTNO', TO_CHAR(o.deptno) FROM o UNION ALL
select o.empno, 'DNAME', TO_CHAR(o.dname) FROM o UNION ALL
select o.empno, 'LOC', TO_CHAR(o.loc) FROM o) j
on (o.empno = j.empno)
order by o.empno;
However, you cannot use dynamic no. of columns and all the 'values' must be convertible to string. I'm not sure if you can get any further without dynamic SQL.
回答6:
And the dark side of xquery ;)
with big_query as (select 7698 empno, 'BLAKE' ename, 'MANAGER' job, 7839 mgr, to_date('1981-05-01','yyyy-mm-dd') hiredate, 2850.00 sal , 30 deptno, 'SALES' dname, 'CHICAGO' loc from dual
union all
select 7698 empno, 'BLAKE2' ename, 'MANAGER2' job, 7839 mgr, to_date('1981-05-01','yyyy-mm-dd') hiredate, 2850.00 sal , 30 deptno, 'SALES2' dname, 'CHICAGO2' loc from dual
),
xmlwrap as (select xmlforest(EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,DEPTNO,DNAME,LOC) xml_body from big_query)
select x.* from xmlwrap,xmltable('for $i in (4 to 9)
return(
<ROW>
<C1>{$doc/*[1]/text()}</C1>
<C2>{$doc/*[2]/text()}</C2>
<C3>{$doc/*[3]/text()}</C3>
<C4>{concat("Name for ", $doc/*[$i]/name())}</C4>
<C5>{$doc/*[$i]/text()}</C5>
</ROW>
)
'
passing xml_body as "doc"
COLUMNS
"C1" varchar2(100) PATH '/ROW/C1',
"C2" varchar2(100) PATH '/ROW/C2',
"C3" varchar2(100) PATH '/ROW/C3',
"C4" varchar2(100) PATH '/ROW/C4',
"C5" varchar2(100) PATH '/ROW/C5'
) x;
xmlforest
- put your columns here in correct order and change big_query.
回答7:
This worked for me on employees and departments hr tables But first, all the columns need to be converted to char in with clause.
This is my SQL
with a as (select emp.employee_id, emp.first_name, emp.job_id, to_char(emp.MANAGER_ID) as MANAGER_ID,
to_char(emp.HIRE_DATE,'YYYYMMDD') as HIRE_DATE, to_char(emp.salary) as salary,
to_char(dept.department_id) as department_id ,
to_char(dept.department_name) as department_name ,
to_char(dept.location_id) as location_id
from HR.employees emp, HR.departments dept where emp.department_id = dept.department_id)
select * from a
unpivot
(Value for Detail in ( MANAGER_ID as 'MANAGER_ID',
HIRE_DATE as 'HIRE_DATE',salary as 'SALARY',
department_id as 'department_id',
department_name as 'department_name',
location_id as 'location_id'));
来源:https://stackoverflow.com/questions/59244741/oracle-11-sql-split-1-row-into-x-rows-and-insert-a-new-column