Skip to Main Content
  • Questions
  • Equivalent for EXISTS() in an IF statement?

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Dennis.

Asked: March 15, 2002 - 7:27 am UTC

Last updated: December 05, 2019 - 9:13 am UTC

Version: 8.1.7

Viewed 100K+ times! This question is

You Asked

In the dialect for procedural SQL in MSSQL and Sybase, there's
a useful little idiom for checking whether rows exist on a table,
and it looks like this...

if exists (select 'x' from foo where bar)
/* found, do something */
else
/* not found, do something else */


In PL/SQL, since I can't put an EXISTS() in an IF statenment,
the nearest I can manage is something like this...

declare
c integer;
begin
select count(*) into c from foo where bar;

if c > 0 then
/* found, do something */
else
/* not found, do something else */
end if;
end;

...which isn't terribly elegant, and probably not fast. Is there
a neater way of coding this? (Prefarably without the need to
fetch into an intermediate variable?)

regds,
Dennis

and Tom said...


for x in ( select count(*) cnt
from dual
where exists ( select NULL from foo where bar ) )
loop
if ( x.cnt = 1 )
then
found do something
else
not found
end if;
end loop;



is one way (very fast, only runs the subquery as long as it "needs" to, where exists stops the subquery after hitting the first row)

That loop always executes at least once and at most once since a count(*) on a table without a group by clause ALWAYS returns at LEAST one row and at MOST one row (even of the table itself is empty!)



Rating

  (19 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks

Dennis Cook, March 15, 2002 - 9:55 am UTC

Looks very efficient. Shame it involves so many lines
of code, though, as I would have thought it quite a
common test.

Thanks for your help.

Tom Kyte
March 15, 2002 - 2:19 pm UTC

Well, actually, in many many many years of coding I think I could count on one hand how many times I've used this construct.

I don't see the usefulness of this particular construct. The reason -- I don't see the point in branching based on the existence of a row in a table WITHOUT ACTUALLY GETTING the row itself. I mean, don't I need the values in that row? The existence of the row doesn't tell me very much. In my code, I have lots of:


begin
select * into l_Rec from t where....;
process....
exception
when no_data_found then
processs.....
end;

which is the same as your


begin
if exists ( select ... )
then
process...
else
process ...
end if;
end;

but I have access to the row itself.


RE: in many many many years...

Mark A. Williams, March 15, 2002 - 2:37 pm UTC

Aw, c'mon Tom, I've got your book - your picture is on it. It can't be *that* many years!

- Mark

Alternate way

Jagdish Malani, April 15, 2002 - 5:46 pm UTC

I am not sure if we really need to check the count in the for loop.
I think we can do as follows:

for x in ( select * from dual
where exists
(select * from foo where bar)
)
loop
.... processing
end loop;

The body of the loop will execute once if the subquery is satisfied, zero times otherwise. This way we do not need to check x.cnt 's value for 1 or 0.


Please correct me if I am wrong.

Tom Kyte
April 16, 2002 - 7:43 am UTC

If you want to do an:

if ( exists ..... )
end if;

your approach works ( I use that myself )

If you want to do:

if ( exists .... )
....
ELSE
.....
end if

you need to look at count

Well, well , well

Famy, April 16, 2002 - 12:08 pm UTC

Tom


According to you a 'where exists'

"only runs the subquery as long as it "needs" to, where
exists stops the subquery after hitting the first row)'

But look below, if what you have said is right then only records of only one department should be returned.

The below query tells us that the WHERE EXISTS is returning all the departments.

Kindly clarify.


Wrote file afiedt.buf

1 select ename, deptno from emp
2 where exists( select null from dept
3* where emp.deptno=dept.deptno)
/

ENAME DEPTNO
---------- ---------
SMITH 20
ALLEN 30
WARD 30
JONES 20
MARTIN 30
BLAKE 30
CLARK 10
SCOTT 20
KING 10
TURNER 30
ADAMS 20
JAMES 30
FORD 20
MILLER 10

