views:

324

answers:

9

What are some Oracle gotchas for someone new to the platform, but not new to relational databases (MySQL, MS SQL Server, Postgres, etc.) in general.

Two examples of the kind of things I'm looking for

  1. Many relational database products handle creating an auto_increment key for you. Oracle does not, you must manually create the sequence, then create the trigger

  2. When INSERTING data via the SQL Developer interface, you have to manually commit the data

Bonus points for PHP related gotchas, as that's the platform I'll this hypothetical experienced newb will be using.

+16  A: 

Note: I'm explaining only the gotchas here, i. e. situations when Oracle behaves not as other systems do. Oracle has numerous benefits over other RDBMS's, but they are not the topic of the post.

  • You cannot SELECT without FROM.

    SELECT  1
    

    will fail, you need to:

    SELECT  1
    FROM    dual
    
  • Empty string and NULL are the same thing.

    SELECT  *
    FROM    dual
    WHERE   '' = ''
    

    returns nothing.

  • There are neither TOP nor LIMIT. You limit your results in the WHERE clause:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    exactly this way, using a subquery, since ROWNUM is evaluated before ORDER BY.

  • You cannot nest the correlated subqueries more than one level deep. This one will fail:

    SELECT  (
            SELECT  *
            FROM    (
                    SELECT  dummy
                    FROM    dual di
                    WHERE   di.dummy = do.dummy
                    ORDER BY
                            dummy
                    )
            WHERE   rownum = 1
            )
    FROM    dual do
    

    This is a problem.

  • NULL values are not indexed. This query will not use an index for ordering:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    , unless col is marked as NOT NULL.

    Note than it's NULL values that are not indexed, not columns. You can create an index on a nullable column, and non-NULL values will get into the index.

    However, the index will not be used when the query condition assumes that NULL values can possibly satisfy it.

    In the example above you want all value to be returned (including NULLs). Then index doesn't know of non-NULL values, hence, cannot retrieve them.

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    But this query will use the index:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            WHERE   col IS NOT NULL
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    , since non-NULL values cannot ever satisfy the condition.

  • By default, NULLs are sorted last, not first (like in PostgreSQL, but unlike MySQL and SQL Server)

    This query:

    SELECT  *
    FROM    (
            SELECT  1 AS id
            FROM    dual
            UNION ALL
            SELECT  NULL AS id
            FROM    dual
            ) q
    ORDER BY
            id
    

    will return

    id
    ---
    1
    NULL
    

    To sort like in SQL Server and MySQL, use this:

    SELECT  *
    FROM    (
            SELECT  1 AS id
            FROM    dual
            UNION ALL
            SELECT  NULL AS id
            FROM    dual
            ) q
    ORDER BY
            id NULLS FIRST
    

    Note that it breaks rownum order unless the latter is not used out of the subquery (like explained above)

  • "MYTABLE" and "mytable" (double quotes matter) are different objects.

    SELECT  *
    FROM    mytable -- wihout quotes
    

    will select from the former, not the latter. If the former does not exist, the query will fail.

    CREATE TABLE mytable
    

    creates "MYTABLE", not "mytable".

  • In Oracle, all implicit locks (that result from DML operations) are row-level and are never escalated. That is no row not affected by the transaction can be implicitly locked.

    Writers never block readers (and vice versa).

    To lock the whole table, you should issue an explicit LOCK TABLE statement.

    Row locks are stored on the datapages.

  • In Oracle, there are no "CLUSTERED indexes", there are "index-organized tables". By default, tables are heap organized (unlike SQL Server and MySQL with InnoDB).

    In Oracle world, a "clustered storage" means organizing several tables so that the rows which share a common key (from several tables) also share a datapage.

    A single datapage hosts multiple rows from multiple tables which makes joins on this key super fast.

