LEFT JOIN slowing the query - oracle

SELECT COALESCE(clnt.ename,clientid) NAME, sum(turnover)
FROM A
LEFT JOIN (SELECT DISTINCT csid,code,CP.ename
FROM Senna
LEFT JOIN (SELECT DISTINCT csid,ename FROM C1) CP ON Senna.csid=CP.csid) clnt ON clientid = clnt.CODE
GROUP BY COALESCE(clnt.ename,clientid)
The above query is taking a lot of time to run and the reason for that is that the Senna and the CP tables are reference tables. I am trying to work a way to the improve the performance so that select clause for code field in the senna table is only picked for the clientid which are contained in the table A. The left join between Senna and the CP table is the reason for the issue of slowness but if somehow I can get the Senna table to fetch records based on only the clientid from A that will improve the performance massively.
Is there a way that the clientids can be included in the where clause of the senna table so that when the join is being done with the cp table, the final join of clientid = clnt.CODE is done on a smaller data set. Essentially, there are a number of records in the senna table which can be filtered out in order to improve performance. The ultimate goal is to pull the ename.
Bear in mind there would be 100s of clientid in table A as well.
Please any help would be appreciated.

I don't know that I understand what you need as an output. Please see whether the below Oracle query helps. If not, please explain your use-case more (say you want to list all the records from table A that matches records with table C1 etc or something similar to it for better understanding)
SELECT
COALESCE(clnt.ename, clientid) NAME,
sum(turnover)
FROM
A
LEFT JOIN (
SELECT
DISTINCT senna.csid,
senna.code,
CP.ename
FROM
Senna senna
join C1 CP on senna.csid = CP.csid
) clnt ON clientid = clnt.CODE
GROUP BY
COALESCE(clnt.ename, clientid);

Related

I'd like to return a single row in a query that joins two tables through a one to many relationship

This is an oracle system. I have a client table (one) and an account table (many). Frankly, I really just want to query the client table but due to poor design, there is a data element that I need for the client table that is only present on the account table - it will be the same value for all accounts for that client so I really just need to return one account row but I'm having problems accomplishing this. Here is the query I tried:
Select
c.client_num,
c.client_name,
a.agency_value
from client c
inner join account a on c.client_num = a.client_num
where a.account_num in (select a2.account_num from account a2 where rownum = 1)
You need to explicitly designate how to pick one record out of many. Here's one method - use MAX
Select
c.client_num,
c.client_name,
(SELECT
MAX(a.agency_value) agency_value
FROM account a
where c.client_num = a.client_num
-- not sure if this line is required - if not please remove
and a.account_num in (select a2.account_num from account a2 where rownum = 1)
) agency_value
from client c
Keep in mind that by implementing this you are "cementing" your bad table design.
Are you absolutely certain that there is only ever one agency_value? use query to find any clients that have more than one agency:
SELECT
a.client_num,
COUNT(DISTINCT a.agency_value) CountOfAgencyValues,
MAX(a.agency_value) max_agency_value,
MIN(a.agency_value) max_agency_value
FROM account a
GROUP BY a.client_num
HAVING COUNT(DISTINCT a.agency_value) > 1
With your input and doing some playing around on my own, here is the code that ultimately addressed my need:
select
c. client_num,
(select a.agency_value
from account a
where a.client_num = c.client_num
and rownum = 1)
from client c

Oracle Performance issues on using subquery in an "In" orperator

