How can I select a particular row from a nested table? [duplicate] - sql

This question already has an answer here:
Oracle SQL: select from table with nested table
(1 answer)
Closed 10 years ago.
I have a table that looks like this:
CREATE
OR
REPLACE
TYPE subaccount_nt
IS TABLE OF VARCHAR2(30);
CREATE
TABLE my_table
( contract_id NUMBER(38,0)
, subaccount SUBACCOUNT_NT );
Here's some sample data:
100 [1000, 1, 2]
200 [2000, NULL, 999]
300 [3000]
How can I write a query to return the the third row from the nested table if the 3rd row exists? Here's the output I'm trying to get:
100 1
200 NULL
300 NULL
Haeving never worked with nested tables before, I'm finding it quite hard to forumate my query. Thanks.

You can use analytics with a lateral join (unnesting of collection):
SQL> SELECT contract_id, CASE WHEN rn = 2 THEN val END val
2 FROM (SELECT t.contract_id, column_value val,
3 row_number() over(PARTITION BY t.contract_id ORDER BY 1) rn,
4 COUNT(*) over (PARTITION BY t.contract_id) cnt
5 FROM my_table t,
6 TABLE(t.subaccount))
7 WHERE rn = 2 OR cnt = 1;
CONTRACT_ID VAL
-------------- ---
100 1
200
300
This will not list rows that have an empty subaccount.
By the way the order is not guaranteed since the nested tables are stored as unordered sets of rows.

Related

nested select or join query?

i am new to sql so this may have a very basic answer but the question to answer is as follows .....
which film has taken the least takings at a performance? include film name and cinema name in the result.?
film name and cinema name are in two different table film and cinema. takings are in the performance table. i cant figure out how to complete this query. this is what i have got so far but it comes with an error in line 3 column 7.
select cinema_no,film_no
from CINEMA, film
where takings ( select min(takings)
from performance);
Because of multiple tags with oracle, I ignored the tag mysql ( of one which you should get rid of. e.g. please decide which DBMS are you using, by the way I already removed the irrelevant one oracle-sqldeveloper ).
It seems you need such a select statement ( prefer using modern ANSI-92 JOIN syntax, easily maintained and understandable ) with ordering by descending sum and contribution of row_number function as :
SELECT Name, Sum_Takings
FROM
(
SELECT f.Name, sum(p.Takings) Sum_Takings,
row_number() over (ORDER BY sum(p.Takings)) as rn
FROM Film f
LEFT JOIN Cinema c ON f.Cinema_ID = c.ID
LEFT JOIN Performance p ON f.ID = p.id_film
GROUP BY f.Name
)
WHERE rn = 1;
with added DDL statement as in the following :
SQL> CREATE TABLE Cinema (
2 ID integer PRIMARY KEY NOT NULL,
3 Title varchar2(100) NOT NULL
4 );
Table created
SQL> CREATE TABLE Film (
2 ID integer PRIMARY KEY NOT NULL,
3 Name varchar2(100) NOT NULL,
4 Cinema_ID integer
5 CONSTRAINT fk_Cinema_ID REFERENCES Cinema(ID)
6 );
Table created
SQL> CREATE TABLE Performance (
2 ID integer PRIMARY KEY NOT NULL,
3 ID_Film integer
4 CONSTRAINT fk_Film_ID REFERENCES Film(ID),
5 Takings integer
6 );
Table created
SQL> INSERT ALL
2 INTO Cinema(ID,Title) VALUES(1,'NiteHawk')
3 INTO Cinema(ID,Title) VALUES(2,'Symphony Space')
4 INTO Cinema(ID,Title) VALUES(3,'The Ziegfeld')
5 INTO Cinema(ID,Title) VALUES(4,'Cinema Village')
6 SELECT * FROM dual;
4 rows inserted
SQL> INSERT ALL
2 INTO Film(ID,Name,Cinema_ID) VALUES(1,'Citizen Kane',1)
3 INTO Film(ID,Name,Cinema_ID) VALUES(2,'Titanic',2)
4 INTO Film(ID,Name,Cinema_ID) VALUES(3,'Brave Heart',4)
5 INTO Film(ID,Name,Cinema_ID) VALUES(4,'Dumb and Dummer',3)
6 INTO Film(ID,Name,Cinema_ID) VALUES(5,'How To Train Your Dragon',2)
7 INTO Film(ID,Name,Cinema_ID) VALUES(6,'Beetle Juice',3)
8 SELECT * FROM dual;
6 rows inserted
SQL> INSERT ALL
2 INTO Performance VALUES(1,1,15)
3 INTO Performance VALUES(2,1,4)
4 INTO Performance VALUES(3,2,10)
5 INTO Performance VALUES(4,3,1)
6 INTO Performance VALUES(5,4,5)
7 INTO Performance VALUES(6,3,3)
8 INTO Performance VALUES(7,2,7)
9 INTO Performance VALUES(8,5,7)
10 INTO Performance VALUES(9,6,6)
11 SELECT * FROM dual;
9 rows inserted
SQL> commit;
Commit complete
SQL> SELECT Name, Sum_Takings
2 FROM
3 (
4 SELECT f.Name, sum(p.Takings) Sum_Takings,
5 row_number() over (ORDER BY sum(p.Takings)) as rn
6 FROM Film f
7 LEFT JOIN Cinema c ON f.Cinema_ID = c.ID
8 LEFT JOIN Performance p ON f.ID = p.id_film
9 GROUP BY f.Name
10 )
11 WHERE rn = 1
12 ;
NAME SUM_TAKINGS
--------------------------------------------------------------------- -----------
Brave Heart 4
dbfiddle.uk demo
As it involves grouping of Performance table you can achieve it by using two queries one to get the cinemaID and filmID of minimum performance takings and the another one to get the cinema name and filmname using the collected ids
SELECT CinemaID,FilmID from Performance where
PerformanceTaking=(Select Min(PerformanceTaking) from Performance);
SELECT CinemaName,FilmName from Cinema,Film where CinemaID=1 and
FilmID=1;
this will work:
select * from (select c.cinema_name,f.film_name
from cinema c, film f,performance p,
rank() over (partition by c.cinema order by sum(p.takings)) as rank
where
c.ID=f.cinema_id and
f,id=p.id
group by f.film_name) where rank=1
;

