Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, ahmed.

Asked: October 12, 2010 - 12:12 am UTC

Last updated: November 07, 2011 - 12:04 pm UTC

Version: 10g

Viewed 10K+ times! This question is

You Asked

i amd using emp table as an example
my query is when i pass empno as parameter it should retrive data of that empno only, but when i pass nothing then it should fetch data of whole emp table
query which i am using is
SELECT * FROM EMP WHERE TO_char(EMPNO) IN CASE WHEN :EMPNO IS NOT NULL THEN :EMPNO
ELSE (SELECT to_char(EMPNO) FROM EMP) END
it is working fine when i am passing one empno as parameter,but when i am passing no value it is giving error as SUBQUERY RETURNS MORE THAN ONE NUMBER OR ROWS.

and Tom said...

select * from emp where empno = nvl(:bind_variable,empno);

would work well in this case, you'll get what is known as an NVL-or expansion - sort of two query plans in one.

You get an index range scan if the bind variable is NOT NULL
You get a full scan otherwise.

scott%ORA10GR2> variable empno number
scott%ORA10GR2> set autotrace traceonly explain
scott%ORA10GR2> select * from emp where empno = nvl(:empno,empno);

Execution Plan
----------------------------------------------------------
Plan hash value: 1180660262

----------------------------------------------------------------------------------------
| Id  | Operation                     | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |        |    15 |   555 |     3   (0)| 00:00:01 |
|   1 |  CONCATENATION                |        |       |       |            |          |
|*  2 |   FILTER                      |        |       |       |            |          |
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP    |    14 |   518 |     2   (0)| 00:00:01 |
|*  4 |     INDEX FULL SCAN           | EMP_PK |    14 |       |     1   (0)| 00:00:01 |
|*  5 |   FILTER                      |        |       |       |            |          |
|   6 |    TABLE ACCESS BY INDEX ROWID| EMP    |     1 |    37 |     1   (0)| 00:00:01 |
|*  7 |     INDEX UNIQUE SCAN         | EMP_PK |     1 |       |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(:EMPNO IS NULL)
   4 - filter("EMPNO" IS NOT NULL)
   5 - filter(:EMPNO IS NOT NULL)
   7 - access("EMPNO"=:EMPNO)


Rating

  (7 ratings)

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

Comments

mfz, October 12, 2010 - 1:13 pm UTC

