Oracle SQL Pivot Command Not Working -- "Invalid Idenitifier" - sql

I am trying to do a simple query in Oracle SQL using Pivot. I have 12 columns in my table, but only the first four are of interest. The columns are "FACILITY_ID" (Unique Identifier), "REPORTING_YEAR" (Year data was reported), "SUBPART" (Category of data wanting to pivot by - values acceptable between A and Z), and "CO2E" (floating point number reported). I want to take only the data from subparts C and K (the third column) and add them up into their own columns.
For example:
If my original table looks like:
FACILITY_ID --- REPORTING_YEAR --- SUBPART --- CO2E
10 -------------- 2020 ---------------------- C -------- 10
11 -------------- 2020 ---------------------- K -------- 20
10 -------------- 2020 ---------------------- K -------- 40
10 -------------- 2020 ---------------------- K -------- 40
11 -------------- 2020 ---------------------- C -------- 30
I would like to get something like:
FACILITY_ID --- REPORTING_YEAR ----- C ----- K
10 -------------- 2020 ----------------- 10 ----- 80
11 -------------- 2020 ----------------- 30 ----- 20
My code as of now is as follows:
Create Table CandK_emissions as
Select
FACILITY_ID,
REPORTING_YEAR,
SUBPART,
CO2E
From
facilityReport
Pivot
(
Sum(CO2E)
For SUBPART
in ('C',
'K')
)
The error I'm getting is 'Ora-00904: "CO2e": Invalid Identifier'. I've double checked that everything is spelled correctly. I'm not sure where I'm going wrong.

The SELECT clause must show what columns will exist in the output, not what columns are used from the base table. In your query, you are aggregating the CO2E values, separately for different values of SUBPART. The result of pivoting doesn't have CO2E values anymore, or SUBPART values; instead, the two SUBPART values you are interested in, C and K, become two distinct columns in the output, and the sums of CO2E appear in these two new columns.
Your query should SELECT columns C and K; the error message is telling you that after pivoting, there is no CO2E column to select. (After you change that, it would tell you the same thing about SUBPART.)
Here is how this should look. Note a few things: In the "test data" I simulated one more column, a primary key named PK, to demonstrate that you must EXCLUDE it first, in an inline view (the sub-SELECT in the FROM clause). Also note that, to make the PIVOT as efficient as possible, in the subquery I only select the rows we need - those where the SUBPART is either C or K. We don't need to see the other rows.
The WITH clause is just for testing - remove it, and use your actual table and column names in the query.
with
test_data (pk, facility_id, reporting_year, subpart, co2e) as (
select 335, 10, 2020, 'C', 10 from dual union all
select 440, 11, 2020, 'K', 20 from dual union all
select 482, 10, 2020, 'K', 40 from dual union all
select 106, 10, 2020, 'K', 40 from dual union all
select 476, 11, 2020, 'C', 30 from dual
)
-- end of test data; actual query begins below this line
select facility_id, reporting_year, c, k
from (
select facility_id, reporting_year, subpart, co2e
from test_data
where subpart in ('C', 'K')
)
pivot (sum(co2e) for subpart in ('C' as c, 'K' as k))
order by facility_id, reporting_year -- or whatever is needed
;
FACILITY_ID REPORTING_YEAR C K
----------- -------------- ---------- ----------
10 2020 10 80
11 2020 30 20
Note - it may be simpler (and more efficient) to do away with PIVOT altogether, and to "pivot" the old way, using conditional aggregation (the way pivoting was done before the PIVOT operator was introduced). Something like this:
select facility_id, reporting_year,
sum(case subpart when 'C' then co2e end) as c,
sum(case subpart when 'K' then co2e end) as k
from test_data
group by facility_id, reporting_year
order by facility_id, reporting_year
;

Related

Fetching XML tag data stored in a varchar2 column