Accessing 2th element in varray column

Let's say a have a table with a varray column, defined as follow:
create or replace TYPE VARRAY_NUMBER_LIST AS VARRAY(15) OF NUMBER;
Now, I'm trying to select the first element of each varray column of my table. It works fine:
select (select * from table(myvarraycolumn) where rownum = 1) from mytable cc
It is returning an output like:
2
1
4
4
2
2
My issue occurs when I try to get the second element of each varray column with this SQL:
select (select * from table(myvarraycolumn) where rownum = 2) from mytable cc
In this case, all output lines are returning null. Please, let me know if I'm forgetting something or making some confusion.
You need to select rows 1 and 2 and then work out a way to filter out the unwanted preceding rows - one way is to use aggregation with a CASE statement to only match the second row:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE mytable ( myvarraycolumn ) AS
SELECT SYS.ODCINUMBERLIST( 1, 2, 3 ) FROM DUAL UNION ALL
SELECT SYS.ODCINUMBERLIST( 4, 5, 6 ) FROM DUAL;
Query 1:
SELECT (
SELECT MAX( CASE ROWNUM WHEN 2 THEN COLUMN_VALUE END )
FROM TABLE( t.myvarraycolumn )
WHERE ROWNUM <= 2
) AS second_element
FROM mytable t
Results:
| SECOND_ELEMENT |
|----------------|
| 2 |
| 5 |
My issue occurs when I try to get the second element of each varray column with this SQL:
select (select * from table(myvarraycolumn) where rownum = 2) from mytable cc
In this case, all output lines are returning null. Please, let me know if I'm forgetting something or making some confusion.
It is not working because: for the first row in the correlated inner query, ROWNUM is 1 and your filter is WHERE ROWNUM = 2 then this reduces to WHERE 1=2 and the filter is not matched and the row is discarded. The subsequent row will then be tested against a ROWNUM of 1 (since the previous row is no longer in the output and will not have a row number), which will again fail the test and be discarded. Repeat, ad nauseum and all rows fail the WHERE filter and are discarded.

Pivot in SQL: count not working as expected

