Best oracle questions in September 2011

unexpected query success

11 votes
SELECT COUNT (*)
  FROM rps2_workflow
 WHERE     workflow_added > TO_DATE ('01.09.2011', 'dd.mm.yyyy')
       AND workflow_finished < TO_DATE ('wtf', 'dd.mm.yyyy')
       AND workflow_status IN (7, 12, 17)
       AND workflow_worker = 159

I expect this query to fail, because of invalid date, but it returns 0

The plan for this query shows that on 8th step the invalid clause is processed:

8 TABLE ACCESS BY INDEX ROWID TABLE RPS2.RPS2_WORKFLOW Object Instance: 1  Filter Predicates: ("WORKFLOW_STATUS"=7 OR "WORKFLOW_STATUS"=12 OR "WORKFLOW_STATUS"=17) AND SYS_EXTRACT_UTC("WORKFLOW_FINISHED")<SYS_EXTRACT_UTC(TO_DATE('wtf','dd.mm.yyyy'))  Cost: 11  Bytes: 33  Cardinality: 1  CPU Cost: 8 M  IO Cost: 10  Time: 1                     

If we comment out AND workflow_status IN (7, 12, 17) condition - then expectedly we get ORA-01858: a non-numeric character was found where a numeric was expected

If we comment out AND workflow_finished < TO_DATE ('wtf', 'dd.mm.yyyy') then we get amount of records that fit that conditions (> 0)

How is this possible?

UPD:

The hint /*+no_index(rps2_workflow) */ doesn't change anything (whereas in the plan we see that fullscan is performed)

SELECT STATEMENT  ALL_ROWSCost: 254  Bytes: 31  Cardinality: 1  CPU Cost: 34 M  IO Cost: 248  Time: 4       
2 SORT AGGREGATE  Bytes: 31  Cardinality: 1     
    1 TABLE ACCESS FULL TABLE RPS2.RPS2_WORKFLOW Object Instance: 1  Filter Predicates: "WORKFLOW_WORKER"=159 AND ("WORKFLOW_STATUS"=7 OR "WORKFLOW_STATUS"=12 OR "WORKFLOW_STATUS"=17) AND SYS_EXTRACT_UTC("WORKFLOW_ADDED")>SYS_EXTRACT_UTC(TIMESTAMP' 2011-09-01 00:00:00') AND SYS_EXTRACT_UTC("WORKFLOW_FINISHED")<SYS_EXTRACT_UTC(TO_DATE('wtf','dd.mm.yyyy'))  Cost: 254  Bytes: 31  Cardinality: 1  CPU Cost: 34 M  IO Cost: 248  Time: 4  

It probably found that every record satisfying all the other conditions have a NULL workflow_finished field.

And anything compared to NULL is unknown so it doesn't need to evaluate the other operand.

How to catch DB errors and translate them into meaningful information for the business layer?

11 votes

Usually I have to insert some data in a DB and it can't be inserted because the table has constraints preventing me from doing that. With the app I'm developing, some business rules (like "there are not two persons with the same id type and number" or "the XXXX product is already registered") are enforced with UNIQUE or composite keys and other mechanisms. Although I know that DBMS throws an error message (like ORA-6346 or ) I do not know how to catch those errors in .net 4.0 and translate them to an error that can be meaningful for the business layer.

As an example: I've seen an insertion mechanism that asks the DB if the register already exists and then it proceeds to insert data if it isn't the case. I want to do this only using a query and catching the database constraint violation error because the first way seems to me as very inefficient (DB can alert you about duplication with an error).

How can I implement something like that?

Note: I think that it is possible to catch the exception from the database and use its ORA-xxxx code to try to figure out what has happened. I do not remember with precision if the error message shows which constraint (the name of...) has been broken, but business layer code can contain constants with the constraint names and, from them, know what has happened.

What you should do here depends really on the architecture of your system, and your attitude towards the placement of business logic.

Many systems architects prefer to use a database as a dumb data store, and implement the type of error handling and integrity checking you're talking about in the middle/application layer. This is a perfectly valid approach, and is particularly suited to systems requiring regular smaller releases, where the business logic is subject to regular change (its much easier to redistribute an executable midweek than to co-ordinate a database release), and where the data model is fairly simple.

