I need to pivot the table. Following are my queries.
CREATE TABLE SurveyResponses (
id int,
question varchar(255),
answer varchar(255)
);
INSERT INTO SurveyResponses (id, question, answer) values (1, 'question 1', 'responseX');
INSERT INTO SurveyResponses (id, question, answer) values (2, 'question 1', 'responseY');
INSERT INTO SurveyResponses (id, question, answer) values (3, 'question 1', 'responseZ');
INSERT INTO SurveyResponses (id, question, answer) values (4, 'question 2', 'responseA');
INSERT INTO SurveyResponses (id, question, answer) values (5, 'question 2', 'responseB');
INSERT INTO SurveyResponses (id, question, answer) values (6, 'question 3', 'responseC');
When I try to use pivot the table with the question as columns I get
ORA-00936: missing expression
What is that I'm missing in my query?
I want it to be dynamic because I have 500+ question in the real table.
Query
SELECT * FROM (
SELECT id, question, answer from SurveyResponses
) SurveyResponsesResults
PIVOT (
MAX(answer)
FOR question
In(
SELECT DISTINCT question FROM SurveyResponses
)
) As pivottable;
IN can't be dynamic. Use line #7 instead of (commented) line #8.
SQL> select * from (
2 select id, question, answer from SurveyResponses
3 ) SurveyResponsesResults
4 PIVOT (
5 max(answer)
6 For question
7 In('question 1' as q1, 'question 2' as q2, 'question 3' as q3
8 --SELECT DISTINCT question FROM SurveyResponses
9 )
10 ) ;
ID Q1 Q2 Q3
---------- ---------- ---------- ----------
1 responseX
6 responseC
2 responseY
4 responseA
5 responseB
3 responseZ
6 rows selected.
SQL>
Extending #Littlefoot answer.
If I have to use dynamic input in IN.
I will have to use PIVOT XML with xmlserialize the query is as follows.
SELECT xmlserialize(content pivottable.question_XML) from (
SELECT id, question, answer from SurveyResponses
) SurveyResponsesResults
PIVOT XML (
Max(answer)
FOR question
In(
SELECT DISTINCT question FROM SurveyResponses
)
) pivottable ;
The query works but it outputs in XML format.
I'm trying to figure out how to convert the XML to TABLE. If you know feel free to edit.
Related
I have two tables,PRODUCTS AND LOOKUP TABLES.Now i want to order the KEY Column in products table based on KEY column value in LOOKUP TABLE.
CREATE TABLE PRODUCTS
(
ID INT,
KEY VARCHAR(50)
)
INSERT INTO PRODUCTS
VALUES (1, 'EGHS'), (2, 'PFE'), (3, 'EGHS'),
(4, 'PFE'), (5, 'ABC')
CREATE TABLE LOOKUP (F_KEY VARCHAR(50))
INSERT INTO LOOKUP VALUES('PFE,EGHS,ABC')
Now I want to order the records in PRODUCTS table based on KEY (PFE,EGHS,ABC) values in LOOKUP table.
Example output:
PRODUCTS
ID F_KEY
-----------
2 PFE
4 PFE
1 EGHS
3 EGHS
5 ABC
I use this query, but it is not working
SELECT *
FROM PRODUCTS
ORDER BY (SELECT F_KEY FROM LOOKUP)
You can split the string using XML. You first need to convert the string to XML and replace the comma with start and end XML tags.
Once done, you can assign an incrementing number using ROW_NUMBER() like following.
;WITH cte
AS (SELECT dt,
Row_number()
OVER(
ORDER BY (SELECT 1)) RN
FROM (SELECT Cast('<X>' + Replace(F.f_key, ',', '</X><X>')
+ '</X>' AS XML) AS xmlfilter
FROM [lookup] F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(500)') AS DT
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT P.*
FROM products P
LEFT JOIN cte C
ON C.dt = P.[key]
ORDER BY C.rn
Online Demo
Output:
ID F_KEY
-----------
2 PFE
4 PFE
1 EGHS
3 EGHS
5 ABC
You may do it like this:
SELECT ID, [KEY] FROM PRODUCTS
ORDER BY
CASE [KEY]
WHEN 'PFE' THEN 1
WHEN 'EGHS' THEN 2
WHEN 'ABC' THEN 3
END
I have created a temp table with pivot as below:
INSERT into A_temp_table(PER_KEY, LANDLINE, FAX, MOBILE)
select * from
(select PER_KEY, TEL_NUM, from ABC.V_PER_TEL)
pivot(max(TEL_NUM) for TEL_TYPE_DESC in ('LANDLINE', 'FAX', 'MOBILE'));
After this I am fetching the data using joins as below:
SELECT P.PERSON_KEY,
P.FIRST_NAME,
P.MIDDLE_NAME,
P.LAST_NAME,
PT.LANDLINE,
PT.FAX,
PT.MOBILE,
FROM ABC.V_PER P
LEFT OUTER JOIN A_temp_table PT ON P.PER_KEY=PT.PER_KEY
My Question is:
Can I replace the temp table used in join with the select statement directly.
Like below:
SELECT P.PERSON_KEY,
P.FIRST_NAME,
P.MIDDLE_NAME,
P.LAST_NAME,
PT.LANDLINE,
PT.FAX,
PT.MOBILE,
FROM ABC.V_PER P
LEFT OUTER JOIN (select * from
(select PER_KEY, TEL_NUM, from ABC.V_PER_TEL)
pivot(max(TEL_NUM) for TEL_TYPE_DESC in ('LANDLINE', 'FAX', 'MOBILE'))) PT ON P.PER_KEY=PT.PER_KEY
But, in doing the above step My code is not running.
Is there any other way to remove temp table use, and why my way is not working.
Please help,
The problem is that you need to use alias for the columns from the PIVOT; for example:
create table testPivot(v varchar2(10), n number);
insert into testPivot values ('A', 10);
insert into testPivot values ('A', 10);
insert into testPivot values ('B', 10);
This will give error:
SQL> select A
2 from (
3 select *
4 from testPivot
5 pivot(max(n) for v in ('A', 'B'))
6 );
select A
*
ERROR at line 1:
ORA-00904: "A": invalid identifier
This will work:
SQL> select A
2 from (
3 select *
4 from testPivot
5 pivot(max(n) for v in ('A' as A, 'B' as B))
6 );
A
----------
10
i have 2 tables .
The columns start with attributes are change based on department. the description of attributes are here
My requirement is to get the values of each attributes with its primary key based on the department as table bellow.
Honestly i am stuck on this problem in my program. I have no permission to change the tables and there is no common unique key column.i would appreciate if anyone could provide me a suggestion.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
e as (
select employeeId, department, attribute1, 1 rn from employees union all
select employeeId, department, attribute2, 2 rn from employees union all
select employeeId, department, attribute3, 3 rn from employees
)
select e.employeeId, a.attributeid, e.department, a.attribute, a.meaning,
e.attribute1 as value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
Test data and output:
create table employees (employeeID number(3), name varchar2(10), department varchar2(5), age number(3), attribute1 varchar2(10), attribute2 varchar2(10), attribute3 varchar2(10));
insert into employees values (1, 'john', 'IT', 22, 'attr1val1', 'attr2val2', null);
insert into employees values (2, 'jane', 'HR', 32, 'attr1val3', 'attr2val4', 'attr3val5');
insert into employees values (3, 'joe', 'HR', 23, 'attr1val6', 'attr2val7', 'attr3val8');
insert into employees values (4, 'jack', 'IT', 45, 'attr1val9', 'attr2val10', null);
create table attributes (attributeID number(3), department varchar2(10), attribute varchar2(10), meaning varchar2(10));
insert into attributes values (1, 'IT', 'attribute1', 'laptoptype');
insert into attributes values (2, 'IT', 'attribute2', 'networkloc');
insert into attributes values (3, 'HR', 'attribute1', 'location');
insert into attributes values (4, 'HR', 'attribute2', 'position');
insert into attributes values (5, 'HR', 'attribute3', 'allocation');
EMPLOYEEID ATTRIBUTEID DEPARTMENT ATTRIBUTE MEANING VALUE
---------- ----------- ---------- ---------- ---------- ----------
1 1 IT attribute1 laptoptype attr1val1
1 2 IT attribute2 networkloc attr2val2
2 3 HR attribute1 location attr1val3
2 4 HR attribute2 position attr2val4
2 5 HR attribute3 allocation attr3val5
3 3 HR attribute1 location attr1val6
3 4 HR attribute2 position attr2val7
3 5 HR attribute3 allocation attr3val8
4 1 IT attribute1 laptoptype attr1val9
4 2 IT attribute2 networkloc attr2val10
Edit: Explanation
In answer I used with
clause just to divide solution into readable steps. You can move them into from clause of main query if it is
more comfortable for you. Anyway: subquery a reads data from table attributes and adds number for rows,
so for each department they are allways numbered from 1. I used row_number() for that. Subquery e unions (all) required attributes and numbers
them accordingly. Numbers generated in both subqueries are then used in main join: a.department=e.department and a.rn=e.rn.
Alternative 1 - if you are using Oracle 11g you could use the unpivot. See what is generated by subquery, and how it is joined with attributes table:
with e as (
select employeeId, name, department, attribute, value from employees
unpivot (value for attribute in ("ATTRIBUTE1", "ATTRIBUTE2", "ATTRIBUTE3"))
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join attributes a on a.department=e.department
and lower(a.attribute)=lower(e.attribute)
order by e.employeeId, a.attributeid;
Alternative 2 - with hierarchical subquery generator (subquery r), realised by connect by which simple creates numbers from 1, 2, 3 which are next joined with employees and proper attribute
is attached as value in case clause. Rest is made in similiar way like in original answer.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
r as (select level rn from dual connect by level<=3),
e as (
select employeeId, department, rn,
case when r.rn = 1 then attribute1
when r.rn = 2 then attribute2
when r.rn = 3 then attribute3
end value
from employees cross join r
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
All three versions gave me the same output. I also tested first option on similiar table with 100k rows and get output in few seconds (for 5 attributes). Please test all solutions and try to understand them. If you can use unpivot version I would prefer this.
Sorry for delayed explanation and any language mistakes.
The WITH clause was added with Oracle 9.2 and should do the trick. For the other attributes just add more sub queries where the filter is att.attribute = 'attribute2' or 'Attribute3'...
WITH e AS
(SELECT emp.employee_ID, emp.department, emp.attribute1
FROM employee emp),
a AS (SELECT att.attribute_id, att.attribute, att.meaning
FROM attribute_TYPE att
WHERE att.attribute = 'attribute1')a
SELECT e.employeeid, att.attributeid, e.department, a.attribute,
a.meaning e.attribute1
FROM e JOIN a ON e.department = a.department
Id Field_id Field_value
------------------------------
1 10 'A'
1 11 'B'
1 12 'C'
I want to make rows like
Id Field_id Field_value data_1 data_2
--------------------------------------
1 10 'A' 'B' 'C'
Pl help.
Take a look at this:
with t (Id, Field_id, Field_value) as (
select 1, 10, 'A' from dual union all
select 1, 11, 'B' from dual union all
select 1, 12, 'C' from dual
)
select FIELD_ID1, "10_FIELD_ID", "11_FIELD_ID","12_FIELD_ID"
from (select id, field_id, min(field_id) over() field_id1, field_value from t)
pivot (
max(field_value) field_id
for field_id in (10, 11, 12)
)
FIELD_ID1 10_FIELD_ID 11_FIELD_ID 12_FIELD_ID
---------------------------------------------------
10 A B C
Read more about pivot here
Normally, people look for PIVOT query, however, your input data is not at all duplicate as already mentioned in comments.
What you could do is, using subquery factoring, select id, 10 as field_id, field_value, thus making field_it static value as 10. Then use LISTAGG. But, the grouped rows would be a single column as delimeter seperated values.
I hope your requirement is exactly what you stated and not a typo or a mistake while posting.
I am new to Oracle technology. Earlier I posted 2 posts for the same issue due to lack of understanding the requirement.
Table 1:
MSGID
-----
1,2,3
2,3
4
null
null
Table 2:
MID MSGDESC
---- -------
1 ONE
2 TWO
3 THREE
4 FOUR
Expected output:
XCOL DESC
----- -----
1,2,3 ONE,TWO,THREE
2,3 TWO,THREE
4 FOUR
I am not able to fulfil this requirement. Please provide me one solution.
Note: tables don't have any unique or primary key values. Table 1 has 5000 records and table 2 only has 80 records with descriptions.
create table Table1 (MSGID varchar2(100));
insert into Table1 values ('1,2,3');
insert into Table1 values ('2,3');
insert into Table1 values ('4');
insert into Table1 values (null);
insert into Table1 values (null);
create table Table2 (MID varchar2(100), MSGDESC varchar2(100));
insert into Table2 values ('1','ONE');
insert into Table2 values ('2','TWO');
insert into Table2 values ('3','THREE');
insert into Table2 values ('4','FOUR');
select
msgid as xcol,
"DESC",
col1, col2, ..., col12
from
Table1
left join (
select
msgid,
wm_concat(msgdesc) as "DESC"
from
(
select
msgid,
msgdesc
from
(select distinct msgid from Table1 where ...)
cross join (
select level as occ from dual connect by level <= 100)
)
left join Table2
on mid = regexp_substr(msgid, '[^,]+', 1, occ)
where
occ <= regexp_count(msgid, ',') + 1
order by msgid, occ
)
group by msgid
) using (msgid)