Oracle SQL hierarchical query with two tables - oracle

I have two tables with the following structure
Table 1:
PRODID | PSTID |
___________________
1 | 4
2 |
3 | 2
4 |
5 |
Table 2:
PSTID | PRODID
_______________
1 | 4
2 | 1
3 | 1
5 |
Now I can say that I start with PRODID 1 (could be any)
It should get joined to table 2 with T1.PRODID = T2.PRODID and result in PSTID -> {2, 3}
Now the corresponding T2.PSTID's are used to start the next round in table 1 with T2.PSTID = T1.PSTID => {3}
Since now there is no more entry for PRODID = 3 in table 2 it should stop (but could go on)

SELECT t1.*
FROM table1 t1
INNER JOIN
table2 t2
ON ( t1.prodid = t2.prodid )
START WITH t1.prodid = 1
CONNECT BY PRIOR t2.pstid = t1.pstid;

Related

Pull conditional statement results based on multiple table joins

I have 3 tables to join to get the output in the below format.
My table 1 is like:
--------------------------------------------------------
T1_ID1 | T1_ID2 | NAME
--------------------------------------------------------
123 | T11231 | TestName11
123 | T11232 | TestName12
234 | T1234 | TestName13
345 | T1345 | TestName14
--------------------------------------------------------
My table 2 is like:
--------------------------------------------------------
T2_ID1 | T2_ID2 | NAME
--------------------------------------------------------
T11231 | T21231 | TestName21
T11232 | T21232 | TestName21
T1234 | T2234 | TestName22
--------------------------------------------------------
My table 3 is like:
----------------------------------------------------------
T3_ID1 | TYPE | REF
----------------------------------------------------------
T21231 | 1 | 123456
T21232 | 2 | 1234#test.com
T2234 | 2 | 123#test.com
----------------------------------------------------------
My desired output is:
------------------------------------------------------
T1_ID1 | PHONE | EMAIL
------------------------------------------------------
123 | 123456 | 1234#test.com
234 | | 123#test.com
345 | |
------------------------------------------------------
Requirements:
T1_ID2 of table 1 left joins with T2_ID1 of table 2.
T2_ID2 of table 2 left joins with T3_ID1 of table 3.
TYPE of table 3 specifies 1 if the value is phone and specified 2 if value is email.
My output should contain T1_ID1 of table 1 and its corresponding value of REF in table 3, with the REF in the same row.
That is, in this case, T1_ID1 with value 123 has both phone and email. So, it is displayed in the same row in output.
If phone alone is available for corresponding value of T1_ID1, then phone should be populated in the result with email as null and vice versa.
If neither phone nor email is available, nothing should be populated.
I had tried the below SQLs but in vain. Where am I missing? Please extend your help.
Option 1:
SELECT DISTINCT
t1.t1_id1,
t3.ref
|| (
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
t3.ref
|| (
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
Option 2:
SELECT DISTINCT
t1.t1_id1,
t3.ref,
(
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
t3.ref,
(
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
Option 3:
SELECT DISTINCT
t1.t1_id1,
(
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
(
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
select t1_id1, max(t3.ref )phone, max(t33.ref) email
from table1
left outer join
table2 on t1_id2=t2_id1
left outer join table3 t3 on t3.t3_id1=t2_id2 and t3.type=1
left outer join table3 t33 on t33.t3_id1=t2_id2 and t33.type=2
group by t1_id1
if you have maximum one phone and one email in table3 for each t2_id2 entry in table2.

use LAG with expression in oracle

I have a column (status) in a table that contain numbers and values are 1, 2 or 4.
I would like, in a SQL query, add a calculated column (bitStatus) that will store the bitwise oerator OR for the status column of the current line and the column in the previous line.
like so :
| id | status| bitStatus|
|----|-------|----------|
| 1 | 1 | 1 |
| 2 | 2 | 3 |
| 3 | 4 | 7 |
| 4 | 1 | 7 |
So what I did is to use LAG function in oracle but I coudn't figure out how to do it as long as I want to create only on calculated column bitStatus
my query is like :
select id, status,
BITOR(LAG(bitStatus) OVER (ORDER BY 1), status)) AS bitStatus
But as you know, I can't use LAG(bitStatus) when calculating bitStatus.
So how could I make it the desired table.
Thanks in advance.
Would this help?
lines #1 - 6 represent sample data
the TEMP CTE is here to fetch LAG status value (to improve readability)
the final select does the BITOR operation as bitor(a, b) = a - bitand(a, b) + b
SQL> with test (id, status) as
2 (select 1, 1 from dual union all
3 select 2, 2 from dual union all
4 select 3, 1 from dual union all
5 select 4, 4 from dual
6 ),
7 temp as
8 (select id, status,
9 lag(status) over (order by id) lag_status
10 from test
11 )
12 select id,
13 status,
14 status - bitand(status, nvl(lag_status, status)) + nvl(lag_status, status) as bitstatus
15 from temp
16 order by id;
ID STATUS BITSTATUS
---------- ---------- ----------
1 1 1
2 2 3
3 1 3
4 4 5
SQL>

Oracle "partition" a table at each new value

I have an Oracle table I need to "partition" :I use the terme loosely, I just need to detect groups and would like to display the group through a SELECT. Here's an example that might serve as a sample data (the four columns):
ID | Ref | Rank | Partition_group (only available for the 1st member)
1 | 1 | 1 | 1_A
2 | 1 | 2 | (null)
3 | 1 | 3 | 1_B
4 | 2 | 1 | (null)
5 | 2 | 2 | 2_A
...
It is sorted (the sort key would be the 'Ref' and a creation date). What I would need here, is to extract three groups:
IDs 1 and 2
ID 3
ID 5
What happens with ID 4 is not really important: it may be in its own group, or with the ID 5.
Two IDS should be in the same group if they have the same 'Ref' and if there hasn't been any 'Partition_group' change. In other words, at each change of 'Ref' or (logical or) 'Partition_group', I need to detect a new group. For instance, we could return something like that:
ID | Ref | Rank | Partition_group | Group
1 | 1 | 1 | 1_A | 1_A
2 | 1 | 2 | (null) | 1_A
3 | 1 | 3 | 1_B | 1_B
4 | 2 | 1 | (null) | (null) (or 2_A)
5 | 2 | 2 | 2_A | 2_A
...
I thought about writing a function or something, but it appears I don't have the rights to do so (yeah...) so I have to use plain Oracle SQL (11g).
I've been looking at CONNECT BY and OVER (analytical functions) but they don't seem to do the trick.
Has anyone been faced to such a problem? How would you resolve it?
Thanks in advance.
Assuming the input data is the first four columns, how about something like:
with sample_data as (select 1 id, 1 ref, 1 rank, '1_A' ptn_group from dual union all
select 2 id, 1 ref, 2 rank, null ptn_group from dual union all
select 3 id, 1 ref, 3 rank, '1_B' ptn_group from dual union all
select 4 id, 2 ref, 1 rank, null ptn_group from dual union all
select 5 id, 2 ref, 2 rank, '2_A' ptn_group from dual)
select id,
ref,
rank,
ptn_group,
last_value(ptn_group ignore nulls) over (partition by ref order by rank, id) grp1,
case when last_value(ptn_group ignore nulls) over (partition by ref order by rank, id) is null then
first_value(ptn_group ignore nulls) over (partition by ref order by rank, id rows between current row and unbounded following)
else last_value(ptn_group ignore nulls) over (partition by ref order by rank, id)
end grp2
from sample_data;
ID REF RANK PTN_GROUP GRP1 GRP2
---------- ---------- ---------- --------- ---- ----
1 1 1 1_A 1_A 1_A
2 1 2 1_A 1_A
3 1 3 1_B 1_B 1_B
4 2 1 2_A
5 2 2 2_A 2_A 2_A
I've given you two options to generate the grp, based on how you want to deal with rows where the first rows of the ptn_group are null - leave them null or pick up the first non-null value in the group.

Oracle compare two count() different tables

I have a problem with a query, see I have two tables, let say:
table a:
progid | name | type
12 | john | b
12 | anna | c
13 | sara | b
13 | ben | c
14 | alan | b
15 | george| b
table b:
progid | name | type
12 | john | b
12 | anna | c
13 | sara | b
14 | alan | b
15 | george| b
table a gets count
progid | count(*)
12 | 2
13 | 2
14 | 1
15 | 1
table b gets
progid | count(*)
12 | 2
**13 | 1**<-this is what I want to find different count
14 | 1
15 | 1
What I want is to find which progid in table b aren't in table a by count, (because as you can see the prog id is there but they should be there the same times! So ben is gone but the progid 13 is there)
So I want to get progid where count varies in the tables, I tried:
select a.progid from
(select progid ,count(*) total from tablea group by progid) a,
(select progid ,count(*) total from tableb group by progid) b
where
a.progid=b.progid and a.total<>b.total;
I get b.total invalid identifier
if I use a.count(progid)<>b.count(progid)
Error says can't use group functions there, any ideas? I'm desperate!
ok i've checked your answers and here's the original one
select a.beneficiarioid from
(select beneficiarioid,count(*) total from lmml_ejercicio_2012_3 where programaid=61 group by beneficiarioid order by beneficiarioid) a,
(select beneficiarioid,count(*) total from ejercicio_2012_3 where programaid=61 group by beneficiarioid order by beneficiarioid) where
a.beneficiarioid=b.beneficiarioid and a.total<>b.total;
anyway, i'll try your querys and let you know!! thank you very much!!
btw it's Oracle 11g
You should be able to use a subquery to get each count and then join them using a FULL OUTER JOIN:
select coalesce(a.progId, b.progId) progid,
coalesce(a.atotal, 0) atotal,
coalesce(b.btotal, 0) btotal
from
(
select progid, count(*) aTotal
from tablea
group by progId
) a
full outer join
(
select progid, count(*) bTotal
from tableb
group by progId
) b
on a.progid = b.progid
where coalesce(a.atotal, 0) <> coalesce(b.btotal, 0);
See SQL Fiddle with Demo. I used a FULL OUTER JOIN in the event you have rows in one table that do not exist in the other table.
Even though your query works fine on my database, I would prefer set operation:
(select progid ,count(*) total from tablea group by progid)
minus
(select progid ,count(*) total from tableb group by progid)

How to make an efficient UNION in Oracle?

I'm using Oracle 11g.
I have 2 related tables: stored values (A) and new values to insert (B). Both are related between them with an id of 3 columns (client, group and personcode). Each table has about 20 other columns (let's call them attributes).
I have to match them so I can know which values are new (id in B and not in A) so I insert them in A, which are equals (id in B and in A with the same attributes) and which are not in the new values (id in A but not in B anymore), so I delete them from the stored values (A).
For instance:
A:
client | group | personcode | name | surname
_________________________________________________
1 | 1 | 1 | joe | doe
1 | 1 | 2 | carl | john
1 | 1 | 3 | john | john
B:
client | group | personcode | name | surname
_________________________________________________
1 | 1 | 1 | joe | doe
1 | 1 | 3 | john | john
1 | 1 | 4 | mary | con
In this example, person 4 is new, person 2 should be deleted and 1 and 3 remains the same.
So, I need a query which returns the following results:
client | group | personcode | action
_________________________________________
1 | 1 | 1 | equal
1 | 1 | 2 | remove
1 | 1 | 3 | equal
1 | 1 | 4 | new
What I've made is the following query:
WITH
A AS (
-- select from A table
),
B AS
(
-- select from B table
),
delete AS
(
-- select from A WHERE NOT EXISTS (B.id = A.ID)
),
news AS
(
-- select from B WHERE NOT EXISTS (A.id = B.ID)
),
eq AS
(
-- select A.* from A, B WHERE A.id = B.id AND A.attributes = B.attributes
)
select action.client, action.group, action.personcode, 'remove' from delete action
UNION ALL
select action.client, action.group, action.personcode, 'new' from news action
UNION ALL
select action.client, action.group, action.personcode, 'equal' from eq action
;
The problem is that, although each of those 3 lasts selects runs in less than 10 seconds, when I merge them using UNION or UNION ALL, the complete query lasts about 90 seconds, even if delete or new or equal are empty. It could be more than 3000 rows in A or in B.
Is there any way to get this results in a better, faster way?
You could outer join the tables to produce a log of the differences between them.
select coalesce(a.id,b.id) id,
case when a.id is null
then 'new'
when b.id is null
then 'remove'
when a.col1 = b.col1 and a.col2 = b.col2 ...
then 'same'
else 'different'
end
from a full outer join b on (a.id = b.id)
If the table B has the data that you want, why do you not use that table instead of that in table A? Create a synonym that points to the one with the correct data in it and reference that.
Well, thanks all for your reply.
I've finally made a view to which I pass some parameters to filter the first two queries, using the strategy described in this blog
The complete process lasts 30 secs now, and 0 if there are no rows at A or B (before, it lasts 90 secs always).
This is the solution which less affects my current procedures.

Resources