I have a table as below:
ID Name Roll Data
1 ABC 12 <ngc_mid_nu_3><tid_nu><status>A</status>
<term_no>03</term_no>
</tid_nu><tid_nu><status>A</status>
<term_no>04</term_no>
</tid_nu><ngc_mid_nu_3>
2 XYZ 32 <ngc_mid_nu_3><tid_nu><status>B</status>
<term_no>08</term_no>
</tid_nu><tid_nu><status>A</status>
<term_no>04</term_no>
</tid_nu><ngc_mid_nu_3>
All the columns are varchar2 including the column named Data.
I want to fetch the values present inside <status> tag and <term_no> tag for each row. How can I do that?
You can cross-join your table rows with an XMLTable that extracts all of the fields you want:
select t.id, t.name, t.roll, x.status, x.term_no
from your_table t
cross join xmltable ('/ngc_mid_nu_3/tid_nu'
passing xmltype(t.data)
columns status varchar2(10) path 'status',
term_no varchar2(10) path 'term_no'
) x;
Set the columns data types and sizes appropriately for the data you expect to really see.
Demo with your sample data in a CTE (with fixed closing tags in your XML - i.e. </ngc_mid_nu_3> instead of <ngc_mid_nu_3>):
with your_table (ID, Name, Roll, Data) as (
select 1, 'ABC', 12, '<ngc_mid_nu_3><tid_nu><status>A</status>
<term_no>03</term_no>
</tid_nu><tid_nu><status>A</status>
<term_no>04</term_no>
</tid_nu></ngc_mid_nu_3>' from dual
union all
select 2, 'XYZ', 32, '<ngc_mid_nu_3><tid_nu><status>B</status>
<term_no>08</term_no>
</tid_nu><tid_nu><status>A</status>
<term_no>04</term_no>
</tid_nu></ngc_mid_nu_3>' from dual
)
select t.id, t.name, t.roll, x.status, x.term_no
from your_table t
cross join xmltable ('/ngc_mid_nu_3/tid_nu'
passing xmltype(t.data)
columns status varchar2(10) path 'status',
term_no varchar2(10) path 'term_no'
) x;
ID NAME ROLL STATUS TERM_NO
---------- ---------- ---------- ---------- ----------
1 ABC 12 A 03
1 ABC 12 A 04
2 XYZ 32 B 08
2 XYZ 32 A 04
If your real XML strings are actually malformed - missing the slash in the final closing tag - then you need to fix that first.

Oracle transpose a simple table

I have a simple table from a select query that looks like this
CATEGORY | EQUAL | LESS | GREATER
VALUE | 60 | 100 | 20
I want to be able to transpose it so it looks like this
CATEGORY | VALUE
EQUAL | 60
LESS | 100
GREATER | 20
I tried using the pivot function in oracle but I can't seem to get it to work.
I've tried looking all over online but I can't find anything that will help me.
Any help is much appreciated thank you!
Using unpivot -
CREATE TABLE Table1
(CATEGORY varchar2(5), EQUAL int, LESS int, GREATER int)
;
INSERT ALL
INTO Table1 (CATEGORY, EQUAL, LESS, GREATER)
VALUES ('VALUE', 60, 100, 20)
SELECT * FROM dual
;
Query -
select COL AS CATEGORY,VALUE from table1
unpivot (value for COL in (EQUAL, LESS, GREATER));
Result -
CATEGORY VALUE
EQUAL 60
LESS 100
GREATER 20
You can use union all:
select 'EQUAL' as category, equal as value from t union all
select 'LESS' as category, less from t union all
select 'GREATER' as category, greater from t;
If you had a large table, you might want to try some other method (such as a lateral join in Oracle 12c). But for a small table, this is fine.
You may unpivot your values by contribution of DECODE and CONNECT BY clauses :
select decode(myId, 1, 'EQUAL',
2, 'LESS',
3, 'GREATER') as category,
decode(myId, 1, EQUAL,
2, LESS,
3, GREATER) as value
from mytable
cross join (select level as myId from dual connect by level <= 3);
CATEGORY VALUE
-------- -----
EQUAL 60
LESS 100
GREATER 20
SQL Fiddle Demo

how to convert table using pivot