The other approach is to put some well-defined semi-permanent business logic into the database layer. This is particularly powerful when the data model is more complex, and you have a good DBA! ;)

My personal opinion is, an enterprise database should be responsible for its own integrity, and so I prefer to have logic in the database layer to ensure this - removing any vulnerability to bugs being introduced in non-database code releases. So in your specific example, I would definitely catch the error and report it meaningfully to your application layer.

Oracle supports catching various types of error using names exceptions, allowing you to raise these exceptions to your applications in a meaningful way. For example:

PROCEDURE test() AS
  b VARCHAR2;
BEGIN

  -- if the following row exists, then DUP_VAL_ON_INDEX will be thrown 
  -- (assuming there is a primary key constraint)        

  INSERT INTO table(a,b,c)
  VALUES(1,2,3);  

  -- if there is no matching record, NO_DATA_FOUND will be thrown

  SELECT a
  INTO b
  FROM TABLE
  WHERE c = 'blah';  

EXCEPTION   -- both types of exception can be caught and embellished 
  WHEN DUP_VAL_ON_INDEX THEN
    raise_application_error(-20570, 'Attempted to insert a duplicate value', TRUE);
  WHEN NO_DATA_FOUND THEN
    raise_application_error(-20571, 'No matching row in table for value:' || 'blah', TRUE);
  WHEN OTHERS THEN
  rollback
END test;

You can find more information here: http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/errors.htm

Hope this helps..

Oracle: Full text search with condition

9 votes

I've created an Oracle Text index like the following:

create index my_idx on my_table (text) indextype is ctxsys.context; 

And I can then do the following:

select * from my_table where contains(text, '%blah%') > 0;

But lets say we have a have another column in this table, say group_id, and I wanted to do the following query instead:

select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;

With the above index, Oracle will have to search for all items that contain 'blah', and then check all of their group_ids.

Ideally, I'd prefer to only search the items with group_id = 43, so I'd want an index like this:

create index my_idx on my_table (group_id, text) indextype is ctxsys.context; 

Kind of like a normal index, so a separate text search can be done for each group_id.