Quassnoi
You need to explain that. AFAIK select '' IS NULL from DUAL, would give you a false.
uswaretech
@uswaretech: no, it will return a row. Just try it.
Quassnoi
the reverse of this bit me on sqlserver yesterday. we had oracle based scripts that were inserting '', and sqlserver doesn't turn that into null... took me a while to backtrack to this.
John Gardner
But thats ... ridiculous. Null is not an empty string, if we are at it why not treat 0 as null, hmm. I assume there is a good reason for it?
uswaretech
@uswaretech: Oracle is very very old. When it was being developed, there were no standards, and they decided empty strings and `NULL`'s to be the same. `ANSI SQL` demands them to be different, that's why `Oracle` uses `VARCHAR2` instead of `VARCHAR`, and reserves `VARCHAR` to make it `ANSI` compatible in future.
Quassnoi
I disagree with your example for "NULL values are not indexed. This query will not use an index:". For one thing, even if the column is not null, it still will not use an index - table scans are faster when traversing the whole table. But even if you have a column that is "NULL", you can still index it, and the index will be used - as long as you're constraining on a value that isn't null. (cont.)
Steve Broberg
A better example would be to say that, if col has an index on it and has NOT been declared as "NOT NULL", then "select * from mytable where col is null" will not use an index, but "select * from mytable where col = 123" will.
Steve Broberg
@Steve: my point was that that very query that I wrote will not use the index under any conditions (even if you hint it with `INDEX`). `rownum < 10` makes index the cheaper to use than full table scan (unless the table is less than a dozen values long).
Quassnoi
Oracle has table level locks. "SELECT id FROM my_table WHERE 1=2 FOR UPDATE". From another session you can't drop the table but you can delete + commit all rows.
jva
@jva: this locks the dictionary, not the table itself.
Quassnoi
How about "LOCK TABLE my_table IN EXCLUSIVE MODE" then?
jva
Also - http://download.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#2937 mentions both table locks and row locks.
jva
@jva: yes, but this is an explicit lock. I meant that all implicit locks are row-level (as opposed to escalated page / table locks in `SQL Server`). I probably should reformulate it.
Quassnoi
What an excellent answer, good job. You might like to mention the function-based index worarounds for the non-indexing of nulls (eg. index on (MY_NULLABLE_COLUMN,0)). Maybe mention using VARCHAR2 instead of VARCHAR. Maybe it's worth mentioning use of NLS parameters for controlling DATE input and output also.
David Aldridge
Oh, you could mention that Oracle has a procedural language that Does Not Suck.
David Aldridge
And Global Temporary Tables, and not being afraid to use the minimum number of complex queries instead of breaking things out into multiple queries. Statistics also? And commiting only on completion of a logical transaction, not as soon as possible (because of the locking model).
David Aldridge
@David: You're right, but I'm writing only of gotchas here, not benetifs.
Quassnoi
@Quassnoi: this is a geat write up, it deserves to be an article or blog post.@Steve Broberg: Key entries for NULLs are not stored in Oracle indexes, so UNIQUE indexes behave differently. Follow the link in my post and see an example.
AlexKuznetsov
@Quassnoi it is not always clear whether the rownum limitation will be applied to the result set after the access path is determined or before.
Steve Broberg
@Alex, I read your article, and am familiar with this, but how does this change what I said? Whether an index is unique or not does not affect whether it will be used in a query where the column constraint is using a non-null value - the index will be used (assuming statistics are up to date, etc.). The only time it will not be used is if your constraint is "column IS NULL". Again, that is what I demonstrate on our 10.2.0.4.0 system here, using a test table with one column of 131075 rows (131072 unique integers and 3 rows with the value of NULL).
Steve Broberg
@Steve: this was the an example query. Example. Like in "to illustrate". What I wanted to say that this very query (this very query) will not use the index, that's all :)
Quassnoi
+2  A: 

SELECT 1 wont work, do select 1 from dual instead.

If you work with hierarchical data, connect by is great.

uswaretech
A: 

It seems I've run into more Oracle databases being case sensitive to schema objects and data than in SQL Server.

Sam
`PostgreSQL` and `MySQL` are too. It's better to say that `SQL Server` is less case-sensitive than other `RDBMS`es :)
Quassnoi
Where is Oracle case sensitive? I've been operating under the assumption it isn't as far as table names and columns go. Is that wrong?
Alan Storm
@Alan: `"MYTABLE"` and `"mytable"` (quotes matter) are different objects. `SELECT * FROM mytable` (without quotes) will select from the former, not from the latter.
Quassnoi
Got it, thanks Quassnoi, I think you just saved me more than one head scratching/banging moment in the future.
Alan Storm
Steve Broberg
A: 

Don't forget to use nvl(column) around any column in a rowset that might be filled entirely with null values. Otherwise, the column will be missing from the rowset.

That's right, missing entirely!

Example:

SELECT nvl(employeeName,'Archie'), nvl(employeeSpouse,'Edith') FROM Employee

This will guarantee you get two columns in your rowset, even if all the values in both are null. You'll just see a bunch of 'Archie' and 'Edith' values. If you don't use nvl(), you could potentially get only one column or neither back. The gotcha-east part of this is that your code can run fine in your development environment, and even pass QA, but when it gets to production, values in the table may change the structure of the results!

So, in short, whenever you are selecting a nullable column, be sure to use nvl().

Chris McCall
@Chris: could you please give an example of what you mean?
Quassnoi
@Chris: Just created table: `CREATE TABLE employee (employeeName VARCHAR2(20), employeeSpouse VARCHAR2(20))`, filled it: `INSERT INTO employee VALUES (NULL, NULL)` and checked: `SELECT * FROM employee`. It returned me one row with two `NULL`'s, as it should.
Quassnoi
Sorry, this is a problem with Oracle's ODBC drivers, not the db itself.
Chris McCall
+1  A: 

There is no group concatenation like in MySQL. If you want a group concatenation aggregate function, you have to write your own. Here is my implementation:

drop type T_GROUP_CONCAT;

create or replace type GROUP_CONCAT_PARAM as object
(
  val varchar2(255),
  separator varchar2(10),
  numToConcat NUMBER,
  MAP MEMBER FUNCTION GROUP_CONCAT_PARAM_ToInt  return VARCHAR2
);

