Add null values when pivoting SQL - Oracle [duplicate] - sql

This question already has answers here:
Oracle SQL pivot query
(4 answers)
Closed last month.
I have a table with stock prices ordered by dates. I only have those 5 stocks, but I have hundreds of dates. I would like to pivot the stocks, but I have no idea on how to do that.
Data:
Date |Stock| Price |
30/09/22 | A | 100.5 |
30/09/22 | B | 151.3 |
30/09/22 | C | 233.4 |
30/09/22 | D | 237.2 |
30/09/22 | E | 38.42 |
01/10/22 | A | 101.5 |
01/10/22 | B | ----- |
01/10/22 | C | 237.6 |
01/10/22 | D | 232.2 |
01/10/22 | E | 38.54 |
Desired results:
Date | A | B | C | D | E |
30/09/22 |100.5|151.3|233.4|237.2|38.42|
01/10/22 |101.5|-----|237.6|232.2|38.54|

You can use conditional aggregation method to achieve your desired result -
SELECT Date,
MAX(CASE WHEN Stock = 'A' THEN Price END) AS A,
MAX(CASE WHEN Stock = 'B' THEN Price END) AS B,
MAX(CASE WHEN Stock = 'C' THEN Price END) AS C,
MAX(CASE WHEN Stock = 'D' THEN Price END) AS D,
MAX(CASE WHEN Stock = 'E' THEN Price END) AS E
FROM DATA
GROUP BY Date;
Another Oracle specific answer could be -
SELECT *
FROM DATA
PIVOT(MAX(PRICE) FOR Stock IN ('A' AS A, 'B' AS B, 'C' AS C, 'D' AS D, 'E' AS E));
Demo.

