I am dealing with tables which (for the purposes of displaying here) look like the following:
A
A_ID | Clob_Col
1 | value
2 | value
3 | null
4 | value
5 | null
6 | value
7 | value
8 | null
9 | value
10 | value
B
B_ID |A_ID | C_ID
10 | 1 | 20
11 | 2 | 20
12 | 6 | 21
13 | 7 | 22
14 | 8 | 22
15 | 9 | 23
C
C_ID
20
21
22
23
24
25
(All tables have more columns)
I wish to write a query which will return values from all three tables but exclude records where certain values match those in Table C (NOT IN).
My problem is to return the values from Table A which do not link to Table B when using a NOT IN clause on Table C.
Example:
SELECT a.A_ID, a.Clob_Col, b.B_ID, c.C_ID
from A a
LEFT JOIN B b on a.A_ID=b.A_ID
LEFT JOIN C c on b.C_ID=c.C_ID
WHERE a.AID >= 2
AND a.AID <= 7
AND c.C_ID NOT IN (22, 23, 24)
The last line - c.C_ID NOT IN (22, 23, 24) - will leave us with the following records in the B table: b_BID = 10,11 or 12
In turn, these link to the following records in Table A: a.ID = 1,2 and 6.
The first clause - a.AID >= 2 AND a.AID <= 7 - further restricts our final result to a.ID = 2 and 6.
The final result will look like:
A_ID |Clob_Col |B_ID |C_ID
2 |value |11 |20
6 |value |12 |21
But I also want to return the A table records which don't link to Table B - a.ID 3, 4 and 5
So I expect my results to be
A_ID |Clob_Col |B_ID |C_ID
2 |value |11 |20
3 |null |null |null
4 |value |null |null
5 |null |null |null
6 |value |12 |21
Note: The reason I included the Clob value is that someone suggested selecting all records and then performing a MINUS operation on the records where c.C_ID IN (22, 23, 24).
This seemed reasonable however Oracle does not allow MINUS operation where Clob columns are involved.
I think you forgot to use "on" clause for join. You can try this :
SELECT a.A_ID, a.Clob_Col, b.B_ID, c.C_ID
from A a
LEFT JOIN B b on a.A_ID=b.A_ID
LEFT JOIN C c on b.C_ID=c.C_ID
WHERE a.A_ID between 2 and 7
AND c.C_ID NOT IN (22, 23, 24)
Hope it will work.
Include what your joining on and also you can use BETWEEN for the first WHERE clause.
Also i would use INNER JOINS rather then LEFT as per your data.
SELECT a.A_ID, a.Clob_Col, b.B_ID, c.C_ID
FROM A a
INNER JOIN B b ON a.A_ID = b.B_ID
INNER JOIN C c ON b.C_ID = c.C_ID
WHERE a.AID BETWEEN 2 AND 7
AND c.C_ID NOT IN (22, 23, 24)
I think this does what you're after:
with a as (select 1 a_id, 'val1' clob_col from dual union all
select 2 a_id, 'val2' clob_col from dual union all
select 3 a_id, null clob_col from dual union all
select 4 a_id, 'val4' clob_col from dual union all
select 5 a_id, null clob_col from dual union all
select 6 a_id, 'val6' clob_col from dual union all
select 7 a_id, 'val7' clob_col from dual union all
select 8 a_id, null clob_col from dual union all
select 9 a_id, 'val9' clob_col from dual union all
select 10 a_id, 'val10' clob_col from dual),
b as (select 10 b_id, 1 a_id, 20 c_id from dual union all
select 11 b_id, 2 a_id, 20 c_id from dual union all
select 12 b_id, 6 a_id, 21 c_id from dual union all
select 13 b_id, 7 a_id, 22 c_id from dual union all
select 14 b_id, 8 a_id, 22 c_id from dual union all
select 15 b_id, 9 a_id, 23 c_id from dual),
c as (select 20 c_id from dual union all
select 21 c_id from dual union all
select 22 c_id from dual union all
select 23 c_id from dual union all
select 24 c_id from dual union all
select 25 c_id from dual)
select a.a_id, a.clob_col, b.b_id, c.c_id
from a
left outer join b on (a.a_id = b.a_id)
left outer join c on (b.c_id = c.c_id)
where a.a_id between 2 and 7
and (c.c_id not in (22, 23, 24) or c.c_id is null)
order by a.a_id;
A_ID CLOB_COL B_ID C_ID
---------- -------- ---------- ----------
2 val2 11 20
3
4 val4
5
6 val6 12 21
and if c_id is 27 for a_id = 6 in the b table:
A_ID CLOB_COL B_ID C_ID
---------- -------- ---------- ----------
2 val2 11 20
3
4 val4
5
6 val6 12
You have to take account of the fact that c_id could be null, as well as not being in the set of values being excluded.
ETA: Thanks to Ponder Stibbons' suggestion in the comments, if you didn't want the row to be displayed where a.a_id = b.a_id matches but there isn't a match on b.c_id = c.c_id, then changing the or c.c_id is null to or b.c_id is null removes that row:
with a as (select 1 a_id, 'val1' clob_col from dual union all
select 2 a_id, 'val2' clob_col from dual union all
select 3 a_id, null clob_col from dual union all
select 4 a_id, 'val4' clob_col from dual union all
select 5 a_id, null clob_col from dual union all
select 6 a_id, 'val6' clob_col from dual union all
select 7 a_id, 'val7' clob_col from dual union all
select 8 a_id, null clob_col from dual union all
select 9 a_id, 'val9' clob_col from dual union all
select 10 a_id, 'val10' clob_col from dual),
b as (select 10 b_id, 1 a_id, 20 c_id from dual union all
select 11 b_id, 2 a_id, 20 c_id from dual union all
select 12 b_id, 6 a_id, 27 c_id from dual union all
select 13 b_id, 7 a_id, 22 c_id from dual union all
select 14 b_id, 8 a_id, 22 c_id from dual union all
select 15 b_id, 9 a_id, 23 c_id from dual),
c as (select 20 c_id from dual union all
select 21 c_id from dual union all
select 22 c_id from dual union all
select 23 c_id from dual union all
select 24 c_id from dual union all
select 25 c_id from dual)
select a.a_id, a.clob_col, b.b_id, c.c_id
from a
left outer join b on (a.a_id = b.a_id)
left outer join c on (b.c_id = c.c_id)
where a.a_id between 2 and 7
and (c.c_id not in (22, 23, 24) or b.c_id is null)
order by a.a_id;
Related
I tried my best to figure and google this out, but couldn't really find a solid answer to it.
The problem I'm facing is that
Table 1:
ID Value 1
1 a
2 b
3 c
Table 2:
ID Value 2
1 4a
3 5b
4 6c
and I'd basically have to select the value from Table 1 that doesn't exist on Table 2 (Thus, 'b')
I can select and identify the ID that I want by using minus function between the tables, but can't seem to figure out a way to call a query to instead call the data.
Use the MINUS as a subquery (i.e. an inline view) (lines #14 - 16):
Sample data:
SQL> with
2 table1(id, value1) as
3 (select 1, 'a' from dual union all
4 select 2, 'b' from dual union all
5 select 3, 'c' from dual
6 ),
7 table2 (id, value2) as
8 (select 1, '4a' from dual union all
9 select 3, '5b' from dual union all
10 select 4, '6c' from dual
11 )
Query begins here:
12 select a.*
13 from table1 a
14 where a.id in (select t1.id from table1 t1
15 minus
16 select t2.id from table2 t2
17 );
ID VALUE1
---------- ----------
2 b
SQL>
Alternatively, use not exists:
<snip>
12 select a.*
13 from table1 a
14 where not exists (select null
15 from table2 b
16 where b.id = a.id
17 );
ID VALUE1
---------- ----------
2 b
SQL>
beacause of a really old db design I need some help. This might be quite simple I'm just not seeing the wood for the trees at the moment.
TABLE A:
ID
1
2
3
4
5
TABLE B:
ID
VALUE B
1
10
1
20
2
10
2
20
3
10
3
20
3
30
4
10
TABLE C:
ID
VALUE C
1
11
1
21
2
11
2
21
2
31
3
11
5
11
Expected result:
where ID = 1
ID
VALUE B
VALUE C
1
10
11
1
20
21
where ID = 2
ID
VALUE B
VALUE C
2
10
11
2
20
21
2
null
31
where ID = 3
ID
VALUE B
VALUE C
3
10
11
3
20
null
3
30
null
where ID = 4
ID
VALUE B
VALUE C
4
10
null
where ID = 5
ID
VALUE B
VALUE C
5
null
11
The entries in table B and C are optional and could be unlimited, the ID from table A is the connection.
B and C are not directly connected. I need a quantitative comparision to find gaps in the database. The number of entries of table B and C should be the same (but not the value), usually entries are missing in either B or C.
I tried it with outer joins but I'm getting two much rows, because I need B or C join only one time per single row.
I hope anybody understand my problem and can help me.
It looks like, for each distinct ID, you want the nth row (ordered by VALUE) from TABLE_A to match with the nth row from TABLE_B. And if one table - A or B - has more values, you want those to match to null.
Your solution will have two parts. First, use row_number() over ( partition by id order by value) to order the rows in both tables. Then, use FULL OUTER JOIN to join on (id, rownumber).
Here is a full example:
-- WITH clauses are just test data...+
with table_a (id) as (
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL ),
table_b (id, value) as (
SELECT 1,10 FROM DUAL UNION ALL
SELECT 1,20 FROM DUAL UNION ALL
SELECT 2,10 FROM DUAL UNION ALL
SELECT 2,20 FROM DUAL UNION ALL
SELECT 3,10 FROM DUAL UNION ALL
SELECT 3,20 FROM DUAL UNION ALL
SELECT 3,30 FROM DUAL UNION ALL
SELECT 4,10 FROM DUAL ),
table_c (id, value) as (
SELECT 1,11 FROM DUAL UNION ALL
SELECT 1,21 FROM DUAL UNION ALL
SELECT 2,11 FROM DUAL UNION ALL
SELECT 2,21 FROM DUAL UNION ALL
SELECT 2,31 FROM DUAL UNION ALL
SELECT 3,11 FROM DUAL UNION ALL
SELECT 5,11 FROM DUAL )
-- Solution begins here
SELECT id, b.value b_value, c.value c_value
FROM ( SELECT b.*,
row_number() OVER ( PARTITION BY b.id ORDER BY b.value ) rn
FROM table_b b ) b
FULL OUTER JOIN ( SELECT c.*,
row_number() OVER ( PARTITION BY c.id ORDER BY c.value ) rn
FROM table_c c ) c USING (id, rn)
ORDER BY id, b_value, c_value;
+----+---------+---------+
| ID | B_VALUE | C_VALUE |
+----+---------+---------+
| 1 | 10 | 11 |
| 1 | 20 | 21 |
| 2 | 10 | 11 |
| 2 | 20 | 21 |
| 2 | | 31 |
| 3 | 10 | 11 |
| 3 | 20 | |
| 3 | 30 | |
| 4 | 10 | |
| 5 | | 11 |
+----+---------+---------+
acid
tran_id
tran_date
tran_particular
tran_amt
part_tran_type
ab500
m1
01-01-2022
123:qwe
10
C
ab500
m5
10-01-2022
124:qse
20
C
ab500
m16
11-01-2022
123:pyh
10
D
I have the above table named htd. I am trying to fetch the result where tran_particular is unique before the ":"
Final output looking for:
acid
tran_id
tran_date
tran_particular
tran_amt
part_tran_type
ab500
m5
10-01-2022
124:qse
20
C
But below query returns no rows:
select tran_id||'|'||tran_date||'|'|| TRAN_PARTICULAR||'|'|| tran_amt
||'|'|| part_tran_type from htd a where a.acid ='ab500' and
Substr(TRAN_PARTICULAR,1,instr(TRAN_PARTICULAR,':')-1) not in (select Substr(TRAN_PARTICULAR,1,instr(TRAN_PARTICULAR,':')-1) from htd b where b.acid ='ab500'
and b.tran_id not in( a.tran_id)) order by tran_date;
koen >CREATE TABLE htd (tran_id, tran_particular)AS
2 (
3 SELECT 'm1', '123:qwe' FROM DUAL UNION ALL
4 SELECT 'm5', '124:qse' FROM DUAL UNION ALL
5 SELECT 'm16', '123:pyh' FROM DUAL
6* );
Table HTD created.
koen >WITH tp_unique_vals (tran_particular, cnt) AS
2 (
3 SELECT SUBSTR(tran_particular,1,INSTR(tran_particular,':')-1), COUNT(*)
4 FROM htd
5 GROUP BY SUBSTR(tran_particular,1,INSTR(tran_particular,':')-1)
6 HAVING COUNT(*) = 1
7 )
8 SELECT h.*
9 FROM htd h
10* JOIN tp_unique_vals u ON SUBSTR(h.tran_particular,1,instr(h.tran_particular,':')-1) = u.tran_particular;
TRAN_ID TRAN_PARTICULAR
__________ __________________
m5 124:qse
koen >
Try to use a regular expression and an aggregation:
with a as (select '123:qwe' b from dual union all
select '124:qse' from dual union all
select '123:pyh' from dual)
select b
from a
where regexp_substr(b, '[^:]+') in ( select c
from (select b, regexp_substr(b, '[^:]+') c
from a)
group by c
having count(*) = 1);
Returns 124:qse.
If I have the following table:
ColumnA ColumnB
A B
C D
D E
B C
How can I write a sql to get the following:
ColumnC ColumnD
A B
A C
A D
A E
B C
B D
B E
C D
C E
D E
The level of nested relationship many be multiple.
Can sql handle this? Or need PLSQL?
Use a hierarchical query with CONNECT_BY_ROOT:
SELECT CONNECT_BY_ROOT( ColumnA ) AS ColumnA,
ColumnB
FROM table_name
CONNECT BY PRIOR ColumnB = ColumnA
For your data:
CREATE TABLE table_name ( ColumnA, ColumnB ) AS
SELECT 'A', 'B' FROM DUAL UNION ALL
SELECT 'C', 'D' FROM DUAL UNION ALL
SELECT 'D', 'E' FROM DUAL UNION ALL
SELECT 'B', 'C' FROM DUAL;
This outputs:
COLUMNA | COLUMNB
:------ | :------
A | B
A | C
A | D
A | E
B | C
B | D
B | E
C | D
C | E
D | E
db<>fiddle here
A recursive CTE might help.
SQL> with
2 test (cola, colb) as
3 -- sample data; you already have that
4 (select 'a', 'b' from dual union all
5 select 'c', 'd' from dual union all
6 select 'd', 'e' from dual union all
7 select 'b', 'c' from dual
8 ),
9 -- recursive CTE
10 temp (parent, child) as
11 (select t.cola parent, t.colb child
12 from test t
13 union all
14 select a.parent, b.colb child
15 from temp a join test b on a.child = b.cola
16 )
17 select *
18 from temp
19 order by parent, child;
PARENT CHILD
---------- ----------
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e
10 rows selected.
SQL>
I have two tables - TABLE_A and TABLE_B.
TABLE_A
AREA_ID LOC_ID OTHER
------ -------- -----
111 1 AA
222 2 BB
333 3 CC
TABLE_B
-------
LOC_ID LOC_NAME
------- ----------
1 USA
2 ITALY
3 SPAIN
Based on the above, I would like to query all the records in TABLE_A and join on TABLE_B using LOC_ID but return USA from TABLE_B for all AREA_IDs in TABLE_A.
Result I am after is:
Result:
AREA_ID NAME
------ --------
111 USA
222 USA
333 USA
That is, I would like to repeat the LOC_NAME in TABLE_B for LOC_ID = 1 (USA) and assign this name to all AREA_ID records in TABLE_A.
This seems to do what you want:
select a.area_id, 'USA' as loc_name
from table_A a;
I can't figure out why a join to table_B is desired.
If you really want the value with "1", then use:
select a.area_id, b.loc_name
from table_A a join
table_B b
on b.loc_id = 1;
You might want a left join . . . but then you would get a NULL value.
Something like this?
If you change condition in line #15, you'll get another LOC_NAME from table_B.
SQL> with table_a (area_id, loc_id, other) as
2 (select 111, 1, 'AA' from dual union all
3 select 222, 2, 'BB' from dual union all
4 select 333, 3, 'CC' from dual
5 ),
6 table_b (loc_id, loc_name) as
7 (select 1, 'USA' from dual union all
8 select 2, 'ITALY' from dual union all
9 select 3, 'SPAIN' from dual
10 )
11 select a.area_id, x.loc_name
12 from table_a a join
13 (select b.loc_name
14 from table_b b
15 where b.loc_id = 1 --> that's what you said
16 ) x
17 on 1 = 1;
AREA_ID LOC_N
---------- -----
111 USA
222 USA
333 USA
SQL>
Use cross join as
select a.area_id, b.loc_name as name
from table_a a
cross join table_b b
where b.loc_id = 1
or inner join as
select a.area_id, b.loc_name as name
from table_a a
join table_b b
on b.loc_id = 1
Demo