How to make an efficient UNION in Oracle? - performance

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.

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.

SELECT Records > 0 and with NO NULL Values

I have a query in which I am producing results with rows that contain 0 values. I would like to exclude any rows in which columns B or C = 0. To exclude such rows, I have added the T2.A <> 0 and T2.A != 0. When I do this, the 0 values are replaced with NULLs. Thus I also added T2.A IS NOT NULL.
My results still produce the columns that I do not need which show (null) and would like to exclude these.
SELECT
(SELECT
SUM(T2.A) as prem
FROM Table_2 T2, Table_2 T1
WHERE T2.ENT_REF = T1.ENT_REF
AND UPPER(T2.PER) = 'HURR'
AND UPPER(T2.ENT_TYPE) = 'POL'
AND T2.Cov NOT IN ('OUTPROP','COV')
AND T2.A <> 0
AND T2.A IS NOT NULL
) as B,
(SELECT
SUM(T2.A) as prem
FROM Table_2 T2, Table_2 T1
WHERE T2.ENT_REFE = T1.ENT_REF
AND UPPER(T2.PER) IN ('I', 'II', 'II')
AND UPPER(T2.ENT_TYPE) = 'POL'
AND T2.Cov NOT IN ('OUTPROP','COV')
AND T2.A <> 0
AND T2.A IS NOT NULL
) as C
Ideally the result will go from:
+----+--------+--------+
| ID | B | C |
+----+--------+--------+
| 1 | 24 | 123 |
| 2 | 65 | 78 |
| 3 | 43 | 89 |
| 3 | 0 | 0 |
| 4 | 95 | 86 |
| 5 | 43 | 65 |
| 5 | (null) | (null) |
+----+--------+--------+
To something similar to the following:
+----+-----+-----+
| ID | B | C |
+----+-----+-----+
| 1 | 24 | 123 |
| 2 | 65 | 78 |
| 3 | 43 | 89 |
| 4 | 95 | 86 |
| 5 | 43 | 65 |
+----+-----+-----+
I have also attempted distinct values, but I have other columns such as dates which are different per row. Although I need to include dates, they are not as important to me as only getting B and C columns with only values > 0. I have also tried using a GROUP BY ID statement, but I get an error that states 'ORA-00979: not a GROUP BY expression'
You have written all the conditions in the SELECT clause.
You are facing the issue because the WHERE clause decides the number of rows to be fetched and SELECT clause decides values to be returned.
In your case, something like the following is happening:
Simple Example:
-- MANUAL DATA
WITH DATAA AS (
SELECT
1 KEY,
'VALS' VALUE,
1 SEQNUM
FROM
DUAL
UNION ALL
SELECT
2,
'IDEAL OPTION',
2
FROM
DUAL
UNION ALL
SELECT
10,
'EXCLUDE',
3
FROM
DUAL
)
-- QUERY OF YOUR TYPE
SELECT
(
SELECT
KEY
FROM
DATAA I
WHERE
I.KEY = 1
AND O.KEY = I.KEY
) AS KEY, -- DECIDE VALUES TO BE SHOWN
(
SELECT
KEY
FROM
DATAA I
WHERE
I.SEQNUM = 1
AND O.SEQNUM = I.SEQNUM
) AS SEQNUM -- DECIDE VALUES TO BE SHOWN
FROM
DATAA O
WHERE
O.KEY <= 2; -- DECIDES THE NUMBER OF RECORDS
OUTPUT:
If you don't want to change much logic in your query then just use additional WHERE clause outside your final query like:
SELECT <bla bla bla>
FROM <YOUR FINAL QUERY>
WHERE B IS NOT NULL AND C IS NOT NULL
Cheers!!
I guess you were on the right track, trying to group values.
In order to do that, columns (that are supposed to be distinct) will be left alone (such as ID in the following example), while the rest should be aggregated (using min, max or any other you find appropriate).
For example, as you said that there's some date column you don't care about - I mean, which one of them you'll select - then select the first one (i.e. min(date_column)). Similarly, you'd do with the rest. The group by clause should contain all non-aggregated columns (id in this example).
select id,
sum(a) a,
sum(b) b,
min(date_column) date_column
from your_current_query
group by id
If I understand your query right, it would be much easier and more performant, to avoid the lookups in the Select clause. Try to bring it all in one Query:
SELECT * FROM (
SELECT T2.ENT_REF AS ID,
SUM(CASE WHEN UPPER(T2.PER) = 'HURR' THEN T2.A END) AS B,
SUM(CASE WHEN UPPER(T2.PER) IN ('I', 'II', 'II') THEN T2.A END) as C
FROM Table_2 T2
WHERE UPPER(T2.ENT_TYPE) = 'POL'
AND T2.Cov NOT IN ('OUTPROP','COV')
GROUP BY T2.ENT_REF
)
WHERE B IS NOT NULL
OR C IS NOT NULL

Outer join query FROM_TABLE Flag required

I want one flag in output of full outer join in oracle saying from_table which show this tows is fron which table in outer join.
For ex
A full outer join will give you the union of A and B, i.e. all the rows in A and all the rows in B. If something in A doesn't have a corresponding datum in B, then the B portion is null, and vice versa.
select * from a FULL OUTER JOIN b on a.a = b.b;
a | b
-----+-----
1 | null
2 | null
3 | 3
4 | 4
null | 6
null | 5
I need below output:
a | b | from_table
-----+-----
1 | null | A
2 | null | A
3 | 3 | both
4 | 4 | both
null | 6 | B
null | 5 | B
Kindly suggest the query
select a.*, b.*,
case when a.a is not null and b.b is not null then 'both'
when a.a is not null then 'a'
else 'b'
end as from_table
from a
FULL OUTER JOIN b on a.a = b.b;
SQLFiddle Demo
It's eligible to use nvl and nvl2 functions consecutively :
select a, b, decode(sign(nvl2(x,1,0)*nvl2(y,1,0)),1,'both',nvl(x,y)) "from_table"
from
(
select a.*, b.*, nvl2(a,'A',null) x, nvl2(b,'B',null) y
from a FULL OUTER JOIN b on a.a = b.b order by a,b
);
demo

Oracle SQL hierarchical query with two tables

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;

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)

Resources