--map function needed for disctinct in select clauses
CREATE OR REPLACE TYPE BODY GROUP_CONCAT_PARAM IS
    MAP MEMBER FUNCTION GROUP_CONCAT_PARAM_ToInt return VARCHAR2 is 
      begin 
        return val; 
      end; 

end;


/

CREATE OR REPLACE TYPE T_GROUP_CONCAT 
AS OBJECT (

runningConcat VARCHAR2(5000),
runningCount NUMBER,

STATIC FUNCTION ODCIAggregateInitialize
  ( actx IN OUT T_GROUP_CONCAT
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateIterate
  ( self  IN OUT T_GROUP_CONCAT,
    val   IN       GROUP_CONCAT_PARAM
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateTerminate
  ( self             IN   T_GROUP_CONCAT,
    returnValue  OUT VARCHAR2,
    flags           IN   NUMBER
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateMerge
  (self  IN OUT T_GROUP_CONCAT,
   ctx2 IN      T_GROUP_CONCAT
  ) RETURN NUMBER

);
/

CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT AS

STATIC FUNCTION ODCIAggregateInitialize
  ( actx IN OUT T_GROUP_CONCAT
  ) RETURN NUMBER IS 
  BEGIN
    IF actx IS NULL THEN
      actx := T_GROUP_CONCAT ('', 0);
    ELSE
      actx.runningConcat := '';
      actx.runningCount := 0;
    END IF;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateIterate
  ( self  IN OUT T_GROUP_CONCAT,
    val   IN     GROUP_CONCAT_PARAM
  ) RETURN NUMBER IS
  BEGIN
    if self.runningCount = 0 then
        self.runningConcat := val.val;
    elsif self.runningCount < val.numToConcat then
        self.runningConcat := self.runningConcat || val.separator || val.val;
    end if;
    self.runningCount := self.runningCount + 1;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateTerminate
  ( self        IN  T_GROUP_CONCAT,
    ReturnValue OUT VARCHAR2,
    flags       IN  NUMBER
  ) RETURN NUMBER IS
  BEGIN
    returnValue := self.runningConcat;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateMerge
  (self IN OUT T_GROUP_CONCAT,
   ctx2 IN     T_GROUP_CONCAT
  ) RETURN NUMBER IS
  BEGIN
    self.runningConcat := self.runningConcat || ',' || ctx2.runningConcat;
    self.runningCount := self.runningCount + ctx2.runningCount;
    RETURN ODCIConst.Success;
  END;

END;
/

CREATE OR REPLACE FUNCTION GROUP_CONCAT
( x GROUP_CONCAT_PARAM
) RETURN VARCHAR2
--PARALLEL_ENABLE
AGGREGATE USING T_GROUP_CONCAT;
/

To use it:

select GROUP_CONCAT(GROUP_CONCAT_PARAM(tbl.someColumn, '|', 2)) from someTable tbl

Rado
I believe it is easier to just use the hierarchical CONNECT BY feature in Oracle to accomplish this. See my answer http://stackoverflow.com/questions/970481/concat-all-column-values-in-sql/984604#984604
Steve Broberg
Or, in `Oracle 10g+`, a `MODEL` clause. See here: http://explainextended.com/2009/04/05/group_concat-in-oracle-10g/
Quassnoi
+1  A: 

I wrote up a few differences here: Think ANSI Standard SQL Is Fully Portable Between Databases? Think Again.

AlexKuznetsov
+1  A: 

One comment: You don't have to create a trigger in order to use sequences, unless you're adamant about replicating the behavior of the Sybase/SQL Server IDENTITY column. I find it more useful to just use the sequence directly in the actual insert statements, e.g.

INSERT
  INTO MyTable
     ( KeyCol
     , Name
     , Value
     )
SELECT Seq_MyTable.NextVal
     , 'some name'
     , 123
  FROM dual;

You don't need to worry about the overhead of trigger execution, and you have the flexibility to deal with inserting rows into the table without having to worry about sequence values being assigned (such as when moving data from schema to another). You can also pre-select values from the sequence for inserting ranges of data and other techniques that the IDENTITY feature either makes difficult or impossible.

Steve Broberg
+1  A: 

+1 for Steve's comment. I think that for people brought up on Oracle it is natural to not use double-quotes around objectr names, and abhorrent to do so. We sometimes forget that others have different practices.

David Aldridge
what makes it worse is the tools export objects with the quotes. So someone new to Oracle sees that and starts using quotes and next thing they are asking why they can't get their data from table MyTable.
David
A: 

Temporary tables

You create and index them like normal tables but every session/transaction only sees its own data. This is different from MS SQL.

Global variables

They are passed by reference. This means that if you pass a global variable to a procedure as a parameter and modify the global variable inside your procedure, the parameter value will also change. Not a very popular method, though.

Triggers

Until very recent versions there was no way to determine a way in which similar triggers will fire. If you really cared which "BEFORE UPDATE FOR EACH ROW" was first you put it all in one trigger.

jva