14 rows selected.


Tom Kyte
April 16, 2002 - 12:31 pm UTC

It runs the subquery for each row in the OUTER query.

It runs the subquery only as much as it needs to.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y char(30) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select 1, 'x' from all_objects;

22843 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 2, 'x' from dual;

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from dual
  3   where exists ( select null from t where x = 1 );


Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
          2  consistent gets
         63  physical reads
          0  redo size
        363  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from dual
  3   where exists ( select null from t where x = 2 );


Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
        125  consistent gets
         60  physical reads
          0  redo size
        363  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off


Notice the consistent gets -- since "1" is near the "front" of T, the subquery stops right away.  I put "2" at the "end" of T, the subquery read the entire table in that case.

If dual had N rows -- it would run the subquery N times with N different values if I used a correlated subquery...



 

re:re:in many many years

Jim, April 16, 2002 - 7:49 pm UTC

Mark,
While I agree with you on your evaluation of Tom's picture on the cover of his book - his youthful appearance - you have to consider things in "Tom Years". For us mortals Tom accomplishes a multiple of what we accomplish. Thus something like 20 years to us Tom accomplishes in just 5.

Hence, the in many many years refers to our time.
:-)

A reader, April 22, 2003 - 10:54 am UTC

Well, here is one occasion where this costruct might be useful. I just realized that I cannot use exists in PL/SQL when I hit a PLS-204 on this block (for validating FK relationship before making an insert).

FOR i IN (SELECT * FROM staging_postal_address)
LOOP
---Many other validation blocks here...
---
---
/************************************
*FK validation.
*Check if pos_add_address_id has parent in address table.
************************************/
IF NOT EXISTS ( SELECT NULL
FROM address
WHERE add_address_id = i.pos_add_address_id)
THEN
v_error_code := 'POS002';
v_error_pk_value := i.pos_address_id;
RAISE e_err;
END IF;

I had to define a variable and select into it as in Dennis' post. I think it will be quite useful to have the exists construct in pl/sql too.

Actually, I am trying to write a generic function for validating the FK relationship. I want to pass as input variables the parent table name, child table name, parent table pk field and child table fk field. Can you please help me write this (it will be a 2min job for you, while may be a day for me!). Or if you have anyother suggestion for validating this relation using an existing oracle fn, please describe. As always thanks verymuch for your time.

I use exists to determine if I need to do an update or insert

Mark Nijhof, July 29, 2004 - 11:20 am UTC

Hi Tom,

I come from using mostly ms sql, but also sybase and mysql. Now I am working in a company where they use oracle, so I am learning a new dialect.

I use exists to determine if I need to do an update or insert in ms sql.

IF EXISTS(SELECT 'True' FROM TABLE WHERE COLUMN = @ID)
BEGIN
UPDATE TABLE;
END
ESLE
BEGIN
INSERT INTO TABLE;
END

Is this a bad way of doing this? And would you use the way you describt on top of the page to do this?

Thanks,

Mark

Tom Kyte
July 29, 2004 - 1:30 pm UTC

i would:


update table
if ( sql%rowcount = 0 )
then
insert
end if;


the exists just wastes time. the update will do the "read" and if it finds something -- the update, if not the insert will happen.

this would be true in sqlserver as well!

Thanks

Mark Nijhof, July 30, 2004 - 4:56 am UTC

Thanks,

I'll starting to use this approch next time, and will alter my own existing procedures. p.s. I am a application developer (programmes) so this might be a stupid mistake, I appreciate to reply.

Mark

Great thread

A reader, January 05, 2007 - 12:08 pm UTC

Tom,

I would like to do the following...I would like to check
for the existance of a partition on a table and if exists email me but do not create it. Do you have something like that...or anyone here in this thread?


Thank you...
Tom Kyte
January 06, 2007 - 8:34 am UTC

does not make sense - what does it mean to "check for the existence of a partition", what would trigger this "check", what causes this "check" to happen.

Given there are potentially an infinite number of partitions you might create on a table, how do you know to "check" for any given one?