Use PIVOT:
SELECT *
FROM table_name
PIVOT (
MAX(price) FOR stock IN ('A' AS a, 'B' AS b, 'C' AS c, 'D' AS d, 'E' AS e)
)
Which, for your sample data:
CREATE TABLE table_name ( "DATE", Stock, Price ) AS
SELECT DATE '2022-09-30', 'A', 100.5 FROM DUAL UNION ALL
SELECT DATE '2022-09-30', 'B', 151.3 FROM DUAL UNION ALL
SELECT DATE '2022-09-30', 'C', 233.4 FROM DUAL UNION ALL
SELECT DATE '2022-09-30', 'D', 237.2 FROM DUAL UNION ALL
SELECT DATE '2022-09-30', 'E', 38.42 FROM DUAL UNION ALL
SELECT DATE '2022-10-01', 'A', 101.5 FROM DUAL UNION ALL
SELECT DATE '2022-10-01', 'B', NULL FROM DUAL UNION ALL
SELECT DATE '2022-10-01', 'C', 237.6 FROM DUAL UNION ALL
SELECT DATE '2022-10-01', 'D', 232.2 FROM DUAL UNION ALL
SELECT DATE '2022-10-01', 'E', 38.52 FROM DUAL;
Note: DATE is a reserved word and it is bad practice to use it as an identifier. If you must use it as an identifier (don't) then you will need to use a quoted identifier (with the correct case) everywhere it is going to be used.
Outputs:
DATE
A
B
C
D
E
2022-09-30 00:00:00
100.5
151.3
233.4
237.2
38.42
2022-10-01 00:00:00
101.5
null
237.6
232.2
38.52
fiddle

Related

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

Oracle Alpha-Numeric column sorting

I am working on to Sort the Revision column of an Oracle DB table in the asc order as per below.
At first the numeric revisions to be sorted (1,2,3,…).
Thereafter Alpha-Numeric to be sorted as following: A, B, B1, C, C1, C2,…,Y, Y2, Y3, Z, AA, AB,..,DA, …ZZ, etc.
Row_Number() in the SELECT statement to be filled with 1,2,3… for each document# (ABC, XYZ) after revision sorting out.
See the uploaded image for the required table.
I tried with SUBSTR , Order by, etc but failed to sort out as per above requirement.
Can someone help me on this ? Thanks!
As I understand your question, you want to put last revisions that contain only two characters and no digits.
You can use a conditional sort:
select
t.*,
row_number() over(
partition by doc#
order by
case when regexp_like(revision, '^\w\d?$') then 0 else 1 end,
revision
) rn
from t
order by doc#, rn
The regular expression describes a string starting with an alphanumeric character, optionally followed by a digit: these revisions should come first.
Demo on DB Fiddle:
with t as (
select 'ABC' doc#, '1' revision from dual
union all select 'ABC', '2' from dual
union all select 'ABC', '3' from dual
union all select 'ABC', 'A' from dual
union all select 'ABC', 'B' from dual
union all select 'ABC', 'B1' from dual
union all select 'ABC', 'C' from dual
union all select 'ABC', 'C1' from dual
union all select 'ABC', 'D' from dual
union all select 'ABC', 'AA' from dual
union all select 'ABC', 'AB' from dual
union all select 'ABC', 'BA' from dual
union all select 'ABC', 'DA' from dual
)
select
t.*,
row_number() over(
partition by doc#
order by
case when regexp_like(revision, '^\w\d?$') then 0 else 1 end,
revision
) rn
from t
order by doc#, rn
DOC# | REVISION | RN
:--- | :------- | -:
ABC | 1 | 1
ABC | 2 | 2
ABC | 3 | 3
ABC | A | 4
ABC | B | 5
ABC | B1 | 6
ABC | C | 7
ABC | C1 | 8
ABC | D | 9
ABC | AA | 10
ABC | AB | 11
ABC | BA | 12
ABC | DA | 13
There is well known old method: rpad(col, max-length, '0')
For example rpad(col, max(length(col)) over(), '0'

How to use query for Oracle pivot?

I have four columns like below.
Will I be able to get something like this?
Thanks
I would recommend conditional aggregation. It is a database-independent syntax, which is more flexible than Oracle-specific pivot syntax:
select
piece_id,
max(case when attrb_code = 'A' then attrb_a_value end) a,
max(case when attrb_code = 'B' then attrb_a_value end) b,
max(case when attrb_code = 'C' then attrb_a_value end) c,
max(case when attrb_code = 'D' then attrb_b_value end) d
from mytable
group by piece_id
Just use COALESCE (or NVL) in the PIVOT:
SELECT *
FROM table_name
PIVOT (
MAX( COALESCE( attrb_a_value, attrb_b_value ) )
FOR attrb_code IN (
'A' AS A,
'B' AS B,
'C' AS C,
'D' AS D
)
)
So, for your sample data:
CREATE TABLE table_name ( piece_id, attrb_code, attrb_a_value, attrb_b_value ) AS
SELECT 22333, 'A', 8, NULL FROM DUAL UNION ALL
SELECT 22333, 'B', 9, NULL FROM DUAL UNION ALL
SELECT 22333, 'C', 4, NULL FROM DUAL UNION ALL
SELECT 22333, 'D', NULL, 5 FROM DUAL UNION ALL
SELECT 22332, 'A', 2, NULL FROM DUAL UNION ALL
SELECT 22332, 'B', 3, NULL FROM DUAL UNION ALL
SELECT 22332, 'C', 7, NULL FROM DUAL UNION ALL
SELECT 22332, 'D', NULL, 5 FROM DUAL
This outputs:
PIECE_ID | A | B | C | D
-------: | -: | -: | -: | -:
22333 | 8 | 9 | 4 | 5
22332 | 2 | 3 | 7 | 5
db<>fiddle here

T-SQL ORDER BY base on MIN of a group's column

Hi take the following data as an example
id | value
----------
A | 3
A | 9
B | 7
B | 2
C | 4
C | 5
I want to list out all the data base on the min value of each id group, so that the expected output is
id | value
----------
B | 2
B | 7
A | 3
A | 9
C | 4
C | 5
i.e. min of group A is 3, group B is 2, group C is 4, so group B first and then the rest of group B in ascending order. Next group A and then group C
I tried this but thats not what I want
SELECT * FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
GROUP BY id, value
ORDER BY MIN(value)
Please help! Thank you
SELECT * FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
ORDER BY MIN(value) OVER(PARTITION BY id), id, value
OVER Clause (Transact-SQL)
Add the over() clause to your query output and you can see what it does for you.
SELECT *,
MIN(value) OVER(PARTITION BY id) OrderedBy FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
ORDER BY MIN(value) OVER(PARTITION BY id), id, value
Result:
id value OrderedBy
---- ----- ---------
B 2 2
B 7 2
A 3 3
A 9 3
C 4 4
C 5 4

Unexpected result of multiset mapping in Oracle SQL

Please help me to confirm is that behavior explained below is a bug, or clearly explain why it's right.
There are a high probability that I misunderstood some concept, but now for me it looks like a bug.
All examples below simplified as much as possible to demonstrate core of the issue. Real situation is very complex, so only general answers and workarounds related to principle of query construction is acceptable.
You are welcome to ask clarifying questions in comments and i'll try to do my best to answer them.
Thank you for attention. :)
Question
Why in last Example (Example 5) collection instance in (select count(1) ... subquery from first row mapped to all rows of the table, while expected result is to map each collection instance to it's own row?
At the same time collections used in cardinality(...) expression chosen properly.
Same situation (not covered in examples) exists if constructed in this way collections used in from or where part of a query.
Test schema setup
(SQLFiddle)
create or replace type TabType0 as table of varchar2(100)
/
create table Table0( tab_str_field varchar2(100), tab_field TabType0)
nested table tab_field store as tab_field_table
/
insert into table0 (tab_str_field, tab_field) values (
'A',
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'B',
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'C',
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'D',
cast(multiset(
select 'A' from dual
) as TabType0)
)
/
select 'Initial table data' caption from dual
/
select * from table0
/
table data:
| TAB_STR_FIELD | TAB_FIELD |
-----------------------------
| A | A,B,C |
| B | B,C |
| C | A,B,C,D |
| D | A |
Examples
Example 1 (SQLFiddle) - work with nested table fields - OK
select 'Work with nested table - OK' caption from dual
/
select
tab_field tab_field,
-- cardinality
cardinality(tab_field) tab_cardinality,
-- select from table field of current row
(select count(1) from table(tab_field)) tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(tab_field)
where column_value = tab_str_field
) same_value
from table0
/
results:
| TAB_FIELD | TAB_CARDINALITY | TAB_COUNT | SAME_VALUE |
--------------------------------------------------------
| A,B,C | 3 | 3 | A |
| B,C | 2 | 2 | B |
| A,B,C,D | 4 | 4 | C |
| A | 1 | 1 | (null) |
Example 2 (SQLFiddle) - work with constructed source data alone - OK
select 'Work with constructed source data alone - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
tab_field tab_field,
-- cardinality
cardinality(tab_field) tab_cardinality,
-- select from table field of current row
(select count(1) from table(tab_field)) tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(tab_field)
where column_value = tab_str_field
) same_value
from table_data_from_set
/
results:
| TAB_FIELD | TAB_CARDINALITY | TAB_COUNT | SAME_VALUE |
--------------------------------------------------------
| A,B,C | 3 | 3 | A |
| B,C | 2 | 2 | B |
| A,B,C,D | 4 | 4 | C |
| A | 1 | 1 | (null) |
Example 3 (SQLFiddle) - join table with multisets constructed in WITH - OK
select 'Join table with multisets constructed in WITH - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
table0.tab_field table0_tab_field,
table_data_from_set.tab_field set_tab_field,
-- cardinality
cardinality(table0.tab_field) table0_tab_cardinality,
cardinality(table_data_from_set.tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(table_data_from_set.tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(table_data_from_set.tab_field)
where column_value = table0.tab_str_field
) same_value
from
table0,
table_data_from_set
where
table_data_from_set.tab_str_field = table0.tab_str_field
/
results:
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 2 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 4 | C |
| A | A | 1 | 1 | 1 | (null) |
Example 4 (SQLFiddle) - join table with multisets constructed in WITH + subquery - OK
select 'Join table with multisets constructed in WITH and subquery - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
table0_tab_field table0_tab_field,
set_tab_field set_tab_field,
-- cardinality
cardinality(table0_tab_field) table0_tab_cardinality,
cardinality(set_tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(set_tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(set_tab_field)
where column_value = table0_tab_str_field
) same_value
from (
select
table0.tab_str_field table0_tab_str_field,
table0.tab_field table0_tab_field,
table_data_from_set.tab_str_field set_tab_str_field,
table_data_from_set.tab_field set_tab_field
from
table0,
table_data_from_set
where
table_data_from_set.tab_str_field = table0.tab_str_field
)
/
results:
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 2 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 4 | C |
| A | A | 1 | 1 | 1 | (null) |
Example 5 (SQLFiddle) - join table with multisets constructed on the fly - FAILED
select 'Join table with multisets constructed on the fly - FAIL (set_tab_count wrong)' caption from dual
/
with string_set as (
select 'A' str_field from dual union all
select 'B' str_field from dual union all
select 'C' str_field from dual union all
select 'D' str_field from dual union all
select 'E' str_field from dual
)
select
table0_tab_field table0_tab_field,
set_tab_field set_tab_field,
-- cardinality
cardinality(table0_tab_field) table0_tab_cardinality,
cardinality(set_tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(set_tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(set_tab_field)
where column_value = table0_tab_str_field
) same_value
from (
select
table0.tab_str_field table0_tab_str_field,
table0.tab_field table0_tab_field,
(
cast(multiset(
select
string_set.str_field
from
string_set,
table(table0.tab_field) tab_table
where
string_set.str_field = tab_table.column_value
) as TabType0)
) set_tab_field
from
table0
)
/
result (all values in set_tab_count column are same - wrong! ) :
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 3 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 3 | C |
| A | A | 1 | 1 | 3 | (null) |
Oracle version information
Instance 1
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE 11.2.0.3.0 Production
TNS for IBM/AIX RISC System/6000: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
Instance 2
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Express Edition Release 11.2.0.2.0 - Production
PL/SQL Release 11.2.0.2.0 - Production
CORE 11.2.0.2.0 Production
TNS for 32-bit Windows: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
SQLFiddle with all queries together.
It's a bug. Adding a /*+ NO_MERGE */ hint to the second inline view in the last example will generate the expected results. See this SQL Fiddle for an example. Regardless of the query, that hint should never change the results. There are a couple of other seemingly unrelated changes you can make that will generate the correct results, such as removing some of the columns, or adding an unused ROWNUM in the middle.
Oracle is re-writing your query to optimize it, but doing something wrong. You could probably get some more information by tracing the query, but I doubt you'll be able to truly fix the issue. Work around it for now and submit a service request to Oracle so they can create a bug and eventually fix it.