I have two query that looks close to the same but Oracle have very different performance.
Query A
Create Table T1 as Select * from FinalView1 where CustomerID in ('A0000001','A000002')
Query B
Create Table T1 as Select * from FinalView1 where CustomerID in (select distinct CustomerID from CriteriaTable)
The CriteriaTable have 800 rows but all belongs to Customer ID 'A0000001' and 'A000002'.
This means the subquery: "select distinct CustomerID from CriteriaTable" also only returns the same two elements('A0000001','A000002') as manually entered in query A
Following is the query under the FinalView1
create or replace view FinalView1_20200716 as
select
Customer_ID,
<Some columns>
from
Table1_20200716 T1
INNER join Table2_20200716 T2 on
T1.Invoice_number = T2.Invoice_number
and
T1.line_id = T2.line_id
left join Table3_20200716 T3 on
T3.id = T1.Customer_ID
left join Table4_20200716 T4 on
T4.Shipping_ID = T1.Shipping_ID
left join Table5_20200716 Table5 on
Table5.Invoice_ID = T1.Invoice_ID
left join Table6_20200716 T6 on
T6.Shipping_ID = T4.Shipping_ID
left join First_Order first on
first.Shipping_ID = T1.Shipping_ID
;
Table1_20200716,Table2_20200716,Table3_20200716,Table4_20200716,Table5_20200716,Table6_20200716 are views to the corresponding table with temporal validity feature. For example
The query under Table1_20200716
Create or replace view Table1_20200716 as
select
*
from Table1 as for period of to_date('20200716,'yyyymmdd')
However table "First_Order" is just a normal table as
Following is the performance for both queries (According to explain plan):
Query A:
Cardinality: 102
Cost : 204
Total Runtime: 5 secs max
Query B:
Cardinality:27921981
Cost: 14846
Total Runtime:20 mins until user cancelled
All tables are indexed using those columns that used to join against other tables in the FinalView1. According to the explain plan, they have all been used except for the FirstOrder table.
Query A used uniquue index on the FirstOrder Table while Query B performed a full scan.
For query B, I was expecting the Oracle will firstly query the sub-query get the result into the in operator, before executing the main query and therefore should only have minor impact to the performance.
Thanks in advance!
As mentioned from my comment 2 days ago. Someone have actually posted the solution and then have it removed while the answer actually work. After waiting for 2 days the So I designed to post that solution.
That solution suggested that the performance was slow down by the "in" operator. and suggested me to replace it with an inner join
Create Table T1 as
Select
FV.*
from
FinalView1 FV
inner join (
select distinct
CustomerID
from
CriteriaTable
) CT on CT.customerid = FV.customerID;
Result from explain plan was worse then before:
Cardinality:28364465 (from 27921981)
Cost: 15060 (from 14846)
However, it only takes 17 secs. Which is very good!

Hash Join with Partition restriction from third table

my current problem is in 11g, but I am also interested in how this might be solved smarter in later versions.
I want to join two tables. Table A has 10 million rows, Table B is huge and has a billion of records across about a thousand partitions. One partition has around 10 million records. I am not joining on the partition key. For most rows of Table A, one or more rows in Table B will be found.
Example:
select * from table_a a
inner join table_b b on a.ref = b.ref
The above will return about 50 million rows, whereas the results come from about 30 partitions of table b. I am assuming a hash join is the correct join here, hashing table a and FTSing/index-scanning table b.
So, 970 partitions were scanned for no reason. And, I have a third query that could tell oracle which 30 partitions to check for the join.
Example of third query:
select partition_id from table_c
This query gives exactly the 30 partitions for the query above.
To my question:
In PL/SQL one can solve this by
select the 30 partition_ids into a variable (be it just a select listagg(partition_id,',') ... into v_partitions from table_c
Execute my query like so:
execute immediate 'select * from table_a a
inner join table_b b on a.ref = b.ref
where b.partition_id in ('||v_partitions||')' into ...
Let's say this completes in 10 minutes.
Now, how can I do this in the same amount of time with pure SQL?
Just simply writing
select * from table_a a
inner join table_b b on a.ref = b.ref
where b.partition_id in (select partition_id from table_c)
does not do the trick it seems, or I might be aiming at the wrong plan.
The plan I think I want is
hash join
table a
nested loop
table c
partition pruning here
table b
But, this does not come back in 10 minutes.
So, how to do this in SQL and what execution plan to aim at? One variation I have not tried yet that might be the solution is
nested loop
table c
hash join
table a
partition pruning here (pushed predicate from the join to c)
table b
Another feeling I have is that the solution might lie in joining table a to table c (not sure on what though) and then joining this result to table b.
I am not asking you to type everything out for me. Just a general concept of how to do this (getting partition restriction from a query) in SQL - what plan should I aim at?
thank you very much! Peter
I'm not an expert at this, but I think Oracle generally does the joins first, then applies the where conditions. So you might get the plan you want by moving the partition pruning up into a join condition:
select * from table_a a
inner join table_b b on a.ref = b.ref
and b.partition_id in (select partition_id from table_c);
I've also seen people try to do this sort of thing with an inline view:
select * from table_a a
inner join (select * from table_b
where partition_id in (select partition_id from table_c)) b
on a.ref = b.ref;
thank you all for your discussions with me on this one. In my case this was solved (not by me) by adding a join-path between table_c and table_a and by overloading the join conditions as below. In my case this was possible by adding column partition_id to table_a:
select * from
table_c c
JOIN table_a a ON (a.partition_id = c.partition_id)
JOIN table_b b ON (b.partition_id = c.partition_id and b.partition_id = a.partition_id and b.ref = a.ref)
And this is the plan you want:
leading(c,b,a) use_nl(c,b) swap_join_inputs(a) use_hash(a)
So you get:
hash join
table a
nested loop
table c
partition list iterator
table b

JOIN 4 tables in one (Oracle R11)

I need to create a query that shows the "Legal Entity", "Application Name", "Close Date" and "Period" I'm working with Oracle R11, Right now I've found the query for
"Legal Entity"
SELECT name
FROM hr_organization_information HOI
INNER JOIN hr_all_organization_units HAOU
ON HOI.ORGANIZATION_ID = Haou.Organization_Id
WHERE HOI.org_information_context LIKE 'Legal Entity Accounting'
ORDER BY NAME ASC;
and for "Application Name, Close Date, Period"
SELECT A.APPLICATION_ID,
B.APPLICATION_NAME,
TO_CHAR(A.END_DATE,'HH24:MI DD-MON-YYYYI'),
A.PERIOD_NUM
FROM GL_PERIOD_STATUSES A
INNER JOIN FND_APPLICATION_TL B ON A.APPLICATION_ID = B.APPLICATION_ID
WHERE A.Application_Id=101
AND LANGUAGE='US'
OR A.APPLICATION_ID=200
AND LANGUAGE='US'
OR A.APPLICATION_ID=222
AND LANGUAGE='US';
Separately but I haven't found the way to join them in one query, can you help me with that?
Antonio, I think Brian has given you sound advice. Posting to an EBS forum (or whatever application this is) might also be worthwhile if his advice has not lead you to the answer. I will offer that sometimes the way to join table_A and table_B is through table_C. That is, if you do not find any directly related data in the queries of one se to one of the tables in the other set then look at the FK defined on and pointing to these tables to see if you can find a table not currently part of either query that relates the sets. You figure out how to join each of your current queries to it and that is how you join the two queries together.
Thank you all!
The advices that all of you gave to me were useful, I've found the table HR_LEGAL_ENTITIES (Table C) that have two columns that allow me to join Table A with Table B, the final query was:
SELECT HAOU.NAME,
FAT.APPLICATION_NAME,
TO_CHAR(GPS.END_DATE,'HH24:MI DD-MON-YYYY'),
GPS.PERIOD_NUM
FROM HR_ALL_ORGANIZATION_UNITS HAOU
INNER JOIN HR_LEGAL_ENTITIES HLE
ON HLE.ORGANIZATION_ID = HAOU.ORGANIZATION_ID
INNER JOIN GL_PERIOD_STATUSES GPS
ON HLE.SET_OF_BOOKS_ID = GPS.SET_OF_BOOKS_ID
INNER JOIN FND_APPLICATION_TL FAT
ON GPS.APPLICATION_ID = FAT.APPLICATION_ID
WHERE GPS.Application_Id IN (101,200,222) AND LANGUAGE='US'
ORDER BY NAME ASC;
Regards!

How to simplify this postgres query

How can I simplify this query?
What I am trying to do is derive the column S9_Unlock via a subquery in which I only look for user_ids which are returned from the main query but this looks very awkward to me, especially as this query here is just an excerpt. In reality I am doing multiple of these subqueries to derive different columns...
SELECT userid, CAST(to_char(S9_unlock,'YYYY/MM/DD') AS timestamp) AS "S9_Unlock"
FROM (
SELECT ca.user_id AS userid
FROM shop_db.invoices AS inv
LEFT JOIN shop_db.carts AS ca ON inv.id = ca.invoice_id
LEFT JOIN shop_db.cart_items AS ci ON ca.id = ci.cart_id
WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
AND inv.status <> 'do_not_book'
AND inv.id IS NOT NULL
GROUP BY user_id) AS master
LEFT JOIN (
SELECT MIN(s3.unl) AS "S9_Unlock", s3.user_id
FROM (
SELECT user_id, challenge_codes.created AS unl,
MAX /* Check if license contains Suite9 */
(CASE WHEN substring(bundle_article_code,1,6) = 'BuSu90' THEN 1 ELSE 0 END) AS "S9_Unlock"
FROM licensing_db.serial_numbers
LEFT JOIN licensing_db.licenses ON licenses.id = serial_numbers.license_id
LEFT JOIN user_db.users ON users.id = licenses.user_id
LEFT JOIN licensing_db.challenge_codes ON challenge_codes.serial_number_id = serial_numbers.id
WHERE user_id IN (
SELECT ca.user_id AS userid
FROM shop_db.invoices AS inv
LEFT JOIN shop_db.carts AS ca ON inv.id = ca.invoice_id
LEFT JOIN shop_db.cart_items AS ci ON ca.id = ci.cart_id
WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
AND inv.status <> 'do_not_book'
AND inv.id IS NOT NULL
GROUP BY user_id
)
GROUP BY user_id, challenge_codes.created) AS s3
)
WHERE "S9_Unlock" = 1
AND s3.unl IS NOT NULL
GROUP BY s3.user_id) AS "S9_Unlock" ON "S9_Unlock".user_id = master.userid
In your query you have two sub-queries that are identical; this screams for a CTE.
In the sub-query on licensing issues you can filter out the valid licenses after the GROUP BY clause using a HAVING clause. Make that a WITH QUERY too and you end up with the rather more readable:
WITH inv AS (
SELECT ca.user_id AS userid
FROM shop_db.invoices AS inv
LEFT JOIN shop_db.carts AS ca ON ca.invoice_id = inv.id
LEFT JOIN shop_db.cart_items AS ci ON ci.cart_id = ca.id
WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
AND inv.status <> 'do_not_book'
AND inv.id IS NOT NULL
), s3 AS (
SELECT u.user_id, min(cc.created) AS first_unlocked, bundle_article_code
FROM licensing_db.serial_numbers AS sn
LEFT JOIN licensing_db.licenses AS lic ON lic.id = sn.license_id
LEFT JOIN user_db.users AS u ON u.id = lic.user_id
LEFT JOIN licensing_db.challenge_codes AS cc ON cc.serial_number_id = sn.id
WHERE u.user_id IN (SELECT userid FROM inv)
GROUP BY u.user_id, bundle_article_code
HAVING bundle_article_code LIKE 'BuSu90%'
AND first_unlocked IS NOT NULL
)
SELECT userid, date_trunc('day', first_unlocked) AS "S9_Unlock"
FROM inv
LEFT JOIN s3 ON s3.user_id = inv.userid;
So the main query is now reduced to 3 lines and both the WITH-QUERY's perform a logically self-contained query of the database. The other sub-queries you refer to can similarly become a WITH-QUERY and then you assemble them in the main query. Remember that you can refer to earlier named queries in the list of with-queries, as is shown above with inv being referred to by s3. While such CTE's are syntactically not providing new functionality (except for the RECURSIVE variant), they do make complex queries much more readable and therefore easier to maintain.
Another approach would be to factor out logical sub-components (such as the inv sub-query) and make a VIEW out of those. Then you can simply reference the view in the main query. Making the whole thing a view is probably also a good idea if you want to make the query more flexible. What if you want to query for Suite9.1 ('BuSu91%') on 27 March 2014? Taken those literals out and then using them as WHERE clauses in a view makes your query more versatile; this can be either with sub-queries or with the complete CTE.
(Please check if the semantics are still right in the s3 with-query because without your table structures and sample data I ccannot test my code above.)
Instead of solving your problem as one big monolithic relational sql query, I would seriously consider going the "procedural" way, by using the built-in "plpgsql" language of postgresql. This could bring a lot of clarity in your application.

Resources