Thanks for the response

A reader, January 08, 2007 - 10:18 am UTC

does not make sense - what does it mean to "check for the existence of a partition", what would trigger this "check", what causes this "check" to happen.

- Every month we drop and re-create partition part_1234
however, let's say we forget to create it or drop it.
how can I check first if the partition is there(exists)
before we re-create it...it is sort like your example
above about checking for the existance of a table...



Tom Kyte
January 08, 2007 - 1:14 pm UTC

query user_tab_partitions.

It is more efficient?

sandro, April 27, 2007 - 10:07 am UTC

You have said...
<<...
for x in ( select count(*) cnt
               from dual 
              where exists ( select NULL from foo where bar ) )
  loop
        if ( x.cnt = 1 ) 
        then
          found do something
        else 
          not found
        end if;
  end loop;

is one way (very fast, only runs the subquery as long as it "needs" to, where exists
stops the subquery after hitting the first row)
That loop always executes at least once and at most once since a count(*) on a table
without a group by clause ALWAYS returns at LEAST one row and at MOST one row (even of
the table itself is empty!)...>>


For me is (little) more efficient this approch...
  for x in ( select /*+ first_rows(1) */ count(*) cnt
               from foo 
              where bar 
                and rownum < 2 )
  loop
        if ( x.cnt = 1 ) 
        then
          found do something
        else 
          not found
        end if;
  end loop;

Without query dual...
...waht do you think about?
Tom Kyte
April 27, 2007 - 11:16 am UTC

sure, it works with a little less work

once again

Reene, April 07, 2008 - 4:28 am UTC

Hi Tom

sorry I have posted it in another therad too.but i think this thread is more relevent to ask this.

my question is

1) the way you have explined exists and not exists..will a query with exists and not exists always use a nested loop as join method..as it works on one record at a time.
2) if that is true then what is the alternat of not exists..

my problem is the query below which takes only nested loop joins which i think correct -as it works on one record at a time.

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 690.28 4899.16 1524771 23494672 0 12
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 690.28 4899.16 1524771 23494672 0 12

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 65 (APPS)

SELECT /*+ ordered use_hash(mtp) push_subq */ ooha.org_id "OPERATING UNIT ID"
FROM apps.oe_transaction_types_all ott,
apps.oe_transaction_types_tl ottt_l,
apps.oe_order_lines_all oola,
apps.mtl_parameters mtp,
apps.oe_order_headers_all ooha,
apps.hz_cust_accounts hca
WHERE ooha.header_id = oola.header_id AND
ooha.org_id = oola.org_id AND
ooha.order_source_id = 1263 AND
ott.transaction_type_id = ottt_l.transaction_type_id AND
ott.org_id = ooha.org_id AND
oola.line_type_id = ottt_l.transaction_type_id AND
oola.line_type_id = ott.transaction_type_id AND
mtp.organization_id = oola.ship_from_org_id AND
hca.cust_account_id = ooha.sold_to_org_id AND
(ottt_l.NAME LIKE 'GPO%FE_CONSIGNMENT_LINE' OR
ottt_l.NAME LIKE 'GPO%FE_SHIPMENT_LINE') AND
oola.flow_status_code IN ('SHIPPED', 'CLOSED') AND
hca.attribute9 IS NOT NULL
and NOT EXISTS
( SELECT /*+ NO_UNNEST */ 1
FROM apps.mtl_material_transactions mmt ,
apps.mtl_transaction_lot_numbers mtnl ,
apps.mtl_parameters mp ,
apps.mtl_item_locations mil
WHERE mmt.inventory_item_id = oola.inventory_item_id AND
mmt.transaction_type_id IN (420, 40, 41, 42) AND
mmt.transaction_quantity > 0 AND
mtnl.lot_number = ooha.order_number AND
mtnl.transaction_id = mmt.transaction_id AND
mmt.transaction_date > (TRUNC(SYSDATE) -90) AND
mmt.organization_id = mp.organization_id AND
mp.attribute7 = 'PARTS' AND
mp.attribute11 ='SERVICE' AND
mil.inventory_location_id = hca.attribute9 AND
mil.organization_id = mp.organization_id
) AND
NVL(oola.actual_shipment_date, oola.schedule_ship_date) > SYSDATE - 90