I've got a set of data that looks something like this:-
product May_Qty June_Qty
--------- --- -----------
p1 2 44
p2 1 54
p3 5 55
i want the result like: (using pivot) or other methods
product Month Qty
--------- --- -----------
p1 May 2
p1 June 44
p2 May 1
p2 June 54
p3 May 5
p3 June 55
I must admit: The whole design smells a bit. Normally it is the other way round: Data is in a list and people want it as pivoted (wide) list for displaying purpose. So consider to work with a table with the design you want as target at the moment...
If you really want to stick with this, here are two approaches which produce the same output:
DECLARE #tbl TABLE(product VARCHAR(100),May_Qty INT,June_Qty INT);
INSERT INTO #tbl VALUES
('p1',2,44)
,('p2',1,54)
,('p3',5,55);
With UNION ALL you create a derived table with the values as list and then you sort it
SELECT product,[Month],Qty
FROM
(
SELECT product,5 AS MonthIndex,'May' AS [Month],May_Qty As Qty
FROM #tbl
UNION ALL
SELECT product,6,'June',June_Qty
FROM #tbl
) AS parted
ORDER BY product,MonthIndex;
UNPIVOT does roughly the same:
SELECT up.*
FROM
(SELECT product, May_Qty AS May, June_Qty AS June FROM #tbl) AS tbl
UNPIVOT
(Qty FOR [Month] IN(May,June)) AS up

SQL - Given table with three entries per day, how to write new table with one entrie per day and three columns

I have three algorithms that run once a day at 7 AM. Each of this algorithms writes its results to a table. Namely, each algorithm writes its Result, the Date of the result and its IDentification. So, everyday, three lines are added to the table, like the following:
ID Result Date
A 35 21/04/2016
B 27 21/04/2016
C 31 21/04/2016
For enhanced reading, I now want to produce a second table where the entries for the same day are stored in the same record, like the following:
Date A B C
21/04/2016 35 27 31
Can you please help?
(SQL Server 2012)
Here is a simple demo of the PIVOT solution for completeness of the answers. I find the PIVOT solution more elegant that conditional aggregation, but it comes at the cost of being less adaptable.
DECLARE #T TABLE (ID CHAR(1), Result INT, Date DATE);
INSERT #T (ID, Result, Date)
VALUES ('A', 35, '2016-04-21'), ('B', 27, '2016-04-21'), ('C', 35, '2016-04-21');
SELECT pvt.Date, pvt.A, pvt.B, pvt.C
FROM #T AS T
PIVOT (MAX(Result) FOR ID IN (A, B, C)) AS pvt;
RESULT
Date A B C
-----------------------
2016-04-21 35 27 35
This is a pivot query. You can use an explicit pivot. I think conditional aggregation is easy enough:
select date,
max(case when id = 'A' then result end) as a,
max(case when id = 'B' then result end) as b,
max(case when id = 'C' then result end) as c
from t
group by date
order by date;

PL/SQL SELECT with multiple COUNT(DISTINCT xxx) - unexpected results

I'm trying to put together a query for an Oracle 11g application and I've run into a problem.
I'll simplify the real scenario to make it easier to understand (and also to protect the client's data):
Table A is the base table. It has a known identifier in it that I pass in to the query.
For each entry in Table A there may be multiple entries in Table B. Table B contains a value that I am interested in.
For each entry in Table B there may also be multiple entries in Table C. Table C contains another value I'm interested in.
I also have an XML snippet containing a list of values that may or may not match up to the values of interest in table C.
The query does an outer join to the XML so that if there is a matching value it will return the value again, otherwise it is null.
What I want to do is get back the identifier I passed in, a count of the unique values in B and C, as well as a count of the unique (and non-null) values from the XML part of the join.
My current query is:
SELECT
a.ID
, COUNT(DISTINCT b.VAL) AS B_VAL
, COUNT(DISTINCT c.VAL) AS C_VAL
, COUNT(DISTINCT xml.VAL) AS XML_VAL
FROM a, b, c,
XMLTABLE('/field1/collection/value' passing my_xml_type
COLUMNS VAL VARCHAR2(50) PATH '.') xml
WHERE
a.ID = b.SOME_ID
AND b.OTHER_ID = c.OTHER_ID
AND c.VAL = xml.VAL (+)
Now if you forget about the counting and just return rows, an example result set might look something like this:
ID B_VAL C_VAL XML_VAL
---------------------------------------
X abc 123 123
X abc 456 null
X abc 789 789
X abc 789 789
DESIRED: Now when I want to do the distinct counts, I'd like it to return:
ID B_VAL C_VAL XML_VAL
---------------------------------------
X 1 3 2
ACTUAL: However, this is what I'm getting when I have them all as COUNT(DISTINCT ...):
ID B_VAL C_VAL XML_VAL
---------------------------------------
X 1 1 1
ALTERNATIVE: ...and if I take the DISTINCT out of the counts then I get:
ID B_VAL C_VAL XML_VAL
---------------------------------------
X 1 4 3
How come the DISTINCT seems to be operating only within a particular B_VAL, but taking it out causes it to operate across all the rows but not taking uniqueness into account?
Is there another way of doing this that doesn't involve having to replicate all the joins as a sub-query? Have I missed the point entirely?
(Please note, I'm not a DB developer at all, I've just been pulled in to help out, so sorry if this is an easy problem... I HAVE searched Google and browsed this site for answers before posting, though!)
Thanks.
I've found that if I take the XML table join out then the count distinct works OK across the B_VAL and C_VAL... So perhaps it's something weird with how Oracle handles XML table joins?
As Vincent's test case works in 10.2.0.3 and 11.2.0.2, and if you're at an earlier version of 11g, this could be bug 8816675: XMLexists query returns wrong results with a select DISTINCT. The example in the bug is referring to a problem with count(distinct). You aren't explicitly using XMLexists, but the bug may have a wider impact then the title suggests, or it may be used under the hood.
If this is the problem, and you can't patch up, you might be able to work around it by wrapping the non-count version, which still isn't pretty:
SELECT
A_ID
, COUNT(DISTINCT B_VAL) AS B_VAL
, COUNT(DISTINCT C_VAL) AS C_VAL
, COUNT(DISTINCT XML_VAL) AS XML_VAL
FROM (
SELECT a.ID as A_ID, b.VAL as B_VAL, c.VAL as C_VAL, xml.VAL as XML_VAL
FROM a, b, c
, XMLTABLE('/field1/collection/value' passing my_xml_type
COLUMNS VAL VARCHAR2(50) PATH '.') xml
WHERE a.ID = b.SOME_ID
AND b.OTHER_ID = c.OTHER_ID
AND c.VAL = xml.VAL (+)
)
GROUP BY A_ID;
I can't reproduce your finding with Oracle 10.2.0.3.
Here's my setup:
SQL> CREATE TABLE a AS SELECT 'X' ID FROM dual;
Table created
SQL> CREATE TABLE b AS SELECT 'abc' val, 'X' some_id, 1 other_id FROM dual;
Table created
SQL> CREATE TABLE c AS
2 SELECT 1 other_id, '123' val,
3 XMLTYPE('<field1>
4 <collection><value>123</value></collection>
5 </field1>') my_xml_type
6 FROM dual UNION ALL
7 SELECT 1 other_id, '456' val, NULL FROM dual UNION ALL
8 SELECT 1 other_id, '789' val,
9 XMLTYPE('<field1>
10 <collection><value>789</value></collection>
11 <collection><value>789</value></collection>
12 </field1>') my_xml_type
13 FROM dual;
Table created
the query returns the right result:
SQL> SELECT
2 a.ID
3 , COUNT(DISTINCT b.VAL) AS B_VAL
4 , COUNT(DISTINCT c.VAL) AS C_VAL
5 , COUNT(DISTINCT xml.VAL) AS XML_VAL
6 FROM a, b, c
7 , XMLTABLE('/field1/collection/value' passing my_xml_type
8 COLUMNS VAL VARCHAR2(50) PATH '.') xml
9 WHERE a.ID = b.SOME_ID
10 AND b.OTHER_ID = c.OTHER_ID
11 AND c.VAL = xml.VAL (+)
12 GROUP BY a.id;
ID B_VAL C_VAL XML_VAL
-- ---------- ---------- ----------
X 1 3 2
Can you run this test case?