Get multiple data into one colomn Oracle Query - oracle

I have 3 tables: table_A, table_B and table_C. Table_A has a Primary key and is referred by a foreign key from table_B. Table_C has a primary key referred by a foreign key from table_B. The design is like this:
Table_A:
ID_A
TextData
Table_B:
ID_B
ID_A
ID_C
Table C:
ID_C
TextData
I want to join between 3 tables like this:
select A.ID_A, A.TextData as DataA,
(
select C.TextData
from Table_B B, Table_C C
where B.ID_C = C.ID_C and B.ID_A = C.ID_A
) as Data_C
from Table_A;
I know that it should be an error if I try to compile it with error like: return more than one elements.
But my client want me to join all data from table C into one row, all i know using concate to every data. But i don't know how to do it. I never try to create function or package on oracle. Can you help me how to fix my query?
The result should be like:
ID_A | DataA | Data_C
1 texta text1, text2, text8
2 textb text2, text3, text9
3 textc text1, text8, text9

You can try with this:
SETUP
create table table_A(ID_A number, TextData varchar2(100))
/
create table Table_B( ID_B number, ID_A number, ID_C number)
/
create table Table_C(ID_C number, TextData varchar2(100))
/
insert into table_A values (1, 'texta');
insert into table_A values (2, 'textb');
insert into table_A values (3, 'textc');
--
insert into table_C values (1, 'text1');
insert into table_C values (2, 'text2');
insert into table_C values (3, 'text3');
insert into table_C values (8, 'text8');
insert into table_C values (9, 'text9');
--
insert into table_b values (11, 1, 1);
insert into table_b values (12, 1, 2);
insert into table_b values (18, 1, 8);
insert into table_b values (22, 2, 2);
insert into table_b values (23, 2, 3);
insert into table_b values (29, 2, 9);
insert into table_b values (31, 3, 1);
insert into table_b values (38, 3, 8);
insert into table_b values (39, 3, 8);
QUERY:
select id_a, a.textData as DataA, listagg(c.textData, ', ') within group (order by c.textData) as Data_c
from table_A a
inner join table_B b
using(id_A)
inner join table_c c
using(id_c)
group by id_a, a.textData

XMLAGG or similar is what you need. Something like (not tested, but should give you a hint):
select A.ID_A, A.TextData as DataA,
(
select XMLELEMENT("Thedata",XMLAGG("textdata",C.TextData)) as td
from Table_B B, Table_C C
where B.ID_C = C.ID_C and B.ID_A = C.ID_A
) as Data_C
from Table_A;
Use REPLACE/TRANSLATE/REGEXP_REPLACE etc to strip out the XML tags if required.
Loads of example about e.g. http://www.dba-oracle.com/t_converting_rows_columns.htm .

May be you should write your aggregate. Funcrion to concat your strings user defined aggregates
Or use undocumented wm_concat. Thus function deprecated in 12c.

If you're on Oracle 11g or higher, you can use LISTAGG:
with table_a as (select 1 id_a, 'texta' textdata from dual union all
select 2 id_a, 'textb' textdata from dual union all
select 3 id_a, 'textc' textdata from dual),
table_b as (select 1 id_b, 1 id_a, 1 id_c from dual union all
select 2 id_b, 1 id_a, 2 id_c from dual union all
select 3 id_b, 1 id_a, 4 id_c from dual union all
select 4 id_b, 2 id_a, 2 id_c from dual union all
select 5 id_b, 2 id_a, 3 id_c from dual union all
select 6 id_b, 2 id_a, 5 id_c from dual union all
select 7 id_b, 3 id_a, 1 id_c from dual union all
select 8 id_b, 3 id_a, 4 id_c from dual union all
select 9 id_b, 3 id_a, 5 id_c from dual),
table_c as (select 1 id_c, 'text1' textdata from dual union all
select 2 id_c, 'text2' textdata from dual union all
select 3 id_c, 'text3' textdata from dual union all
select 4 id_c, 'text8' textdata from dual union all
select 5 id_c, 'text9' textdata from dual)
-- end of mimicking your tables; see sql below.
select a.id_a,
a.textdata,
listagg(c.textdata, ', ') within group (order by c.id_c) data_c
from table_a a
inner join table_b b on (a.id_a = b.id_a)
inner join table_c c on (b.id_c = c.id_c)
group by a.id_a, a.textdata;
ID_A TEXTDATA DATA_C
---------- -------- --------------------
1 texta text1, text2, text8
2 textb text2, text3, text9
3 textc text1, text8, text9