Rows Row Source Operation
------- ---------------------------------------------------
12 NESTED LOOPS (cr=23494672 r=1524771 w=0 time=4640213970 us)
157696 NESTED LOOPS (cr=5730245 r=1402270 w=0 time=3699650845 us)
157697 HASH JOIN (cr=5257151 r=1379549 w=0 time=3576187058 us)
157697 TABLE ACCESS BY INDEX ROWID OE_ORDER_LINES_ALL (cr=5257149 r=1379549 w=0 time=3575115599 us)
2789657 NESTED LOOPS (cr=13232 r=12982 w=0 time=90323177 us)
76 HASH JOIN (cr=277 r=82 w=0 time=175347 us)
4597 TABLE ACCESS FULL OE_TRANSACTION_TYPES_ALL (cr=157 r=0 w=0 time=6683 us)
76 TABLE ACCESS FULL OE_TRANSACTION_TYPES_TL (cr=120 r=82 w=0 time=157808 us)
2789580 INDEX RANGE SCAN GEMS_ONT_ORDER_LINES_N99 (cr=12955 r=12900 w=0 time=86301122 us)(object id 1863725)
377 INDEX FULL SCAN MTL_PARAMETERS_U1 (cr=2 r=0 w=0 time=367 us)(object id 9847)
157696 TABLE ACCESS BY INDEX ROWID OE_ORDER_HEADERS_ALL (cr=473094 r=22721 w=0 time=123022055 us)
157697 INDEX UNIQUE SCAN OE_ORDER_HEADERS_U1 (cr=315397 r=1363 w=0 time=11565697 us)(object id 688729)
12 TABLE ACCESS BY INDEX ROWID HZ_CUST_ACCOUNTS (cr=17764427 r=122501 w=0 time=1114177945 us)
157696 INDEX UNIQUE SCAN HZ_CUST_ACCOUNTS_U1 (cr=315395 r=274 w=0 time=3770326 us)(object id 715003)
157621 TABLE ACCESS BY INDEX ROWID MTL_TRANSACTION_LOT_NUMBERS (cr=17291336 r=121205 w=0 time=1096619120 us)
6897874 NESTED LOOPS (cr=14562731 r=114663 w=0 time=1018837983 us)
3448931 NESTED LOOPS (cr=7488435 r=112057 w=0 time=936487677 us)
157633 NESTED LOOPS (cr=788165 r=581 w=0 time=10022817 us)
157633 INDEX RANGE SCAN MTL_ITEM_LOCATIONS_U1 (cr=472899 r=581 w=0 time=7984520 us)(object id 9761)
157633 TABLE ACCESS BY INDEX ROWID MTL_PARAMETERS (cr=315266 r=0 w=0 time=1667395 us)
157633 INDEX UNIQUE SCAN MTL_PARAMETERS_U1 (cr=157633 r=0 w=0 time=770942 us)(object id 9847)
3448931 TABLE ACCESS BY INDEX ROWID MTL_MATERIAL_TRANSACTIONS (cr=6700270 r=111476 w=0 time=924106849 us)
8273225 INDEX RANGE SCAN MTL_MATERIAL_TRANSACTIONS_N1 (cr=695175 r=32781 w=0 time=303092226 us)(object id 6205511)
3448931 INDEX RANGE SCAN MTL_TRANSACTION_LOT_NUMBERS_N1 (cr=7074296 r=2606 w=0 time=74416944 us)(object id 9976)

is there a way to improve this query.

Thanks
Tom Kyte
April 07, 2008 - 9:31 am UTC

1) the way you have explined exists and not exists..will a query with exists
and not exists always use a nested loop as join method..as it works on one
record at a time.


no, it will not - the CBO knows how to rewrite both constructs as semi-joins.