In Oracle 8i manual , it was suggested to use union for these kind of queries ( aka nvl(nvl(:empno,empno)) ...

Is this statement still holds good ?

<Quote from Oracle Manual>
Predicate Collapsing
Predicate collapsing occurs when a column predicate involves more than one bind variable. An expression of the form [col = DECODE (:b1,'',:b3,col)] is a an example of predicate collapsing. This implies that if the bind variable 1 is null, then the bind variable 3 should be used; otherwise, the expression will result in [ col = col]. This prevents the optimizer from utilizing the index on the "col" column due to the decode construct.

The following example demonstrates how predicate collapsing is used to collapse a name bind variable with the delivery_id bind variable in a single filter. As can be seen from the EXPLAIN PLAN, this results in a full table scan on the wsh_deliveries table because of the NVL() construct on the delivery_id column, as well as the DECODE() construct on the name column.

SELECT delivery_id, planned_departure_id, organization_id, status_code
FROM wsh_deliveries
WHERE delivery_id = NVL(:b1,delivery_id) AND name = DECODE(:b1,'',:b3, NAME)
ORDER BY UPPER(HRE.full_name)

PLAN:

Cost=2090 SELECT STATEMENT
Cost=2090 TABLE ACCESS FULL WSH_DELIVERIES


This query can be rewritten using a UNION to short-circuit one-side of the UNION based on the bind variable values. For example, if the delivery_id bind is supplied, only the first branch of the UNION is executed.

....
....
....
</Quote from Oracle Manual>

Tom Kyte
October 12, 2010 - 1:47 pm UTC

that statement actually stopped being true in Oracle 8i - that was the version that introduced NVL() or expansion.


ahmed, October 13, 2010 - 12:01 am UTC

Thanks.Now it is working fine.

decode with multiple row subquery

Bilal, December 22, 2010 - 2:56 am UTC

Hi Tom,

DECODE function in WHERE clause is not capable of handling sub-query with multiple rows. Suppose the following scenario:

VARIABLE ParaCCenter NUMBER

BEGIN
:paraccenter:=0;
END;
/

CREATE TABLE ccenters
(id NUMBER,
name VARCHAR2(20),
base number);

INSERT INTO ccenters VALUES(1,'NUST',null);
INSERT INTO ccenters VALUES(2,'SEECS',1)
INSERT INTO ccenters VALUES(3,'NBS',1)
commit;

SELECT * FROM ccenters
WHERE id IN DECODE(:ParaCCenter, 0, id,
(SELECT id FROM ccenters
START WITH base=:ParaCCenter
CONNECT BY PRIOR id = base
UNION
SELECT :ParaCCenter FROM dual
)
)
/
BEGIN
:paraCCenter:=1;
END;
/

SELECT * FROM ccenters
WHERE id IN DECODE(:ParaCCenter, 0, id,
(SELECT id FROM ccenters
START WITH base=:ParaCCenter
CONNECT BY PRIOR id = base
UNION
SELECT :ParaCCenter FROM dual))
/
The result is
(SELECT id FROM ccenters
*
ERROR at line 3:
ORA-01427: single-row subquery returns more than one row

How this query can be rewritten for the same result.

Thanks
Tom Kyte
December 22, 2010 - 2:26 pm UTC

this is a bad idea

You will code instead:

where ( 
        (:paraCCenter = 0) and id is not null)
        OR 
        (:paraCCenter<> 0) and id in (select id from ccenters ....)
      )


your approach, had it worked, would have resulted in a pretty poorly performing plan.


the id is not null is only necessary is id is nullable.

NVL OR expansion with COALESCE

Martin, December 23, 2010 - 3:35 am UTC

Hi Tom,

very interesting discussion, however in some circumstances, COALESCE is preferable to NVL (due to short-circuiting), however, it seems the NVL OR expansion has not been implemented for COALESCE (this is 10.2.0.4, haven't tried it on 11g) :

SQL> explain plan for
  2   select * from emp where empno = nvl(:bind_variable,empno); 

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 851340519

----------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              |    15 |   555 |     3   (0)| 00:00:01 |
|   1 |  CONCATENATION                |              |       |       |            |          |
|*  2 |   FILTER                      |              |       |       |            |          |
|*  3 |    TABLE ACCESS FULL          | EMP          |    14 |   518 |     2   (0)| 00:00:01 |
|*  4 |   FILTER                      |              |       |       |            |          |
|   5 |    TABLE ACCESS BY INDEX ROWID| EMP          |     1 |    37 |     1   (0)| 00:00:01 |
|*  6 |     INDEX UNIQUE SCAN         | SYS_C0066895 |     1 |       |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(:BIND_VARIABLE IS NULL)
   3 - filter("EMPNO" IS NOT NULL)
   4 - filter(:BIND_VARIABLE IS NOT NULL)
   6 - access("EMPNO"=:BIND_VARIABLE)

21 rows selected.

SQL> explain plan for
  2  select * from emp where empno = coalesce(:bind_variable,empno); 

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2872589290

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    37 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |    37 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("EMPNO"=COALESCE(:BIND_VARIABLE,"EMPNO"))

13 rows selected.

Are there any plans to implement a similar optimisation using COALESCE? (apologies if there already is in 11g, can't try it here). 

Tom Kyte
December 23, 2010 - 10:59 am UTC

coalese takes a variable number of inputs - making it not generally useful with an or expansion. It can have two OR MORE parameters - not just two.

Ok

Martin, December 24, 2010 - 4:28 am UTC

Hi Tom,

Yep, understand that COALESCE can take a variable number of inputs (and the generic expansion would be unwieldy), but I could imagine the situation that the optimizer, if the COALESCE call has two (and only two) input parameters, could do a similar expansion. One for the future, maybe... ;-)

Thanks for the info.

Martin.

I'm using subquery in case statement

Venkat, November 07, 2011 - 4:47 am UTC

Tom, I have learned a lot from your site. Currently I'm facing small issue. I'm using subquery in case statement. Its working in 11g and nor working in 10g(10.2.0.4.0). Am doing some thing wrong? please find the sql.

SELECT
 TO_DATE(:p_cob_date,'DD-MON-YYYY') month,
  case when  WORKFLOW_TYPE IN (SELECT DOCUMENT_TYPE FROM DOCUMENT_TYPE_FILTERS WHERE FILTERS='INITIAL') then 'CAWV_INITIAL_CA'
        when  WORKFLOW_TYPE IN (SELECT DOCUMENT_TYPE FROM DOCUMENT_TYPE_FILTERS WHERE FILTERS='ANNUAL') then 'CAWV_ANNUAL_CA'           
  end kri,COUNT(WORKFLOW_ID) itms_total,
FROM ME_DATA
WHERE to_date(MONTH, 'DD-MON-YYYY') = to_date(:p_cob_date, 'DD-MON-YYYY')
    AND (WORKFLOW_TYPE IN (SELECT DOCUMENT_TYPE FROM DOCUMENT_TYPE_FILTERS WHERE FILTERS IN ('ANNUAL','INITIAL')
GROUP BY NVL(COMPLETED_BY, 'UNKNOWN'),
case when  WORKFLOW_TYPE IN (SELECT DOCUMENT_TYPE FROM DOCUMENT_TYPE_FILTERS WHERE FILTERS='INITIAL') then 'CAWV_INITIAL_CA'
     when  WORKFLOW_TYPE IN (SELECT DOCUMENT_TYPE FROM DOCUMENT_TYPE_FILTERS WHERE FILTERS='ANNUAL') then 'CAWV_ANNUAL_CA'          
  end;



Thanks & Regards,
Venkat.
Tom Kyte
November 07, 2011 - 12:04 pm UTC

I need a full example, just like I give you guys.

I cannot run your query
I don't know what you mean by "not working". does it mean "getting wrong answer", does it mean "getting an error", etc.

I would suggest a JOIN in any case - just joing to document_type_filters


the concept seems to work in 10.2.0.5

ops$tkyte%ORA10GR2> l
  1  select dummy,
  2         case when dummy in (select 'Y' from dual) then 'it is y'
  3              when dummy in (select 'X' from dual) then 'it is x'
  4          end esac
  5*  from dual
ops$tkyte%ORA10GR2> /

D ESAC
- -------
X it is x

This is bug in Oracle 10g.

venkat, November 07, 2011 - 11:34 am UTC

Hi,

We find out this is bug Oracle 10g . We are apply the fix.

ORA-00979 GROUP BY expression Error for SDE_ORA_PersistedStage_WorkforceEvent_SupervisorStatus_Full [ID 884298.1]

Modified 08-SEP-2009 Type PROBLEM Status MODERATED
Symptoms
When attempting to run 7.9.6 SDE_ORA_PersistedStage_WorkforceEvent_SupervisorStatus_Full, the following error occurs:

ORA-00979: not a GROUP BY expression
Database driver error...
Function Name : Execute
SQL Stmt : SELECT

The Oracle DB version for source and target instances is Oracle 10.2.0.4
Cause
It was found that this issue has been caused by Oracle Database issue, which is reported in Bug:
Bug 8564792: SR 3-934264767: WORFORCEENHANCEMENT: R12:ORA-00979: NOT A GROUP BY EXPRERROR

This OBI Apps Bug reference and Oracle DB Bug 5059447.
Solution
The patch specified in bug 5059447 fixed the issue.
Bug 5059447 - ORA-979 WITH CASE, SUBQUERY AND COMPLEX VIEW MERGING

After logging a ticket with Oracle DB Support to download and apply this Oracle DB Patch the issue was resolved.

Regards,
Venkat.