Related

Oracle How to make SELECT INSIDE A SELECT work?

Just wondering why the following select isn't working:
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
ERROR at line 2: ORA-00904: "A"."CUST": invalid identifier
Important to mention:
TABLE1 has as fields FIELD1, CUST.
TABLE2 has as fields PCN, PRIORITY, CUST.
Thanks in advance.
Your query shouldn't give you that error message, on when you remove the outer qiery this would happen
CREATE tABLE TABLE1 (FIELD1 int, CUST int)
INSERT INTO TABLE1 VALUES(1,1)
1 rows affected
CREATE TABLE TABLE2 (PCN int, PRIORITY int, CUST int)
INSERT INTO TABLE2 VALUES (1,1,1)
1 rows affected
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
FIELD1
PCN
1
1
fiddle
You can't nest inline selects (more than one level) without losing the ability of the inner nested selects being able to reference the parent block. So your query on TABLE2 cannot see the columns from TABLE1 because of this nesting.
Try this:
SELECT a.field1,
pcn.pcn
FROM table1 a,
(SELECT b.cust,
b.priority,
b.pcn,
ROW_NUMBER() OVER (PARTITION BY b.cust ORDER BY b.priority DESC) seq
FROM table2 b) pcn
WHERE a.cust = pcn.cust(+)
AND pcn.seq(+) = 1
That will work well for report queries. If you end up adding a filter on a specific customer, then you would be better off using OUTER APPLY if you have a recent-enough version of Oracle that supports that.
You could try this:
SELECT
A.FIELD1
, (SELECT B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY
FETCH FIRST 1 ROWS ONLY) AS PCN
FROM TABLE1 A;
FETCH FIRST 1 ROWS ONLY gets you the first ordered record. Works on 12c and up and supports nesting, and no 2nd subquery needed.
Yet another option might be a CTE.
Sample data:
SQL> with
2 table1 (field1, cust) as
3 (select 1, 100 from dual union all
4 select 2, 200 from dual
5 ),
6 table2 (pcn, priority, cust) as
7 (select 10, 1, 100 from dual union all
8 select 20, 2, 100 from dual union all
9 select 30, 1, 200 from dual
10 ),
Query begins here. Rank rows by priority, and then fetch the ones that rank as the highest (line #20):
11 temp as
12 (select a.field1,
13 b.pcn,
14 rank() over (partition by a.field1 order by b.priority desc) rnk
15 from table1 a join table2 b on a.cust = b.cust
16 )
17 select field1,
18 pcn
19 from temp
20 where rnk = 1;
FIELD1 PCN
---------- ----------
1 20
2 30
SQL>
You may use first aggregate function to achieve the same (assuming that you have completely deterministic order by) functionality without nested subquery:
select
a.field1
, (
select max(b.pcn) keep(dense_rank first order by b.priority)
from table2 b
where b.cust = a.cust
) as pcn
from table1 a
which for this sample data
insert into table1 values(1,1);
insert into table1 values(2,2);
insert into table2 values(1,1,1);
insert into table2 values(2,2,1)
returns
FIELD1
PCN
1
1
2
(null)
SQL fiddle

SQL | SPLIT COLUMNS INTO ROWS

How can I split the column data into rows with basic SQL.
COL1 COL2
1 A-B
2 C-D
3 AAA-BB
Result
COL1 Col2
1 A
1 B
2 C
2 D
3 AAA
3 BB
From Oracle 12, if it is always two delimited values then you can use:
SELECT t.col1,
l.col2
FROM table_name t
CROSS JOIN LATERAL (
SELECT SUBSTR(col2, 1, INSTR(col2, '-') - 1) AS col2 FROM DUAL
UNION ALL
SELECT SUBSTR(col2, INSTR(col2, '-') + 1) FROM DUAL
) l
Which, for the sample data:
CREATE TABLE table_name (COL1, COL2) AS
SELECT 1, 'A-B' FROM DUAL UNION ALL
SELECT 2, 'C-D' FROM DUAL UNION ALL
SELECT 3, 'AAA-BB' FROM DUAL;
Outputs:
COL1
COL2
1
A
1
B
2
C
2
D
3
AAA
3
BB
db<>fiddle here
Snowflake is tagged, so here's the snowflake way of doing this:
WITH TEST (col1, col2) as
(select 1, 'A-B' from dual union all
select 2, 'C-D' from dual union all
select 3, 'AAA-BB' from dual
)
SELECT test.col1, table1.value
FROM test, LATERAL strtok_split_to_table(test.col2, '-') as table1
ORDER BY test.col1, table1.value;
As of Oracle:
SQL> with test (col1, col2) as
2 (select 1, 'A-B' from dual union all
3 select 2, 'C-D' from dual union all
4 select 3, 'AAA-BB' from dual
5 )
6 select col1,
7 regexp_substr(col2, '[^-]+', 1, column_value) col2
8 from test cross join
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(col2, '-') + 1
11 ) as sys.odcinumberlist))
12 order by col1, col2;
COL1 COL2
---------- ------------------------
1 A
1 B
2 C
2 D
3 AAA
3 BB
6 rows selected.
SQL>
For MS-SQL 2016 and higher you can use:
SELECT Col1, x.value
FROM t CROSS APPLY STRING_SPLIT(t.Col2, '-') as x;
BTW: If Col2 contains null, it does not appear in the result.