2) lose the hints, just loose them.

sometime hints helps too

Reene, April 07, 2008 - 9:50 am UTC

Hi Tom

database is oracle 9iR2.oracle apps.
i have seen that in most of the queries where there is too many tables and /or too many where clauses are there.the appropriate hints have improved the performance..why did you say that loose the hints.as sometime they do improve the performance.

consider this

select /*+ parallel(ooh,4) parallel(ool,4) ordered use_hash(ooh ool) */ ooh.order_number,count(ool.line_id)
from ont.oe_order_headers_all ooh,
ont.oe_system_parameters_all osp,
ont.oe_order_lines_all ool,
wsh.wsh_delivery_Details wdd
where ooh.org_id = osp.org_id
and ooh.order_type_id = osp.attribute1
and ooh.order_source_id = 1203
and ooh.header_id = ool.header_id
and ooh.org_id = ool.org_id
and ool.line_id = wdd.source_line_id (+)
and nvl(released_status,'X') <> 'C'
and nvl(released_status,'X') <> 'D'
and ool.shippable_flag = 'Y'
group by ooh.order_number

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.03 0.91 1 6 0 0
Fetch 362 0.36 2570.08 1 3 0 36019
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 364 0.40 2571.00 2 9 0 36019

select oh.order_number,count(ol.line_id)
from ont.oe_order_headers_all oh,
ont.oe_order_lines_all ol,
wsh.wsh_delivery_Details wdd,
ont.OE_SYSTEM_PARAMETERS_ALL osp
where oh.header_id=ol.header_id and
ol.line_id=wdd.source_line_id (+) and
oh.org_id = ol.org_id and
oh.ORDER_TYPE_ID = osp.ATTRIBUTE1 and
oh.org_id=osp.org_id and
(oh.order_source_id = 1203 ) and
nvl(wdd.released_status,'x') <> 'C' and
nvl(wdd.released_status,'x') <> 'D' and
ol.shippable_flag='Y'
group by oh.order_number

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 362 698.53 6250.86 2015571 15852070 0 36019
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 364 698.54 6250.87 2015571 15852070 0 36019


-- another one

select /*+ parallel(ooh,4) push_subq */ ooh.order_number,count(ool.line_id)
from ont.oe_order_headers_all ooh,
ont.oe_order_lines_all ool,
wsh.wsh_delivery_Details wwdd
where ooh.header_id=ool.header_id and
ool.line_id=wwdd.source_line_id (+) and
exists
( select /*+ NO_UNNEST ordered */ 'X'
from ont.oe_order_headers_all oh,
ont.OE_SYSTEM_PARAMETERS_ALL osp,
ont.oe_order_lines_all ol,
wsh.wsh_delivery_Details wdd
where oh.header_id=ol.header_id and
ol.line_id=wdd.source_line_id (+) and
oh.ORDER_TYPE_ID = osp.ATTRIBUTE1 and
oh.org_id=osp.org_id and
oh.order_source_id=1203 and
nvl(wdd.released_status,'x') <> 'C' and
nvl(wdd.released_status,'x') <> 'D' and
ol.shippable_flag='Y' and
ooh.order_number = oh.order_number
) and
ooh.order_source_id=1203 and
nvl(wwdd.released_status,'x') <> 'D' and
ool.shippable_flag='Y'
group by ooh.order_number

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 0 0 0 0
Execute 1 0.02 2.32 0 3 0 0
Fetch 362 219.42 2056.17 205496 18468316 0 36019
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 364 219.44 2058.51 205496 18468319 0 36019