Is there a way to do something like this in Oracle (I'm using 10g if that is important)?

Edit (clarification)

Consider a table with one million rows and the following two columns among others, A and B, both numeric. Lets say there are 500 different values of A and 2000 different values of B, and each row is unique.

Now lets consider select ... where A = x and B = y

An index on A and B separately as far as I can tell do an index search on B, which will return 500 different rows, and then do a join/scan on these rows. In any case, at least 500 rows have to be looked at (aside from the database being lucky and finding the required row early.

Whereas an index on (A,B) is much more effective, it finds the one row in one index search.

Putting separate indexes on group_id and the text I feel only leaves the query generator with two options.

(1) Use the group_id index, and scan all the resulting rows for the text.
(2) Use the text index, and scan all the resulting rows for the group_id.
(3) Use both indexes, and do a join.

Whereas I want:

(4) Use the (group_id, "text") index to find the text index under the particular group_id and scan that text index for the particular row/rows I need. No scanning and checking or joining required, much like when using an index on (A,B).

Oracle Text

1 - You can improve performance by creating the CONTEXT index with FILTER BY:

create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;

In my tests the filter by definitely improved the performance, but it was still slightly faster to just use a btree index on group_id.

2 - CTXCAT indexes use "sub-indexes", and seem to work similar to a multi-column index. This seems to be the option (4) you're looking for:

begin
  ctx_ddl.create_index_set('my_table_index_set');
  ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/

create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
    parameters('index set my_table_index_set');

select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0

This is likely the fastest approach. Using the above query against 120MB of random text similar to your A and B scenario required only 18 consistent gets. But on the downside, creating the CTXCAT index took almost 11 minutes and used 1.8GB of space.

(Note: Oracle Text seems to work correctly here, but I'm not familiar with Text and I can't gaurentee this isn't an inappropriate use of these indexes like @NullUserException said.)

Multi-column indexes vs. index joins

For the situation you describe in your edit, normally there would not be a significant difference between using an index on (A,B) and joining separate indexes on A and B. I built some tests with data similar to what you described and an index join required only 7 consistent gets versus 2 consistent gets for the multi-column index.

The reason for this is because Oracle retrieves data in blocks. A block is usually 8K, and an index block is already sorted, so you can probably fit the 500 to 2000 values in a few blocks. If you're worried about performance, usually the IO to read and write blocks is the only thing that matters. Whether or not Oracle has to join together a few thousand rows is an inconsequential amount of CPU time.

However, this doesn't apply to Oracle Text indexes. You can join a CONTEXT index with a btree index (a "bitmap and"?), but the performance is poor.

Oracle - How to create a materialized view with FAST REFRESH and JOINS

7 votes

So I'm pretty sure Oracle supports this, so I have no idea what I'm doing wrong. This code works:

CREATE MATERIALIZED VIEW MV_Test
  NOLOGGING
  CACHE
  BUILD IMMEDIATE 
  REFRESH FAST ON COMMIT 
  AS
    SELECT V.* FROM TPM_PROJECTVERSION V;

If I add in a JOIN, it breaks:

CREATE MATERIALIZED VIEW MV_Test
  NOLOGGING
  CACHE
  BUILD IMMEDIATE 
  REFRESH FAST ON COMMIT 
  AS
    SELECT V.*, P.* FROM TPM_PROJECTVERSION V
    INNER JOIN TPM_PROJECT P ON P.PROJECTID = V.PROJECTID

Now I get the error:

ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view

I've created materialized view logs on both TPM_PROJECT and TPM_PROJECTVERSION. TPM_PROJECT has a primary key of PROJECTID and TPM_PROJECTVERSION has a compound primary key of (PROJECTID,VERSIONID). What's the trick to this? I've been digging through Oracle manuals to no avail. Thanks!

To start with, from the Oracle Database Data Warehousing Guide:

Restrictions on Fast Refresh on Materialized Views with Joins Only

...

  • Rowids of all the tables in the FROM list must appear in the SELECT list of the query.

This means that your statement will need to look something like this:

CREATE MATERIALIZED VIEW MV_Test
  NOLOGGING
  CACHE
  BUILD IMMEDIATE 
  REFRESH FAST ON COMMIT 
  AS
    SELECT V.*, P.*, V.ROWID as V_ROWID, P.ROWID as P_ROWID 
    FROM TPM_PROJECTVERSION V,
         TPM_PROJECT P 
    WHERE P.PROJECTID = V.PROJECTID

Another key aspect to note is that your materialized view logs must be created as with rowid.

Below is a functional test scenario:

CREATE TABLE foo(foo NUMBER, CONSTRAINT foo_pk PRIMARY KEY(foo));

CREATE MATERIALIZED VIEW LOG ON foo WITH ROWID;

CREATE TABLE bar(foo NUMBER, bar NUMBER, CONSTRAINT bar_pk PRIMARY KEY(foo, bar));

CREATE MATERIALIZED VIEW LOG ON bar WITH ROWID;

CREATE MATERIALIZED VIEW foo_bar
  NOLOGGING
  CACHE
  BUILD IMMEDIATE
  REFRESH FAST ON COMMIT  AS SELECT foo.foo, 
                                    bar.bar, 
                                    foo.ROWID AS foo_rowid, 
                                    bar.ROWID AS bar_rowid 
                               FROM foo, bar
                              WHERE foo.foo = bar.foo;

Oracle MIN as analytic function - odd behavior with ORDER BY?

6 votes

This particular case was distilled from an example where the programmer assumed that for two shipments into a tank car, line #1 would be loaded first. I corrected this to allow for the loading to be performed in any order - however, I discovered that MIN() OVER (PARTITION BY) allows an ORDER BY in Oracle (this is not allowed in SQL Server), and additionally, it alters the behavior of the function, causing the ORDER BY to apparently be added to the PARTITION BY.

WITH data AS (
SELECT 1 AS SHIPMENT_ID, 1 AS LINE_NUMBER, 2 AS TARE, 3 AS GROSS FROM DUAL
UNION ALL
SELECT 1 AS SHIPMENT_ID, 2 AS LINE_NUMBER, 1 AS TARE, 2 AS GROSS FROM DUAL
)
SELECT MIN(tare) OVER (PARTITION BY shipment_id) first_tare
,MAX(gross) OVER (PARTITION BY shipment_id) last_gross
,FIRST_VALUE(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect
,FIRST_VALUE(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect
,MIN(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect_still
,MAX(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect_still
,MIN(tare) OVER (PARTITION BY shipment_id, LINE_NUMBER) first_tare_incorrect_still2
,MAX(gross) OVER (PARTITION BY shipment_id, LINE_NUMBER) last_gross_incorrect_still2
 FROM data

A SQL Server example (with non-applicable code commented out):

WITH data AS (
SELECT 1 AS SHIPMENT_ID, 1 AS LINE_NUMBER, 2 AS TARE, 3 AS GROSS -- FROM DUAL
UNION ALL
SELECT 1 AS SHIPMENT_ID, 2 AS LINE_NUMBER, 1 AS TARE, 2 AS GROSS -- FROM DUAL
)
SELECT MIN(tare) OVER (PARTITION BY shipment_id) first_tare
,MAX(gross) OVER (PARTITION BY shipment_id) last_gross
-- ,FIRST_VALUE(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect
-- ,FIRST_VALUE(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect
-- ,MIN(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect_still
-- ,MAX(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect_still
,MIN(tare) OVER (PARTITION BY shipment_id, LINE_NUMBER) first_tare_incorrect_still2
,MAX(gross) OVER (PARTITION BY shipment_id, LINE_NUMBER) last_gross_incorrect_still2
 FROM data

So question: What is Oracle doing and why and is it right?

If you add an ORDER BY to the MIN analytic function, you turn it into a "min so far" function rather than an overall minimum. For the final row for whatever you're partitioning by, the results will be the same. But the prior rows may have a different "min so far" than the overall minimum.

Using the EMP table as an example, you can see that the minimum salary so far for the department eventually converges on the overall minimum for the department. And you can see that the "min so far" value for any given department decreases as lower values are encountered.

SQL> ed
Wrote file afiedt.buf

  1  select ename,
  2         deptno,
  3         sal,
  4         min(sal) over (partition by deptno order by ename) min_so_far,
  5         min(sal) over (partition by deptno) min_overall
  6    from emp
  7*  order by deptno, ename
SQL> /

ENAME          DEPTNO        SAL MIN_SO_FAR MIN_OVERALL
---------- ---------- ---------- ---------- -----------
CLARK              10       2450       2450        1300
KING               10       5000       2450        1300
MILLER             10       1300       1300        1300
ADAMS              20       1110       1110         800
FORD               20       3000       1110         800
JONES              20       2975       1110         800
SCOTT              20       3000       1110         800
smith              20        800        800         800
ALLEN              30       1600       1600         950
BLAKE              30       2850       1600         950
MARTIN             30       1250       1250         950
SM0                30        950        950         950
TURNER             30       1500        950         950
WARD               30       1250        950         950
BAR
PAV

16 rows selected.

Of course, it would make more sense to use this form of the analytic function when you're trying to do something like compute a personal best that you can use as a comparison in future periods. If you're tracking an individual's decreasing golf scores, mile times, or weight, displaying personal bests can be a form of motivation.

SQL> ed
Wrote file afiedt.buf

  1  with golf_scores as
  2  (  select 1 golfer_id, 80 score, sysdate dt from dual union all
  3     select 1, 82, sysdate+1 dt from dual union all
  4     select 1, 72, sysdate+2 dt from dual union all
  5     select 1, 75, sysdate+3 dt from dual union all
  6     select 1, 71, sysdate+4 dt from dual union all
  7     select 2, 74, sysdate from dual )
  8  select golfer_id,
  9         score,
 10         dt,
 11         (case when score=personal_best
 12               then 'New personal best'
 13               else null
 14           end) msg
 15    from (
 16  select golfer_id,
 17         score,
 18         dt,
 19         min(score) over (partition by golfer_id
 20                              order by dt) personal_best
 21    from golf_scores
 22*        )
SQL> /

 GOLFER_ID      SCORE DT        MSG
---------- ---------- --------- -----------------
         1         80 12-SEP-11 New personal best
         1         82 13-SEP-11
         1         72 14-SEP-11 New personal best
         1         75 15-SEP-11
         1         71 16-SEP-11 New personal best
         2         74 12-SEP-11 New personal best

6 rows selected.

Oracle combine multiple rows into one with distinct title

6 votes

I have this statement that combines multiple rows into one and then outputs it in the way I need the data:

 SELECT COURSES_ID, REQUISITE_TYPE_TITLE
            , RTRIM
           ( xmlagg (xmlelement (c, CONDITION_TITLE || '' || REQ_TEXT || ''  ) order by   ORDER_NUM).extract ('//text()')
           , ',' ) AS REQTexts
    FROM   COS_REQUISITES
    WHERE COURSES_ID = '1175'
    AND REQUISITE_TYPE_TITLE !=  'Corequisite'
    GROUP BY COURSES_ID, REQUISITE_TYPE_TITLE;

Result:

╔═══════════╦════════════╦═════════════════════════════════════╦═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ORDER_NUM ║ COURSES_ID ║        REQUISITE_TYPE_TITLE         ║                                                                                                                                                           REQ_TEXT                                                                                                                                                            ║
╠═══════════╬════════════╬═════════════════════════════════════╬═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║         1 ║       1175 ║ Limitation on Enrollment            ║   With minimum grade of &quot;C&quot;                                                                                                                                                                                                                                                                                         ║
║         2 ║       1175 ║ Advisory on Recommended Preparation ║ MATH 200 or equivalent college course with &quot;C&quot; or better  or equivalent college course with &quot;C&quot; or better or MATH 205 or equivalent college course with &quot;C&quot; or better or   or equivalent college course with &quot;C&quot; or better  or equivalent college course with &quot;C&quot; or better ║
╚═══════════╩════════════╩═════════════════════════════════════╩═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

Structure:

╔═══════════╦════════════╦═══════════╦═════════════════════════════════════╦══════════════════════════════════════════════════════════╗
║ ORDER_NUM ║ COURSES_ID ║ CONDITION ║        REQUISITE_TYPE_TITLE         ║                         REQ_TEXT                         ║
╠═══════════╬════════════╬═══════════╬═════════════════════════════════════╬══════════════════════════════════════════════════════════╣
║      1164 ║       1175 ║           ║ Advisory on Recommended Preparation ║ MATH 200 or equivalent college course with "C" or better ║
║      1165 ║       1175 ║           ║ Advisory on Recommended Preparation ║   or equivalent college course with "C" or better        ║
║      1166 ║       1175 ║  or       ║ Advisory on Recommended Preparation ║ MATH 205 or equivalent college course with "C" or better ║
║      1167 ║       1175 ║  or       ║ Advisory on Recommended Preparation ║   or equivalent college course with "C" or better        ║
║      1168 ║       1175 ║           ║ Advisory on Recommended Preparation ║   or equivalent college course with "C" or better        ║
║      1169 ║       1175 ║           ║ Limitation on Enrollment            ║   With minimum grade of "C"                              ║
╚═══════════╩════════════╩═══════════╩═════════════════════════════════════╩══════════════════════════════════════════════════════════╝

Wanted result:

╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Advisory on Recommended Preparation MATH 200 or equivalent college course with &quot;C&quot; or better  or equivalent college course with &quot;C&quot; or better or MATH 205 or equivalent college course with &quot;C&quot; or better or equivalent college course with &quot;C&quot; or better  or equivalent college course with &quot;C&quot; or better Limitation on Enrollment With minimum grade of &quot;C&quot; ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

I'm trying to avoid having to put this to a temp table or multiple views that would output this into one section.

I know it has bad data but I was told Garbage In, Garbage Out. Any help would be greatly appreciated.

Even with LISTAGG which doesn't allow for distinct as far as I can tell:

SELECT COURSES_ID, LISTAGG(REQUISITE_TYPE_TITLE || ' ' || CONDITION_TITLE || ' ' || REQ_TEXT, ' ') WITHIN GROUP (ORDER BY ORDER_NUM) AS Title FROM COS_REQUISITES WHERE COURSES_ID = '1175' GROUP BY COURSES_ID;

Result:

╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                                                                                                                                                                                                                                             TITLE                                                                                                                                                                                                                                                              ║
╠════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ Advisory on Recommended Preparation  MATH 200 or equivalent college course with "C" or better Advisory on Recommended Preparation    or equivalent college course with "C" or better Advisory on Recommended Preparation  or  MATH 205 or equivalent college course with "C" or better Advisory on Recommended Preparation  or    or equivalent college course with "C" or better Advisory on Recommended Preparation    or equivalent college course with "C" or better Limitation on Enrollment    With minimum grade of "C" ║
╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

Notice the REQUISITE_TYPE_TITLE repeats, I only want it to show up once like the wanted result. I can't modify the table to have them null due to this is a table being loaded dynamically and updated by someone else daily.

a bit messy but should yield what you asked for

SELECT 
COURSES_ID, 
RTRIM ( xmlagg (xmlelement (c, REQUISITE_TYPE_TITLE || '' || REQTexts || ''  ) order by   mino).extract ('//text()'), ',' ) AS REQTexts
FROM
(
SELECT 
COURSES_ID, REQUISITE_TYPE_TITLE, MIN (ORDER_NUM) mino, 
RTRIM ( xmlagg (xmlelement (c, CONDITION_TITLE || '' || REQ_TEXT || ''  ) order by   ORDER_NUM).extract ('//text()') , ',' ) AS REQTexts
FROM COS_REQUISITES
WHERE COURSES_ID = '1175'
AND REQUISITE_TYPE_TITLE !=  'Corequisite'
GROUP BY COURSES_ID, REQUISITE_TYPE_TITLE
) 
GROUP BY COURSES_ID;

Oracle - Why is SELECT * FROM Foo; so slow?

5 votes

I'm working on some Oracle performance issues with our web app. One thing I've noticed that's seems to obfuscate any sort of tests is that simple queries that return a lot of results are still very slow. One example is:

select * from TPM_PROJECTWORKGROUPS;

When I run it, I get:

 5825 record(s) selected [Fetch MetaData: 0ms] [Fetch Data: 59s] 

 [Executed: 9/22/2011 1:52:38 PM] [Execution: 203ms] 

If I understand this correctly, it means the actual query took 203ms to run but it took 59 seconds for that data to be returned to the client, is that was "Fetch" means in this case?

I don't have access to connect to the database machine directly and run the query locally, but is it safe to assume that the culprit is the actual network bandwidth itself? This makes sense since I'm in Seattle and the server is in New York, but still a minute for 5800 rows seems like pretty slow through-put.

Is there any quick advice for a) confirming that network bandwidth is indeed the problem and b) any "gotchas" or things to check for why serializing the data over the wire is so slow? Thanks!

A Few Updates Based On Comments:

SELECT COUNT(*) FROM (select * from TPM_PROJECTWORKGROUPS) t;

Results:

 1 record(s) selected [Fetch MetaData: 0ms] [Fetch Data: 0ms] 

 [Executed: 9/22/2011 2:16:08 PM] [Execution: 219ms] 

And if I try selecting only one column:

SELECT PROJECTID FROM TPM_PROJECTWORKGROUPS;

Results:

5825 record(s) selected [Fetch MetaData: 0ms] [Fetch Data: 1m 0s]

[Executed: 9/22/2011 2:17:20 PM] [Execution: 203ms]

Table schema:

PROJECTID (NUMBER) WORKGROUPID (NUMBER)

What API are you using to interact with the database (SQL*Plus, JDBC, ODBC, etc.)? Any API will have some function that specifies how many rows (or how much data) to fetch in a single network round-trip. For example, in SQL*Plus, it's set arraysize N. In JDBC, it's setFetchSize. Other APIs will have similar functions. If you're on a WAN, you generally want to minimize how chatty your application is by increasing the number of rows fetched with each network round trip.

Along the same lines, you'll probably benefit from moving less data over the network and pushing more logic to the server. Do you actually display a grid with 5800 rows of data to the user? Or do you do fetch that data and then perform some processing in the application (i.e. order the data and display the first 100 rows)? If you can push that processing to the database and reduce the amount of data that has to be transmitted over the database, you'll be much better off.

Oracle has options to configure the SDU and TDU as well as a few other networking parameters in SQL*Net. I wouldn't start looking at those options, though, until you've optimized the fetch size and ensured that you're pulling back the least amount of data possible.