I have in my Oracle Responsys Database a table that contains records with amongst other two variables:
status
location_id
I want to count the number of records grouped by status and location_id, and display it as a pivot table.
This seems to be the exact example that appears here
But when I use the following request :
select * from
(select status,location_id from $a$ )
pivot (count(status)
for location_id in (0,1,2,3,4)
) order by status
The values that appear in the pivot table are just the column names :
output :
status 0 1 2 3 4
-1 0 1 2 3 4
1 0 1 2 3 4
2 0 1 2 3 4
3 0 1 2 3 4
4 0 1 2 3 4
5 0 1 2 3 4
I also gave a try to the following :
select * from
(select status,location_id , count(*) as nbreports
from $a$ group by status,location_id )
pivot (sum(nbreports)
for location in (0,1,2,3,4)
) order by status
but it gives me the same result.
select status,location_id , count(*) as nbreports
from $a$
group by status,location_id
will of course give me the values I want, but displaying them as a column and not as a pivot table
How can I get the pivot table to have in each cell the number of records with the status and location in row and column?
Example data:
CUSTOMER,STATUS,LOCATION_ID
1,-1,1
2,1,1
3,2,1
4,3,0
5,4,2
6,5,3
7,3,4
The table data types :
CUSTOMER Text Field (to 25 chars)
STATUS Text Field (to 25 chars)
LOCATION_ID Number Field
Please check if my understanding for your requirement is correct, you can do vice versa for the location column
create table test(
status varchar2(2),
location number
);
insert into test values('A',1);
insert into test values('A',2);
insert into test values('A',1);
insert into test values('B',1);
insert into test values('B',2);
select * from test;
select status,location,count(*)
from test
group by status,location;
select * from (
select status,location
from test
) pivot(count(*) for (status) in ('A' as STATUS_A,'B' as STATUS_B))

How to add sequence number for groups in a SQL query without temp tables

I've created a complex search query in SQL 2008 that returns data sorted by groups, and the query itself has paging and sorting functions in it, but rather than returning a set number of records based on the paging options, it needs to return a set number of groups (so the number of records will vary).
I'm currently doing this through the use of Temp Tables (the first temp table creates a list of the Groups that will be selected as part of the search, and then numbers them... and the second query joins this table to the actual search... so, it ends up running the search query twice).
What I'm looking for is a more efficient way to do this using some of the new functions in SQL 2008 (which wouldn't require the use of temp tables).
If I can get the data in a format like this, I'd be set...
Record Group GroupSequence
-------|---------|--------------
1 Chickens 1
2 Chickens 1
3 Cows 2
4 Horses 3
5 Horses 3
6 Horses 3
Any ideas on how to accomplish this with a single query in SQL 2008, without using temp tables?
Sample data
create table sometable([group] varchar(10), id int, somedata int)
insert sometable select 'Horses', 9, 11
insert sometable select 'chickens', 19, 121
insert sometable select 'Horses', 29, 123
insert sometable select 'chickens', 49, 124
insert sometable select 'Cows', 98, 1
insert sometable select 'Horses', 99, 2
Query
select
Record = ROW_NUMBER() over (order by [Group], id),
[Group],
GroupSequence = DENSE_RANK() over (order by [Group])
from sometable
Output
Record Group GroupSequence
-------------------- ---------- --------------------
1 chickens 1
2 chickens 1
3 Cows 2
4 Horses 3
5 Horses 3
6 Horses 3
Without more details about the tables you have, I'd say look into CTE queries and the row_number function... something along the lines of:
;with groups as (
select top 10 name, row_number() over(order by name) 'sequence'
from table1
group by name
order by name
)
select row_number() over(order by g.name) 'Record',
g.name 'GroupName',
g.sequence 'GroupSequence'
from groups

Inline query to update multiple rows

