Thursday, July 12, 2007

Oracle's DECODE funtion...

Oracle's decode function can be used to create arbitrary ORDER BY clauses. This is useful if you are providing lists with the most frequently picked value at the top - e.g. a list of countries which starts with 'United States' and then turns into a conventional alphabetical listing. Your SELECT statement will have two things in the ORDER BY clause - first it sorts by a DECODE that turns your favoured value into '1' and all other values into '2' and then it sorts the rest of the list conventionally. The alternatives to using DECODE are to hard code the entire list (bad!) or add a numeric column to the underlying table that defines the sort order, which is a lot more work than a DECODE in an ORDER BY.


SQL> r



1 select country_name


2 from iso_countries


3* order by decode(country_name,'UNITED STATES',1,2)

  , country_name





COUNTRY_NAME


--------------------------------------------------


UNITED STATES


AFGHANISTAN


ALBANIA


ALGERIA


AMERICAN SAMOA


ANDORRA


ANGOLA


ANGUILLA


ANTARCTICA


ANTIGUA AND BARBUDA


ARGENTINA


...





This behavior is common for web sites where drop down lists have to be dynamic (the contents may change) but also require location or market specific default preferences.





While we're on the subject of lists of countries one really should think twice before including Bouvet Island one's list...

Labels:

Wednesday, July 11, 2007

BLOBs, CLOBs and BFILEs

Summary




Oracle users currently have 3 different Large OBject data types to choose from - BLOB, CLOB and BFILE. We'll look at the basic mechanics of how they work from a Java developers perspective and then explain why oracle.sql.BFILE poses serious challanges for web service developers.





LOB's 101





When you retrieve an oracle.sql.BLOB or oracle.sql.CLOB the single most important thing to understand is that you're dealing with a pointer, not a file. Not only that, but that the oracle.sql.CLOB you just retrieved from the database is totally independent of the SQL statement or PL/SQL procedure that was used to return it to you. What you've actually got is a pointer to a large object inside the database that will continue to exist until your commit, rollback or lose your connection. Once you understand this life will become bearable. There are significant API changes for Oracle's JDBC drivers as you move up through the different versions of Oracle. You will need to re-read the JavaDocs and check your LOB code line by line when you upgrade. Of course, if you used OrindaBuild you could just re-generate your code...



BFILE



oracle.sql.BFILE is different. A BFILE is a pointer to a file that resides outside the database. In fact a BFILE consists of two things:

  1. The name of an Oracle DIRECTORY (as found in the ALL_DIRECTORIES view)
  2. The name of the file within that directory


oracle.sql.BFILE objects are small - often under 100 bytes. Oracle makes no attempt to verify that the file referred to by the oracle.sql.BFILE exists and is usable until you attempt to read from it. The only way to create an oracle.sql.BFILE is to call use the SQL function BFILENAME, which implies a database call. You can quite happily create BFILE objects for files which don't exist. This makes sense when you consider how much work checking every BFILE every time it was referred to would impose on the server, but obviously needs to be understood by the developer.



LOBs and Web Services



If you're going to write a Web Service app and are one of the surprising number of people who are still on 8i you should upgrade to at least 9i. Creating CLOBS and BLOBS is a lot easier because of the createTemporary() method that appears in 9.0.1.Using LOBS in a Web Service environment is not a problem, provided you think carefully about how big the LOBs will actually be and how much memory you'll need - Oracle can support huge CLOBS and BLOBS that simply wouldn't be practical in a Web Service scenario.Creating BFILES and Web Services. If you are writing a web service application that stores LOBs in Oracle you should avoid the use of the BFILE data type. It may well seem very tempting but the JVM which is running the service will need to be on the same filesystem as the Oracle database server, which will severly limit scalability. If, as is normal, your JVM is on a different machine you could in theory get round this by creating a BFILE that points to a non-existent directory/file on the database server and then trying to asynchronously move the file to where the BFILE pointer claims it is before anyone actually tries to read the BFILE. For obvious reasons we don't recommend this.



[This post originally appeared on JRoller]

Sunday, July 1, 2007

Not All Jdbc Advice Is Good JDBC Advice...

While wandering around the web I stumbled across a link to a sample chapter for 'Java Programming with Oracle JDBC', published in December 2001 by O'Reilly. Now 2001 is a bit dated but one would assume that if they are plugging it on their web site it must still be relevent. And that any glaring

errors would have been corrected, right? Think again...

The author spends the chapter conducting elaborate benchmarks which in his view demonstrate that there's nothing special about PreparedStatement
and that you should really be using Statement most of the time. His reasoning is based on the premise that the additional set up costs of Prepareing a statement and sending all the bind variables etc cause applications to run more slowly than if you had simply said something like:

code>


Statement stmt = conn.createStatement();

stmt.executeUpdate(

"insert into oehr.testxxxperf ( id, code, descr,insert_user, insert_date ) "

"values ( " + Integer.toString( i ) + ", '125678901234567890', "

"'1234567890', " +




"USER, to_date('" + sdf.format(new java.util.Date(System.currentTimeMillis()))
+ "', 'YYYYMMDDHH24MISS'))");

instead of:

PreparedStatement stmt = conn.prepareStatement(

"insert into oehr.testxxxperf ( id, code, descr,insert_user,

insert_date ) " +

"values ( ?, ?, ?, ?, ? )");

startTime = System.currentTimeMillis();

int i=1;

stmt.setInt(1,i);

stmt.setString(2,"123456789012345678901234567890");

stmt.setString(3,"123456789019012345678901234567890");

stmt.setString(4,"ZXVI01");

stmt.setDate(5,newjava.sql.Date(System.currentTimeMillis()));

stmt.executeUpdate();




The point made is that unless you plan on issuing hundreds of statements the first method is faster because it has fewer network round trips and therefore less elapsed time. All of which is true - provided there is only 1 copy of the client software being used. In practice what will happen is that using Statement in preference to PreparedStatement will cause the database server to devote significant resources to compiling execution plans for each and every version of the insert statement being called. Indeed, not using PreparedStatement and bind variables is listed as the second most common mistake in Oracle's '
Top Ten Mistakes Found in Oracle Systems'.There's also a less obvious but equally serious problem with cobbling together your SQL statements instead of using bind variables and PreparedStatement - sooner or later someone with an apostrophe in their name will show up and cause a syntax error:



Int empno = 42;

String empname = "O'Reilly";

String myStatement = "INSERT INTO EMP (empno, ename) VALUES ("

+ empno + ",'" + empname + "');";




But wait... it gets worse! If you're working on a public facing web system and some bright spark realizes you are hacking together SQL statements you could have something like this happen:



Int empno = 42;

String ename = "'Smith', sal = 99999";

String myStatement = "UPDATE EMP set ename = "

+ ename+ " WHERE empno = " + empno');";


which means that your Statement would be:


UPDATE EMP set ename = 'Smith', sal = 99999 WHERE empno = 42;

[This post originally appeared on JRoller]

Labels: , ,