select ooh.order_number,count(ool.line_id)
from ont.oe_order_headers_all ooh,
ont.oe_order_lines_all ool,
wsh.wsh_delivery_Details wwdd
where ooh.header_id=ool.header_id and
ool.line_id=wwdd.source_line_id (+) and
exists
( select 'X'
from ont.oe_order_headers_all oh,
ont.OE_SYSTEM_PARAMETERS_ALL osp,
ont.oe_order_lines_all ol,
wsh.wsh_delivery_Details wdd
where oh.header_id=ol.header_id and
ol.line_id=wdd.source_line_id (+) and
oh.ORDER_TYPE_ID = osp.ATTRIBUTE1 and
oh.org_id=osp.org_id and
oh.order_source_id=1203 and
nvl(wdd.released_status,'x') <> 'C' and
nvl(wdd.released_status,'x') <> 'D' and
ol.shippable_flag='Y' and
ooh.order_number = oh.order_number
) and
ooh.order_source_id=1203 and
nvl(wwdd.released_status,'x') <> 'D' and
ool.shippable_flag='Y'
group by ooh.order_number

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 362 856.28 6515.79 2117825 35153157 0 36019
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 364 856.29 6515.80 2117825 35153157 0 36019

i agree - inapproriate use of hints would impact the peformance negatively,but if we use them after analysis then they help also.

what is your thought on this.
Tom Kyte
April 07, 2008 - 10:35 am UTC

there are two types of hints:

good ones - parallel, cardinality, first_rows, all_rows, dynamic sampling, cursor sharing exact...

there are bad ones - ordered, index, use_nl


use the good ones whenever you want. avoid the bad ones like the plague - use them as a temporary "bug fix", knowing that with every release you need to re-evaluate their use. also, try to understand why you felt compelled to use a hint - it is always for one reason: the optimizer got the estimated cardinality wrong - now, ask "why" and ask "how can I fix that" (eg: dynamically sample the data and fix the problem, tell the optimizer to optimize for initial response time, *give the optimizer information* - do not tell it how to do it's job)

no one likes to use it...

Reene, April 08, 2008 - 1:58 am UTC

Hi Tom

agree.no one i guess likes to go through the cumbersome process of using the good or bad hint but what to do - it is to save the job,if i as a consultant hired to tune a query then i would have to do it - if i go around i tell that i need 7 days to fix the cardinality issue..no one in general likes to hear it,it is another thing that if this thing come from a person of your repute or stature..then probably they will listen and they will never say that this guy has told me this thing as well.rather they will say -he knows nothing.

the query above where i used ordered hint - i could tune in 4 very long days as customer wanted it.that was not a pleasing thing to do..but part of job.part of life.

the point here is - no matter how much we explain or read or understand the truth in last 10 yrs i have found that hint are pretty much part of oracle code. In many oracle own standard programs - i have seen extensive use of hints.

i am sure . someone can put together - the cases where hints can be used and also where hint should not be used.

optimizer dynamic sampling or other good hints work well if query is simpler , having 2-3-4 tables and may be couple of conflicting where clauses..but when it comes to those junk code..which we suppose to tune..i just could not find a way to use them and get the work done.

may be its me..not oracle.


Tom Kyte
April 09, 2008 - 2:02 pm UTC

if you need 7 days to solve a cardinality issue, we might have a problem identified already.

and if it took 4 long days to tune a single query....

maybe you've never actually tried the other hints :(

when will CBO use semi join

Reene, April 08, 2008 - 7:16 am UTC

Hi Tom

in your respons above you said


"no, it will not - the CBO knows how to rewrite both constructs as semi-joins."

when CBO chooses a semi join and when it does not chooses a semi join ? is there a specific case where it will use semi join .




Tom Kyte
April 09, 2008 - 2:08 pm UTC

it will use a semi-join

a) when it can
b) and when the statistics drive it that way

it is all about relative costs - when a semi-join is deemed better than another approach - it'll use it.

here is a simple function

sd, May 22, 2008 - 12:44 pm UTC

CREATE OR REPLACE Function Is_Exists
RETURN char
IS
flgExists Char(1);
begin
select 'T' into flgExists from dual where exists (select empno from emp where deptno=20);
return flgExists;
EXCEPTION
WHEN NO_DATA_FOUND THEN
return 'F';
end;

Selecting into where exists

Allan Peda, October 28, 2011 - 11:57 am UTC