Match characters in oracle sql where condition

I want to match Col1 from Table a to colum1 from table B.
A B
123 123-ab
234 234-bc
3443 3443-dd
However, value in table b has concatenated data. I want to match only the characters until before special character occurs(-).
I tried this : substr(table1.a,1,3) = substr(table2.b,1,3)
But this doesn’t work as some values have 4 digits.
use join and substr
select * from table_a
inner join table_b on table_a.col_a = substr(table_b.col_b, 1, length(table_a.col_a);
Using REGEXP_SUBSTR() to match on one or more numbers from the beginning of the string up to but not including the first hyphen:
SQL> with a(col1) as (
select '123' from dual union
select '234' from dual union
select '3443' from dual
),
b(col1) as (
select '123-ab' from dual union
select '234-bc' from dual union
select '3443-dd' from dual
)
select a.col1, b.col1
from a, b
where a.col1 = regexp_substr(b.col1, '^(\d+)-', 1, 1, NULL, 1);
COL1 COL1
---- -------
123 123-ab
234 234-bc
3443 3443-dd
SQL>

Oracle Hierarchical Queries

I am struggling with a query with the below requirement:
Table A
ID Name Key
1 A1 Key1
2 A2 Key2
3 A3 Key3
Table B
ID A_ID NAME CONTAINER_A_ID
1 1 B1 NULL
2 1 B2 NULL
3 1 B3 2
4 2 B4 NULL
5 2 B5 NULL
6 3 B6 NULL
7 3 B7 NULL
The Key column in table A is unique
The A_ID column in table B is a foreign key of table A
The CONTAINER_A_ID column in table B means the row in table B can be a
container, it contains other data rows indicated by the CONTAINER_A_ID value.
Below is the example:
the input parameter is table A key column value, let's say A.Key = 'key1', and the result based on the above sample data will be:
A.ID A.NAME A.KEY B.ID B.A_ID B.NAME B.CONTAINER_A_ID
1 A1 KEY1 1 1 B1 NULL
1 A1 KEY1 2 1 B2 NULL
1 A1 KEY1 3 1 B3 2
2 A2 KEY2 4 2 B4 NULL
2 A2 KEY2 5 2 B5 NULL
if the input parameter is A.Key = 'key2', then the result will be:
A.ID A.NAME A.KEY B.ID B.A_ID B.NAME B.CONTAINER_A_ID
2 A2 KEY2 4 2 B4 NULL
2 A2 KEY2 5 2 B5 NULL
Thanks
This is on Oracle 11g.
If you are specifically looking for CONNECT BY I am not aware of that yet.
drop table t1; drop table t2;
create table t1 (id int primary key, name char(5), key char(5));
create table t2 (id int primary key, a_id int, name char(5) , container int);
insert into t1 values (1, 'A1', 'K1');
insert into t1 values (2, 'A2', 'K2');
insert into t1 values (3, 'A3', 'K3');
insert into t2 values (1, 1, 'B1', null);
insert into t2 values (2, 1, 'B2', null);
insert into t2 values (3, 1, 'B3', 2);
insert into t2 values (4, 2, 'B4', null);
insert into t2 values (5, 2, 'B5', null);
insert into t2 values (6, 3, 'B6', null);
insert into t2 values (7, 3, 'B7', null);
with t(id, name, key, bid, aid, bname, con) as (
select a.id, a.name, a.key, b.id, b.a_id, b.name, b.container
from t1 a
inner join
t2 b
on a.id = b.a_id
where a.key = 'K1'
union all
select a.id, a.name, a.key, b.id, b.a_id, b.name, b.container
from t t
inner join
t1 a
on a.id = t.con
inner join
t2 b
on a.id = b.a_id
) select * from t;
EDIT: Reponse to Jorge's comment
insert into t2 values (4, 2, 'B4', 3);
This is for Hierarchical Query
with TableA as
(
select 1 id, 'A1' Name, 'Key1' key from dual union all
select 2, 'A2', 'Key2' from dual union all
select 3, 'A3', 'Key3' from dual
)
, tableb as
(
select 1 id, 1 a_id, 'B1' name , null CONTAINER_A_ID from dual union all
select 2 , 1 , 'B2' , null from dual union all
select 3 , 1 , 'B3' , 2 from dual union all
select 4 , 2 , 'B4' , null from dual union all
select 5 , 2 , 'B5' , null from dual union all
select 6 , 3 , 'B6' , null from dual union all
select 7 , 3 , 'B7' , null from dual
)
select
a.id, a.name, a.key, b.id, b.a_id, b.name, b.container_a_id
from
tableb b
left join
tablea a
on
a.id = b.a_id
start with
A.Key = 'Key1'
connect by
prior b.container_a_id = b.a_id;
If you need order then add order by a.id, b.id,a.name,...; to the end.
CTEs are fine to use in 11g Oracle. I just saw Jorge is in before me. It is easier to see how the recursion works if you just use tableB in the CTE and then join to the CTE to get all the fields, like this
with
recurse (a_id, b_id, parent_id)
as
(select a_id, id, container_a_id as parent_id
from tableB
WHERE A_ID = 1 -- Put your parameter here
union all
select b.a_id, b.id, b.container_a_id
from recurse r, tableB b
where b.a_id = r.parent_id
)
select r.a_id, a.name, a.key, b.id, b.a_id, b.name, b.container_a_id
from recurse r, tableA a, tableB b
where r.a_id = a.id and r.b_id = b.id
;
This gets the same results, but although you have to use a_id and not a_key for the condition, it is a little bit easier to understand the recursion.
So, leaving this here in case it helps someone to understand a bit about CTEs.

column A has 3 same values and need to check column B values for the same column A

For example, I have column A Department and column B as employee type, I need to check the following.
Department Emp Type
Dep1 S
Dep1 H
Dep1 P
Dep2 H
Dep2 H
Dep2 H
I need to retrieve the departments only with H employee type for all the 3 rows. If the emp types are different I need to ignore that department.
you can use a group by with having to filter out the ones with only H:
SQL> create table dept(dep varchar2(5), typ varchar2(1));
Table created.
SQL> insert into dept
2 select 'Dep1', 'S' from dual union all
3 select 'Dep1', 'H' from dual union all
4 select 'Dep1', 'P' from dual union all
5 select 'Dep2', 'H' from dual union all
6 select 'Dep2', 'H' from dual union all
7 select 'Dep2', 'H' from dual;
6 rows created.
SQL> select dep
2 from dept
3 group by dep
4 having count(distinct typ) = 1
5 and max(typ) = 'H';
DEP
-----
Dep2
SELECT DISTINCT Department
FROM tableX t
WHERE NOT EXISTS
( SELECT *
FROM tableX s
WHERE s.Department = t.Department
AND s.EmpType <> 'H'
) ;
If you have an index on (Department, EmpType), the fastest way is probably:
SELECT Department
FROM tableX t
GROUP BY Department
HAVING MIN(EmpType) = 'H'
AND MAX(EmpType) = 'H' ;

Resources