Here is a brief description of the tables I'm working with in Oracle 10g:
Notes:
Table : jnldetail : Single row with data as shown.
There are multiple package id's attached to the same bill_ref_no for an account. Therefore, I'm trying to update "jnldetail " with the multiple package_id's.
Relation between index_bill_ref and bill_ref_no : 1 - 1
Relation between account_no and ( index_bill_ref and bill_ref_no ) : 1 - Many
**Table : jnldetail** :
account_no bill_ref_no amount
8594822 74282843 822
I'm adding another column package_id with the following command:
alter table jnldetail add package_id number(10)
**table: bill_invoice**:
account_no bill_ref_no index_bill_ref
8594822 74282843 763653495
**table: bill_invoice_detail**:
index_bill_ref package_id component_id
763653495 20000077 20000177
763653495 20000250 20000528
763653495 13000019 13000137
**Expected Result:**
**Table : jnldetail** :
account_no bill_ref_no amount package_id
8594822 74282843 822 20000077
8594822 74282843 822 20000250
8594822 74282843 822 13000019
My Query is:
UPDATE jnldetail tp
SET tp.package_id = (
select
t1.package_id
from bill_invoice_detail t1
, bill_invoice t2
where
t1.index_bill_ref = t2.index_bill_ref
and
t2.account_no = tp.account_no
)
The error message is : ora 01427 : single row subquery returns more than one row
Any inputs will be helpful.
Thanks!
The problem is that you're trying to set tp.package_id to more than one number, because your subquery is returning more than one result, e.g. 20000077 and 13000019. You'll need to alter the subquery so that only one value is returned.
Why not keep the tables separate and use a join when you are ready to get the complete data?
This is tricky for two reasons:
1) you want to update the existing row, and want to add two new rows
2) the two new rows need the data from both the original jnldetail table (amount) and the bill_invoice tables (package_id)
To address 1, you can use the MERGE statement, but because of 2, the jnldetail is needed in the using clause of the MERGE statement.
Here is your example:
SQL> create table jnldetail (account_no, bill_ref_no, amount)
2 as
3 select 8594822, 74282843, 822 from dual
4 /
Tabel is aangemaakt.
SQL> alter table jnldetail add package_id number(10)
2 /
Tabel is gewijzigd.
SQL> create table bill_invoice (account_no, bill_ref_no, index_bill_ref)
2 as
3 select 8594822, 74282843, 763653495 from dual
4 /
Tabel is aangemaakt.
SQL> create table bill_invoice_detail (index_bill_ref, package_id, component_id)
2 as
3 select 763653495, 20000077, 20000177 from dual union all
4 select 763653495, 20000250, 20000528 from dual union all
5 select 763653495, 13000019, 13000137 from dual
6 /
Tabel is aangemaakt.
The tables as you described them.
SQL> UPDATE jnldetail tp
2 SET tp.package_id =
3 ( select t1.package_id
4 from bill_invoice_detail t1
5 , bill_invoice t2
6 where t1.index_bill_ref = t2.index_bill_ref
7 and t2.account_no = tp.account_no
8 )
9 /
( select t1.package_id
*
FOUT in regel 3:
.ORA-01427: single-row subquery returns more than one row
Your update statement fails, because you try to assign the result of a 3-rows-returning-query to a single column.
Here is the MERGE statement:
SQL> merge into jnldetail jd
2 using ( select bi.account_no
3 , bi.bill_ref_no
4 , jd.amount
5 , bid.package_id
6 , row_number() over (partition by bi.account_no,bi.bill_ref_no,bi.index_bill_ref order by null) rn
7 from bill_invoice bi
8 , bill_invoice_detail bid
9 , jnldetail jd
10 where bi.index_bill_ref = bid.index_bill_ref
11 and bi.account_no = jd.account_no
12 and bi.bill_ref_no = jd.bill_ref_no
13 ) bi
14 on ( jd.account_no = bi.account_no
15 and jd.bill_ref_no = bi.bill_ref_no
16 and bi.rn = 1
17 )
18 when matched then
19 update set package_id = package_id
20 when not matched then
21 insert values (bi.account_no,bi.bill_ref_no,bi.amount,bi.package_id)
22 /
3 rijen zijn samengevoegd.
Note that we pick an arbitrary row to be updated: the one with rn = 1.
It leads to the desired result set:
SQL> select * from jnldetail
2 /
ACCOUNT_NO BILL_REF_NO AMOUNT PACKAGE_ID
---------- ----------- ---------- ----------
8594822 74282843 822 13000019
8594822 74282843 822 20000077
8594822 74282843 822 20000250
3 rijen zijn geselecteerd.
Regards,
Rob.