I came upon this thread when googling select into where exists. I wanted to avoid the two step checking whether a row is found when doing a select into, and I don't like the idea of using an exception. I elected to try an outer join on a placeholder select of a constant from dual to force the return of at least one row. In this way if there is no result, the returned value is null. The minor caveat is that this method is limited to cases where a null result is handled in the same fashion as no result at all (selecting columns guaranteed to be "not null").

Naturally I would welcome comments if this is simply too convoluted to be considered a good idea.

declare
v_view_name user_objects.object_name%type := '';
begin
-- outer join trick to return null on no match
-- we get at least one row, simply check for null
select uo.object_name into v_view_name
from (
select o.object_name,
1 lnk
from user_objects o
where o.status = 'INVALID'
and o.object_type = 'VIEW'
and o.object_id = :object_id
) uo
right outer join (
select 1 as lnk from dual
) d
on d.lnk = uo.lnk;
if( v_view_name is not null ) then
execute immediate 'alter view '
||v_view_name||' compile';
end if;
end;
/


Tom Kyte
October 28, 2011 - 12:14 pm UTC

well, I disagree with your coding style. This is what I would code:

begin
   select .. into  ...
   execute immediate 'alter view ' || ....;
exception
   when no_data_found then null;
end;



more performant than a weird query to always get a row.
easier to read and understand.

another approach is:

begin
   for x in (select ... )
   loop
      execute immediate 'alter ....;
   end loop;
end;



It avoids the exception - but at the cost of clarity - it is no longer clear that you are getting one row at most....

a trick you can use to get at least one row and at most one row is to use an aggregate without a group by

begin
  select max(object_name) into v_view_name
    from user_objects
   where status = 'INVALID'
     and object_type = 'VIEW'
     and object_id = :object_id;

   if v_view_name is not null 
   then 
      execute immediate ...
   end if;
end;


But - I'm a fan of the first one with the exception block - it says what it does, it does what it says, it is concise, does the least amount of work, and it easy to understand. The others are all trickery.


Checking based on primary key

Nathan, October 04, 2013 - 1:30 pm UTC

Hi Tom,

I am learning a lots of stuff from the great guru like you.What would be your approach to the following query.
Because from the very begining It was known that the query is based on primary key and must return single row.
So for this, your first apporach is good enough or can carry with the same.

declare
  l_exists integer :=0;
  p_empno  integer :=7839;
begin
  begin
    select 1 into l_exists from emp where empno=p_empno;
  exception
  when no_data_found then
    l_exists:=0;
  end;
  if l_exists=1 then
    dbms_output.put_line('exists');
  else
    dbms_output.put_line('not exists');
  end if;
end;


Regards,
Nathan

Jess, December 03, 2019 - 8:53 pm UTC

Is there a simple way to do a conditional assignment based on existence, but in a sql statement? Say we're hardcoding a select value based on existence like so (don't ask why, legacy code...)

select sd.id, sd.<other columns>,'classroom X'
from student_details sd
where exists (select from students s where s.id = sd.id and (<a bunch of conditionals against S>)
-- above can be rewritten to exists, of course
UNION
select sd.id, sd.<same other columns>, 'classroom Z'
from student_details sd, honor_students h
where sd.id = h.id
and NOT exists (select from students where s.id = sd.id and <the same exact conditionals against S as above>)

so essentially students are split based on whether they have/don't have certain values in student, but the driving superset is a little different...

Is there a way to do it, so that we'd only hit "student_details" and "students" once?

Thank you.



Chris Saxon
December 05, 2019 - 9:13 am UTC

You mean something like this?

create table t1 (
  c1 int
);

create table t2 (
  c1 int
);

insert into t1 values ( 1 );
insert into t1 values ( 2 );

insert into t2 values ( 1 );

select t1.*,
       case 
         when exists (
           select null from t2
           where  t1.c1 = t2.c1
         )
         then 'YES'
         else 'NOPE!'
       end match
from   t1;

C1    MATCH   
    1 YES      
    2 NOPE!