Wednesday, April 25, 2007

Ask a stupid question...

From time to time we get people in the forums seeking advice about really bad notions. Like this chap yesterday, who wants to turn an Oracle database into a mainframe-style IDMS emulator. The title of the thread captures the full horror: "How to block readers in Oracle?" Yes, they want to disable Oracle's multi-user concurrency model to prevent people issuing SELECT statements against rows which are locked by DML from other sessions. A similar recurring nightmare is the utterly generic data model. Another is changing the physical order of columns in a table.

It's hard to know how to deal with such questions. It's easy enough to just scream No! No! No! but that's not very helpful. We can explain why it's a bad idea, and sometimes that's enough for the poster to gain enlightenment. But often it happens that the questioner is being driven into a bad implementation by daft user requirements. Now the appropriate tactic in this scenario is to advise the OP to go back to their users and explain why the requirement is a bad idea. Unfortunately it can be a tough thing to do, especially if the user outranks you and/or pays your salary. Of course, we know that IT practitioners are brave, noble and wise (not to mention good looking) but many people outside the IT department regard us as servitors and trolls, employed to do their bidding without question.

So. Should we help people implement a bad solution because they don't have any choice? Or should we refuse to help them because what they are being asked to do something stupid? As people answering questions in the forums we have a moral obligation to be intelligent. But do we have us the right to judge others for failing to meet their obligation?

In the case of the would-be IDMS implementer I chose to post a partial solution, with a view to demonstrating the probable shonkiness of the actual application. Ideally this, together with warnings from me and other Forum regulars, ought to have persuaded the OP to reconsider their approach. Instead they come back with an even shonkier implementation:
"We are planning to create a new table that will have tablename,primary key column value and flag .User who issues FOR UPDATE statement should insert data and flag as 'Y'. At the end this record should be deleted .

Any user before trying to read the record should check for entry of this record in that table .If found , then we will keep him on hold by executing a procedure in loop until record not found."

And that's when I knew I should have turned them over to the Oracle WTF Police in the first place.

Friday, April 20, 2007

AppForge: No news is, er, no news

I confess that I had never heard of AppForge when I posted about it last week. Nevertheless by dint of being one of only two recent things on the topic (the other was Wikipedia) my article has become a focal point for beleagured AppForge customers. Its comments section is now a useful resource of links of interest to these poor folks. Obviously, I can take neither credit nor responsibility for this: it is just one of those web happenings.

According to The Register, I guess picking up an earlier article by MarketWatch, Oracle are now confirming that they have bought AppForge's "intellectual property assets". At the time of blogging there is still nothing on the Oracle site and, crucially for AppForge licencees, no information regarding any future support for AppForge products.

Friday, April 13, 2007

UKOUG 2007: Call for papers

The UKOUG Annual Conference web site is now open for business. If your paper is accepted you'll get a free pass to possibly the best Oracle user conference in the world. Okay it's in Birmingham, England and not Amsterdam or Florida but you'll be in lecture theatres all day anyway, so what the heck.

For those who haven't presented before, the UKOUG does offer training for neophytes. And, of course, you can always ease yourself into it by talking at a SIG. For instance the Development Engineering SIG has meetings coming up in June and October.

Oracle buys AppForge? Rumour: ON

Here's an interesting thing. Somebody pops up on the OTN Forums wanting to know about AppForge. Because the AppForge web site is now being re-directed to Oracle. Has Oracle bought AppForge, they ask?

The Oracle search engine draws a blank on "appforge". The Google cache of the AppForge home page has been invalidated. On the wider net there doesn't seem to be much news about AppForge apart from this recent story on The Register which suggests AppForge is in financial trouble.

So has Larry been shopping again? AppForge is some kind of programming kit for "mobile application development using C#, VB.NET or Visual Basic 6", which doesn't seem like a good fit with Oracle's current tool set. On the other hand, Oracle have been doing things in the mobile apps space for some time now. Maybe they need more .Net apps to achieve greater market penetration.

Tuesday, April 03, 2007

UKOUG 2007: The first signs

I have just been doing some testing of the website for the UKOUG 2007 Conference. I found one fairly serious bug, so the exercise was worthwhile.

There's a couple of noteworthy things about the website. Firstly, like the main UKOUG site, it is written in ASP. Of course there are lots of Oracle applications with VB (or VB.Net) front ends to the database but it is still surprising that an Oracle user group doesn't use Oracle tools for its own website. I haven't dared ask the folks in the back office which database supports the site; I might not like the answer.

The other thing is that conference theme is Many faces, one voice. The echoes of the Borg are worrying enough but the conference banner has a distressingly Pink Floydian tinge. Obviously this year we will be putting the prog into "applications programming". I look forward to Ronan Miles treating us to some nifty lead breaks on his electric guitar. Just remember: there is no data side of the app, it's all data.

Thursday, March 15, 2007

Ratios redux

I have got myself embroiled with Don Burleson in another OTN forum thread on cache hit ratios. In it he asks me
'Surely, you don;t(sic) mean that you would dismiss something because it's "not always relevant"?

If it applied on only 5% of the cases, it's still useful then, right?'


I think we can quibble for ever over the usefulness of a metric that is not applicable in 95% of cases. Of course we can use our skill and judgement to evaluate the importance of a low cache hit ratio is meaningful. But how much investigation is required to determine whether it matters? Is that the best use of our time? In most tuning situations I come across these days it doesn't seem relevant. YMMV (Don's obviously does).

Funnily enough one of the other threads I mentioned in my previous article demonstrated a situation where a low cache hit ratio was indeed an indicator of a bad situation. When the OP finally posted the relevant init.ora parameters we discovered

buffer cache size = 32 mb
shared pool = 76 mb

As Laurent Schneider observed, that's barely enough memory for a pocket calculator.

Wednesday, March 14, 2007

UKOUG Super SIG 13-MAR-2007: the lowdown

And so the sun has set on another UKOUG Combined SIG. This one was something of a landmark for me because it was the first one where the Development Engineering stream had the most registrations. So we turfed the Modelling, Analysis and Development SIG out of the biggest room and they got the one next to the kitchens. The large registration was partly down to my attempt to woo our core constituency with sessions on Oracle Forms and client/server applications, and partly due to the presence of Dr Timothy S Hall, Oracle ACE of the year.

The presentations


Grant Ronald kicked off proceedings with a session on building client/server applications with Java Swing and ADF. Basically Grant whipped through several basic Forms type features showing us how to build them using JDeveloper and Oracle's ADF Swing implementation. Clearly Oracle's decision a few years back to put some Forms-style 4GL into their Java offering is bearing fruit: each time I see a JDeveloper demo it looks better and better. I'm still glad I don't build front-end apps any more but I wish JDev had been even half as good when I had to use it back in the day. It's still not true to say that we can build Forms-style desktop apps without knowing any Java code. Nevertheless JDev looks like the best bet for leveraging old Forms developers' experience to the building Java apps.

Danny Roach from Oracle gave a presentation based on his graduate thesis project, a prototype for an internet-based cheque system. This was largely theoretical - academic research and assumptions formed the basis of the business model rather than actual input from major banking institutions. Nevertheless Danny provided some interesting insights. Firstly he showed how easy it could be to stitch together complex systems out of simple web service components. Secondly Danny explained how the banks and their customers currently handle electronic credit/debit payments. For instance, the banks guarantee any payment made with chip and pin, provided it goes through the full system. Supermarkets often don't use this system online, because it takes too long; instead they queue the transactions for asynchronous processing, taking the risk of fraud on themselves in exchange for faster throughput. Danny's prototype is a neat solution to the age old problem to the conundrum of paying a cheque into a bank account when we have to work but the banks are only open during regular work hours. The problem is getting enough of the banks signed up to make it attractive to potential users.

After all that modern .Net and SOAP stuff it was a relief to get down to some proper programming: PL/SQL. Last year I persuaded Rob Baillie to give his first presentation. This year I plucked Oracle-Base's Tim Hall from the ranks of the blogosphere as my victim. Tim claimed to be nervous about this beforehand, although he had seemed chuffed to do mini-talks at Open World last year (which is why I approached him). On the day Tim was fine. He knew his stuff, he was largely fluent and he was funny too. A natural presenter in fact. Of course, as people who have met Tim will know, he really just likes talking. I asked Tim to expound on Tuning PL/SQL because it is an area which doesn't receive as much exposure as it deserves. Tuning is a topic which usually gets directed at DBAs but it is just as important for developers to understand. Tim spent most of his time talking about the tools for diagnosing PL/SQL problems, both high level (baselines, Statspack and user complaints) and low level (home brewed instrumentation, DBMS_PROFILER, DBMS_DEBUG). Then he whizzed through a number of different optimisations which might fix our problems. Tim had the largest audience of the day (at least in the development stream).

After lunch Grant Ronald went though the Forms messages again:
  1. Oracle still has a long term commitment to Forms.
  2. Once an application is migrated to a web platform it can be integrated with other apps written in different technologies.
  3. Now is a good time to start thinking about Forms modules as components in service-based architectures.
I'm afraid it is difficult for us DE SIG regulars not to feel a bit jaded by this. So Grant proposed that Peter Sechser from PITSS, an Oracle partner, give a quick overview of their product. PITSS.CON is a repository-based tool which uses the API built into Forms to automate the management and upgrade of the entire lifecycle of Forms applications. It offers, auditing, documentation, version control, impact analysis, reverse engineering of the data model, etc. Of particular interest is its potential to automate most if not all of any changes required by an upgrade.

Networking session


Then it was time for the three streams to join for the last session, which was a networking exercise. Most of the delegates took the opportunity to slope off early, and given the state of the M4 in the morning I don't altogether blame them. I ran this session with teh remaining few split into teams around tables. The objective was for each team to come up with a ten word sentence. The scenario: you get into the lift at Oracle's City office and you are surprised to see that the only other person in the lift is Larry Ellison. You can see from the button he's pressed that Larry is getting off at the next floor, so you only have time to say one thing to him.What do you say?

The resulting sentences are quite interesting but it is the process that's most revealing. Jeremy Duggan's table seemed to have the most fun, generating lots of ideas, although the suggestion they finnally agreed on was rather oblique. One group plumped for a (non-rhetorical) question, which rather missed the point that Larry was getting off at the next floor. The team with a representative from Oracle Consulting needed extra time because they couldn't come up with anything within the allotted ten minutes and the team with a representative from Oracle's product division overshot their ten word budget by 20%. It would be unfair to draw any conclusions from this.

Here is the final list of sentences:

  • I'm really busy now ... call me in 6 months.
  • Don't forget to guide the customers who put us where we are now.
  • All this Fusion is confusing - will you buy my company?
  • Please de-jargon Oracle - let's be simple.
  • What do you REALLY think about open source?

These were written on giant post-its which were stuck to the walls and everybody voted for their favourite by standing next to it. Unfortunately I forgot to do my Mike Reid "Runaround!" impression. The winner was "All this Fusion is confusing - will you buy my company?" which suggests a hitherto unsuspected desire amongst UKOUG members to become Oracle employees. But all had done well and all deserved prizes. So it was fortunate that the UKOUG had laid on a free bar.

The need for cloning


Cracking though the DE line-up was I must confess that the presentation I would most have liked to hear was one I had to miss because it was in the MAD stream: Oracle's Rob Squire talking about temporal databases. Now I know Chris Date and Hugh Darwen have been doing some theoretical work into this sphere but I am interested to find out what the vendors think of its practicality. Rob's presentation was by all accounts very interesting. Mostly it consisted of demonstration through SQL scripts, some of which were hidden because they contained proprietary ideas. I guess this was a reprise of the session Rob gave at the Temporal Database seminar. Apparently Rob is going to be presenting to senior VPs at Redwood Shores soon. It would require changes to the kernel so it probably take a couple of generations for any implementation to come through. But when/if does happen I expect it will Oracle 13t.

Call for papers


Well that was yesterday and now it's time to start planning for the next one. It's in June, at the Oracle office in Blythe Valley Park (near Salford). So if any of you would like to experience what Tim calls fun then please contact Julius at the UKOUG office. Just ignore the fact that Tim's idea of fun includes sparring with a 3rd Dan Karateka....

Tuesday, March 06, 2007

Rationalising ratios

Over the last couple of weeks I have participated in some threads in the OTN DB General forum which converge on a pattern. A neophyte starts a thread asking for assistance in understanding buffer cache hit ratios. I join the thread with the suggestion that there are better, more useful ways of tuning the database. The irrepressible Don Burleson weighs in with a defence of ratios and the thread turns into a exercise in Hegelian dialectics as the inestimable Mark Powell provides some contextualisation.

Thesis (me): ratios are not particularly helpful for database tuning
Antithesis (DB): ratios must be useful because Oracle still include them in Statspack and AWR
Synthesis (MP): ratios cannot be used in isolation and at best can only support more detailed diagnostic practices.

It happens here and here, and it may yet happen here.

I usually point people at Connor McDonald's helpful script which allows us to "tune" the SGA by generating enough useless activity to set the buffer cache hit ratio to as high a value as we could wish for. But I have recently discovered that Mogens Nørgaard has published an article he wrote for the UKOUG Oracle Scene magazine in 2005. In the third section of this article he compares and contrasts several different approaches to tuning, including his own MOANS strategy. This requires us to tune a process simply by focusing on the SQL statement which takes the longest chunk of elapsed time.

I recently undertook a tuning exercise which used the MOANS approach. We have a background process which runs a couple of thousand times a day. When we first deployed it the process ran in about six seconds; now the average time was approaching seventy-five seconds. A query against V$SQLAREA quickly identified the most expensive SQL statement by any number of criteria: ELAPSED_TIME, CPU_TIME, DISK_READS. It was a select statement consisting of a three table join which matched a single row in an intersection table to single rows in table #1 and table #2.

An explain plan revealed that the query was executing full table scans against both the intersection table and table #2. This was bad news as the intersection table was the largest table in the schema, with over 4.5 million rows and growing all the time. Inadequate indexing looked to be the reason. I built a composite index on the intersection and the query improved quite a bit. But it was still doing a full table scan on table #2. As it happens, this set up still uses the Rule-Based Optimizer (for sound but irrelevant reasons). The order of the tables in the FROM clause seem to be causing the query to drive off table #2 when it ought to have been driving off the intersection table. A quick re-jigging corrected this...except that now the query was doing a full table scan of the intersection table again. Grrrr. This was fixed by re-ordering the predicates in the WHERE clause.

At this point we had a process which ran in less than seven seconds. This was good enough, so according to the MOANS best practice we stopped tuning. At some point we will need to tweak this process further and it has other statements in the process which look susceptible to MOANS-style tuning. The point is, this approach was focused on identifying the statement which took the longest time to ruin and fixing that. Ratios didn't feature.

Tuesday, February 13, 2007

UKOUG DE SIG 13-MAR-2007 Announcement

Well it's four weeks until the first Development Engineering SIG of 2007. For the third year running we're doing a mini-conference with the App Server and Modelling & Design SIGs. There are three streams, one for each SIG and delegates can mix'n'match from any of the three streams.

The agenda for the DE SIG has been published for a while. Once again I have striven to cover new technologies and old technologies whilst still coming up with an offering which will justify a whole day out of the office. I hope I have succeeded.

First out of the stalls is Oracle's Grant Ronald talking about "Building Client/Server applications with Java Swing and ADF components". Everybody tends to associate Java with the web so this will be a useful reminder that it is possible to built client/server systems in Java. After Grant we have another Oracle employee, Danny Roach, who will be presenting a case study on
Internet banking. Danny will be talking about a system built using .Net, which is the first talk we have had on that product.

In the much coveted "between you and your lunch" slot is Tim Hall of UPS (although I suspect more people will know him from his Oracle-Base web site). Tim is supposed to be talking about the tools and processes available for tuning PL/SQL programs. Although there's always the chance he will spend the forty-five minutes on kung fu movies and the evils of pipe smoking. After lunch is Grant Ronald again, possibly sporting a fake moustache and a funny accent (make your own joke). Grant will be co-presenting with Peter Sechser from PITSS and they'll be talking about migrating Oracle Forms from client/server to the web.

The final session will be a joint session across all three streams. We SIG chairs are keeping the precise details of this under wraps. Does this mean we don't yet know quite what's going to happen here? You might think that, I couldn't possibly comment. After that there will be drinks in the bar.

The venue is Baylis House in Slough, which is a nice venue and isn't too bad for transport links. The food generally gets good marks in the feedback. Because it's a joint SIG each member organisation can send three delegates for free. Many people think of the UKOUG as primarily about the annual conference, which is the big event but the SIGs are a valuable learning resource too. My UKOUG colleague Neil Jarvis recently wrote about their benefits and his remarks are just as true for developers, designers and PMs as for DBAs. So please come along in large numbers. If your organisation is based in the UK and uses Oracle but isn't a member of the UKOUG, then why the heck isn't it? The membership rates represent very good value.

Wednesday, February 07, 2007

CREATE SCHEMA: a SQL curiosity

Pete Finnegan picked up on my recent piece USER != SCHEMA and linked to his article on the CREATE SCHEMA statement. This was prescient on his part, as I had decided against discussing it statement in that article (for reasons of length). There is nothing wrong with Pete's piece but I thought expanding on CREATE SCHEMA might be helpful, as one of the other people who commented seemed confused about it.

And who wouldn't be? For start, it's not really CREATE SCHEMA, it's CREATE SCHEMA AUTHORIZATION. ....

SQL> create schema c
2 create table t3 (col1 number, col2 number)
3 /
create schema c
*
ERROR at line 1:
ORA-02420: missing schema authorization clause

SQL>

Furthermore, we are not creating a schema, we are adding new objects to a pre-existing schema.

SQL> create schema authorization c
2 create table t3 (col1 number, col2 number)
3 /
create schema authorization c
*
ERROR at line 1:
ORA-02421: missing or invalid schema authorization identifier


SQL>

The schema authorization identifier is invalid because my database does not have a user C. So let's try again with good ol' user A, who already has some objects in his schema.

SQL> select object_type, object_name from user_objects
2 where object_type in ('TABLE', 'VIEW')
3 /
OBJECT_TYPE OBJECT_NAME
------------------ ----------------
TABLE T1
TABLE T2
VIEW V1

3 rows selected.

SQL> create schema authorization a
2 create view v2 as select * from t2
3 /

Schema created.

SQL>

A misleading response there: the schema already existed. But I suppose "Schema authorization applied" is a bit of a mouthful.

The cool thing about CREATE SCHEMA is that we can put together several CREATE statements and run them as a single transaction. So if one of the CREATE statements fails they all fail. It's the closest Oracle gets to being able to rollback DDL statements.

SQL> create schema authorization a
2 create table t3 (col1 number, col2 number)
3 create table t1 (col1 number, col2 number)
4 /
create table t1 (col1 number, col2 number)
*
ERROR at line 3:
ORA-02425: create table failed
ORA-00955: name is already used by an existing object

SQL> desc t3
ERROR:
ORA-04043: object t3 does not exist

SQL>

Unlike with the table statement a CREATE VIEW exception doesn't give out the underlying error when it fails (at least in 9.2.0.6)....

SQL> create schema authorization a
2 create table t3 (col1 number, col2 number)
3 create view v1 as select * from b.t1
4 /
create schema authorization a
*
ERROR at line 1:
ORA-02427: create view failed
SQL>

As it happens we know the view already exists, so let's presume the error is ORA-955. Normally we could work around that with CREATE OR REPLACE VIEW but ...

SQL> create schema authorization a
2 create table t3 (col1 number, col2 number)
3 create or replace view v1 as select * from b.t1
4 /
create or replace view v1 as select * from b.t1
*
ERROR at line 3:
ORA-02422: missing or invalid schema element

SQL>

CREATE SCHEMA is a "contractual obligation" command: Oracle has it because the ANSI standard says it has to be there. In Oracle SQL only three commands are supported: CREATE TABLE, CREATE VIEW and GRANT. (In DB2 the statement also supports creating indexes). It also only supports standard SQL, so there are some proprietary Oracle SQL which will cause the statement to hurl. CREATE SCHEMA is actually very restricted in its scope and consequently is of limited usefulness. I don't think it can replace a proper regression script for doing database deployments.

There is one last gotcha. Although the CREATE statements bundled with the CREATE SCHEMA statement are transactional and appear to be rollback-able, the statement itself is still plain DDL and issues the implicit commit before it executes.

SQL> insert into t1 values (566, 888)
2 /

1 row created.

SQL> create schema authorization a
2 create table t3 (col1 number, col2 number)
3 create view v1 as select * from b.t1
4 /
create schema authorization a
*
ERROR at line 1:
ORA-02427: create view failed

SQL> rollback
2 /

Rollback complete.

SQL> select * from t1
2 /
COL1 COL2
---------- ----------
566 888
1 AAAAAAAAAA

SQL>

Now, the ability to suspend the transactionality of individual DDLs would be quite helpful. My last installation (which was a change to an existing system) required the deployment of two new schemas with over a hundred tables each, plus indexes, several hundred types, procedures, packages and bodies, not to mention a number of changes to existing schemas. It would be nice to be able to rollback such gargantuan deployments if something goes wrong. But, even if the syntax allowed it, CREATE SCHEMA is not appropriate. Putting all that into a CREATE SCHEMA statement would have resulted in a command almost 2MB long. Debug that! The coming 11g Editioning feature (caveat: BETA!) strikes me as a more practical alternative.

So, CREATE SCHEMA looks like the SQL equivalent of the human appendix. It's there but it doesn't really do anything useful.

Friday, February 02, 2007

"Bootstrapping Web2.0": BCS SPA, 31-JAN-2007

According to presenter Adrian Van Emmenis this was "a brief, superficial and opinionated look at web technology past, present and future." In reality it was a splendid broadside against the current state of web development, with a quick scoot through AJAX, RIA, etc tacked on the end. Van did put up a big BETA! sign, presumably to cover any inaccuracies or problems with timing (very Web2.0).

Ninety minutes of splenetics runs the risk of boredom but Van did his best make his rant entertaining. His Powerpoint was lovely to look at (even though it broke all the rules - too many slides, too many words, too much animation). The SPA Players (Van with Immo Huneke) did a skit set in a greengrocers to illustrate the problems that the Back button poses for e-commerce. And he has a lovely turn of phrase, for instance, describing Java Server Pages as "a blizzard of punctuation".

So, according to Van, what is wrong with Web1.0?
  • The REST model is suitable for static pages but inappropriate for dynamic or stateful transactions.
  • Browsers are all different and broken.
  • Developers are confused about when to use GET and POST, when to use buttons and links.
  • CSS is a nightmare; furthermore it is used by non-programmers who teach themselves from books written by non-programmers.
  • Page refresh/back button/browser caching cause problems within transactions, which often result in users being confronted with unhelpful error messages (POSTDATA)
  • Browsers are all different and broken.

There are toolkits available to make lifer easier (PHP, JSP, ASP, Ruby On Rails, .Net, Django, etc, etc) but in Van's analysis they all suffer from one major problem: they try to work with HTML pages, but inserting their own markup. This is a mistake because HTML is not a good programming language.

At last, Web2.0


Of course, we all know that Web2.0 is made of badgers paws but the engine driving it is AJAX. This is just a catchy rebranding of some JavaScript routines and the use of XmlHttpRequest. I hadn't appreciated before, but it is the latter thing which is crucial to the improved user experience, because it allows post calls to communicate with the web server without causing a page refresh. Consequently the page can be altered dynamically without breaking the transaction.

AJAX on its own is (apparently) complicated. So there are lots of AJAX toolkits springing up. These all seem to have their own library and markup language, and some require plug-ins too. The key thing is, AJAX is still basically fiddling with markup. In Van's opinion a more sensible solution would be to have the pages abstracted into a stack of widgets stored on the server. Events should be communicated from the browser to the server, the changes calculated there and the resultant HTML returned to the browser as a delta. I had a spooky feeling as I listened to Van because this is the model for Oracle web-deployed Forms, which is being junked in favour of Java Server Faces with added AJAX sprinkles.

Oracle Forms uses an applet to achieve this marvel. Applets are very Web0.5 and never really took off. There are a number of reasons for this but the biggest hurdle remains the problem of installing a Java browser plug-in, which is harder than the equivalent task for Flash. Partly this was due to the wrangles between Microsoft and Sun, but Oracle didn't help their cause by insisting on the Jiniator plug-in. These proprietary issues show why Oracle, like so many others, are attracted by the promise of open standards.

No discussion of Web2.0 would be complete without a swipe at some of the more vacuous offerings. Van introduced us to Zebo, a site where you can list everything you own and then network with other people who own the same things. Brilliant! Although, for a demonstration of Web2.0's potential to generate revenue out of nothing in its purest form check out this Business2.0 interview with Richard Rosenblatt, the man who sold MySpace to Rupert Murdoch for $$$.

Update


There is a very cool albeit evangelical video about Web2.0 on (where else) YouTube. pdp has posted a perceptive retort to this video on his GNUCitizen blog, stressing the cross-scripting perils of Web2.0.

USER != SCHEMA

Most of us tend to bandy around the terms USER and SCHEMA is if they were synonyms, but in Oracle they are different objects. USER is the account name, SCHEMA is the set of objects owned by that user. True, Oracle creates the SCHEMA object as part of the CREATE USER statement and the SCHEMA has the same name as the USER but it is quite easy to demonstrate that they are different things.

SQL> conn b/b
Connected.
SQL> select * from my_tab
2 /
OBJECT_ID OBJECT_NAME
---------- ------------------------------
80116 BIG_PK
80114 BIG_TABLE
45709 BP
45710 BP_PK

SQL> grant select on my_tab to a
2 /

Grant succeeded.

SQL> conn a/a
Connected.
SQL> select * from my_tab
2 /
OBJECT_ID OBJECT_NAME
---------- ------------------------------
80404 ACTOR
80414 ACTOR_NT
45707 AP
45708 AP_PK
52765 ASSIGNMENT
49747 A_ID
52768 A_OBJTYP

7 rows selected.

SQL> alter session set current_schema=b
2 /

Session altered.

SQL> select * from my_tab
2 /
OBJECT_ID OBJECT_NAME
---------- ------------------------------
80116 BIG_PK
80114 BIG_TABLE
45709 BP
45710 BP_PK

SQL> select username, schemaname
2 from v$session
3 where sid in (select sid from v$mystat)
4 /
USERNAME SCHEMANAME
--------- -----------
A B

SQL>

The important thing to remember about alter session set current_schema is that it only changes the default schema. It does not change the privileges we have on that schema and it does not change the results when we issues queries that depend upon username, for instance against the USER_ views.

SQL> conn u2/u2
Connected.
SQL> select table_name from user_tables
2 /
TABLE_NAME
------------------------------
T1
T2
T3

SQL> select * from t1
2 /
COL1 COL2
---------- ----------
1 BBBBBBBBBB

SQL> grant select on t1 to u1
2 /

Grant succeeded.

SQL> conn u1/u1
Connected.
SQL> select table_name from user_tables
2 /
TABLE_NAME
------------------------------
T1
T2

SQL> select * from t1
2 /
COL1 COL2
---------- ----------
1 AAAAAAAAAA

SQL> alter session set current_schema=U2
2 /

Session altered.

SQL> select * from t1
2 /
COL1 COL2
---------- ----------
1 BBBBBBBBBB

SQL> select * from t2
2 /
select * from t2
*
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> select * from u1.t2
2 /

no rows selected

SQL> select table_name from user_tables
2 /
TABLE_NAME
------------------------------
T1
T2

SQL>

Of course, the confusion stems partly from the fact that there is a one-to-one correspondence between USER and SCHEMA, and a user's schema shares its name. But the fact that people who ought to know better use them interchangeably (me included) doesn't help matters.

Update


If you found this interesting you might also want to read a piece I have published on the CREATE SCHEMA statement.

Monday, January 29, 2007

Dept. Of Greener Grass

In a refreshing change from developers wanting to become DBAs Padraig O'Sullivan is a young man who doesn't want to become stereotyped as a DBA:
"I guess I am only 23 but I don't want to become labelled as a DBA and therefore someone who cant program or develop to save his life."


I think Padraig's attitude is sensible. As Robert A. Heinlein said, "Specialization is for insects." The trick is to find a boss who is happy to allow us to be generalists but pay us the wages of a specialist.

I have one piece of advice for Padraig: most managers looking for somebody to work in an IT role will plump for somebody who actually knows their own age over somebody who can only guess at it.

Thursday, January 25, 2007

Explicit Cursors RIP? Not Quite

Congratulations to Misbah Jalil over at the Oracle Contractors blog who has just heard the news that implicit cursors are more performant than explicit ones. I think rather unfairly he sticks Steven Feuerstein with the blame for the propagation of the idea that explicit cursors were more efficient. In the days of Oracle 7 lots of Oracle tuning books promoted this shibboleth. It just so happens that Oracle PL/SQL Programming was a bestseller.

Steven long ago issued a mea culpa for his bloomer. In fact he uses it now in his presentations as a classic example of why we should never take a guru's word for anything. We ought always to spike a test case to prove that it is true for our situation. Trust but verify.

The title of Misbah's article is "Explicit Cursors are Dead - Long live the Implicit Cursor". Is this true? Should we really never choose to use an explicit cursor rather than an implicit cursor? I can think of two clear cases when we still need explicit cursors. One is when we want to use BULK COLLECT INTO and the other is when we need to use a dynamic Ref Cursor. There is also the WHERE CURRENT OF clause for use with UPDATE and DELETE statements but I don't think it is mentioned in polite society anymore.

A fuzzier case is the use of a cursor attribute rather than trapping a NO_DATA_FOUND exception. I think exceptions ought to be raised for actual exceptions, that is non-standard states. So when I am expecting a query to return zero rows the proper thing is to use an explicit cursor and raise an exception if the cursor %FOUND attribute returns true. I think this is clearer about the intended meaning of the code than using an implicit cursor and a NO_DATA_FOUND handler to handle the expected path. Of course, most of the time NO_DATA_FOUND is the exception and so the implicit cursor is usually the way to go.

In the comments Paul Driver says he still uses explicit cursors to avoid TOO_MANY_ROWS exceptions being raised when the query returns duplicate rows. Personally I think this is a bug: SELECT ... INTO queries really ought to return a single row. If there is some very good reason why the query does return duplicate rows and we really don't care which row we get then our code should make this clear. Which is why Nature gave us the ability to filter by ROWNUM = 1.

So, does anybody out there still use for explicit cursors for regular querying? If so, what benefits do they offer over the performance of implicit cursors?

Online participation survey

O'Reilly are running a short survey into the motivation of people who contribute online information about software and projects (for reasons other than being paid to do so). The factors listed don't include "getting a little ACE symbol whenever I post in the forums" but it's still worth completing the survey. Check it out.

Wednesday, January 24, 2007

Why ctrl+S should always mean "save"

Fadi Hasweh introduces a primer on keyboard remapping in Oracle Forms with the following scenario:

"my development team asked me to disable ctlr+s for saving they don’t want the end user to you use it to save, he must click the save button, they want to make sure that he want to save and its not just an habit."


I must admit I find such user hostile action rather provoking. This action just creates a system which fools its users into thinking they have saved their work when in fact they haven't.

Our users live in a world of applications they use all the time. They bring that experience to the systems we build. If our systems don't work the way the way our users expect (i.e. like all those other apps) they will hate our systems, and, by extension, us. Apart from anything else, disabling certain keyboard input punishes those people who prefer to type rather than drive a mouse. We might as well build an annoying pop-up widget to wag a graphical finger and remind them that they didn't click the magic button.

Besides, what is the danger of a user saving too often? Such behaviour usually indicates previous traumatic experiences (such as major loss of work due to persistent network outages). Or else it might indicate a badly designed system with workflow chunks that are too big and an insufficient number of POST calls. If anything, users tend to save too infrequently rather than the other way round.

Of course, users are often unreasonable in what they expect of in-house applications compared to professional products. Like demanding rich-client levels of interactivity from an HTML interface. But that doesn't mean we developers should go out of our way to patronise, annoy or confuse our users. As the author Spider Robinson once observed, "Me, I have a science fiction writer's conviction that the damn robot is supposed to speak human, not the other way around."

Update


This is just a specific example of a general principle: that users who are familiar with computers are much less likely to read the manual than neophytes. A phenomenom known as The Paradox Of The Active User.

Tuesday, January 23, 2007

Would you ask Wally?

Entertainment Rights have licenced Martin Handford's classic Where's Wally, apparently for use in branding a search engine aimed at pre-teenboys. According to chief executive Mike Heap, in the future the phrase 'I'm going to Wally it" will replace "Google it". I think this is not going to happen. The main reason why will already be apparent to my American readers, who are probably asking "Who's Wally?". For them, the series is called Where's Waldo? And the phrase "I'm going to Waldo it" just refuses to trip off the tongue.

The other problem is that the metaphor is all wrong. Jeeves famously had a solution to every scrape into which Bertie Wooster got himself. Therefore the branding of Ask Jeeves made a lot of sense. Wheareas Wally loses stuff. He always loses his key, and in the first book he loses all his possessions at a rate of one per double-page spread. This doesn't inspire confidence in the ability of a Wally-ified search engine to find anything. Of course, the apt metaphor of the Ask Jeeves brand counted for nothing against the uncluttered usability and freakishly useful results of Google. The Wally engine will need an interface which is not as cluttered as one of Handford's trademark scenes.

Friday, January 12, 2007

Management: The Techie's Dilemma

It's not unusual on the Forums to get a young person asking for career advice. Usually they have got a Comp Sci degree and an OCP or a couple of years experience in development and they want to become a DBA (like this guy). It is rarer for somebody at the, er, maturer end of the spectrum asking for advice. But Peter Scott did just that this week: his employer has offered him the post of managing the company's Oracle applications team. Should he take this job or should he stick with the data warehousing job he currently has?

The advice he got back fell into two categories:
(1) Supporting E-Business Suite sucks (I paraphrase)1, steer well clear of it.
(2) Managing a team will be more boring than being a data warehousing expert.

Here's my take on these arguments.

"E-Business Suite sucks"


Peter pointed out that the job would entail managing the support of applications written in-house as well as (uppercase) Oracle Applications. The underlying message remains the same: supporting systems written by other people is not as much fun as working on our own systems. The key thing is that Peter wouldn't be supporting other people's systems. he would be managing the people who were supporting other people's systems. So the minions have all the aggro and Peter would just have to manage their unhappiness. So it doesn't really matter whether it's just EBS or in-house apps too. The challenges of the post remain the same.

Technical interest


We become techies because we enjoy wrestling with technical issues. It can be tremendously satisfying to track through a system and nail a bug. There is joy to be had in devising an elegant algorithm to implement a gnarly piece of business logic. The hardest part of adjusting to a managerial role will be saying to a team member, "That sounds like a really interesting problem. Let me know when you've solved it." I know I'm not ready to say that (although there are days...) A manager who keeps involving themselves in the low-level details is not only not doing their own job properly but is hindering their staff in the performance of their duties. Which is not to say that management is an inherently boring role. The difference is that the role requires the manager to debug people rather than software or hardware

The sub-text


The thing is, the other reason why many people become techies is they get on better with machines than they do with human beings. Debugging a database performance problem is easy. Run a trace, tkprof the output, look for the heaviest waits, slap on a index. It's altogether more difficult to tune the performance of a person. There are no diagnostic scripts to run, no trace to analyse, no configuration parameters to tweak. Instead you have to take your performance problem down the pub for a lunchtime pint (or two), subtly mine the small talk for the underlying reasons why they are not working to their full potential and figure out what changes you can make. It may be as simple as telling them to buck their ideas up.

The tricky bit is that there is no backup and recovery option. If a manager says something inappropriate or clumsy and their staff member storms out the room in tears or high dudgeon, there is no flashback query to retrieve the situation. Management requires a different set of skills. It requires softer skills, which unfortunately tend to be innate, although it is still possible to work on them, if we have some basic aptitude and the right attitude.

Oh, and management usually requires wearing a suit and a tie, which also grates with many techies.

So why should Peter take the post


As those who have met Peter know he has the requisite people skills, and famously he does own a suit. So what benefits might he get if he should make the jump?

Time. Being a technical expert requires mastery of a lot of low-level details. Because of the nature of our industry, these details keep changing. Furthermore, the number of areas which we have to master is always on the increase. There are only so many hours in the day. Whereas management skills are a lot more transferable. It helps if the manager is generally familiar with the problem space their team works in, but it really isn't essential. And they certainly do not need to keep up with the details. I remember a manager who, having sat through a fifteen minute presentation on Ant, asked, "Isn't this just like make?" Which was really all he needed to know.

Money. We may not agree with the value proposition but it is overwhelmingly the case that managers make more money than the staff they manage.

Career opportunities. In the technical stream there is a limit to the opportunities for career development. There are a few rock stars recognised throughout the world by a single name (Tom, Jonathan, Ritters). They get the travel, the book deals, the glamour, the groupies. For the rest of us there is the daily grind of doing basically the same old thing. Eventually we reach a point at which the current role has lost its spearmint. At the same time we cannot move into different technical space because it would mean taking too big a pay cut (nobody is going to pay data warehouse expert-level wages for a J2EE novice) and we have families to support. The best chance of doing something different is going non-technical.

The standard advice to neophyte developers wanting to become DBAs is that it is easier to manoeuvre oneself into a DBA role with an existing employer than it is to get a new company to take one on without any real actual experience. The same is true for grizzled techies wanting to get into management. Peter has been offered such an opportunity by his current employer. If he is tempted he should take it.

And I'm sure he will still find things to blog about.


1. For the record, I have never worked with Oracle Apps and have no idea whether supporting EBS sucks as a job. I defer to the opinion of those with experience.

Tuesday, January 02, 2007

Yet Another NULL Article

One of the things that seems to puzzle many Oracle newbies who have experience of other programming languages is that Oracle treats a zero length string as NULL. Over the Christmas period the OTN PL/SQL forum featured an entertaining but ultimately futile discussion on the philosophical ramifications of this behaviour. Personally I cannot see why there should be any more meaning in an empty string than in a NULL but many people think otherwise. And if you deeply believe that there is a meaningful distinction between an empty string and NULL then Oracle's indifference must be galling, if not heretical.

Interestingly, some of the people in this camp might be Oracle engineers. As Laurent Schneider has recently pointed out, Oracle have implemented different behaviour for LOBs. A zero length CLOB is not NULL. We could infer from this that, given the opportunity, Oracle might choose to treat empty VARCHAR2 variables this way too. Then again, it may just be an artefact of the LOB implementation. Either way. the upshot is that Oracle is unlikely to change the existing behaviour simply because such a change would probably break too many existing applications.

Of course, every languages have their quirks, especially with something as slippery as NULL. And developers in other programming languages do funny things with NULL, as this posting from the Daily WTF demonstrates.

Bulk fetches and the LIMIT clause gotcha

I have been reading Tim Hall's book on Oracle PL/SQL Tuning (do I know how to have a swinging festive season or what?). I'll write later about the whole book but I noticed a problem in an example in the chapter on bulk binds. It's the sort of thing which is easily missed until it bites you on the backside.

Here is a slightly compacted version of Tim's example...

SQL> create table t262 as select * from all_objects
2 where rownum <= 262;

Table created.

SQL>
SQL> set serveroutput on
SQL>
SQL> declare
2 type r262 is table of t262%rowtype;
3 l_data r262;
4 cursor c_data is
5 select * from t262;
6 begin
7 open c_data;
8 loop
9 fetch c_data bulk collect into l_data limit 100;
10 -- Process contents of collection here
11 dbms_output.put_line('rowcount='||l_data.count());
12 exit when c_data%notfound;
13 end loop;
14 close c_data;
15 end;
16 /
rowcount=100
rowcount=100
rowcount=62

PL/SQL procedure successfully completed.

SQL>

So where is the problem? Well, if we replace Tim's comment with some actual processing and work with a resultset which just happens to be an exact multiple of the LIMIT clause we find this ugliness...

SQL> declare
2 type r262 is table of t262%rowtype;
3 l_data r262;
4 cursor c_data is
5 select * from t262
6 where rownum <= 200;
7 n pls_integer := 0;
8 begin
9 open c_data;
10 loop
11 fetch c_data bulk collect into l_data limit 100;
12 dbms_output.put_line('rowcount='||l_data.count());
13 for i in l_data.first()..l_data.last() loop
14 n := n+1;
15 end loop;
16 exit when c_data%notfound;
17 end loop;
18 dbms_output.put_line('total rows processed ='||n);
19 close c_data;
20 end;
21 /
rowcount=100
rowcount=100
rowcount=0
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 13

SQL>

The temptation is to solve this by moving the %NOTFOUND check ahead of the processing. Unfortunately %NOTFOUND checks whether the FETCH returned the exact number of rows specified by the LIMIT clause, not whether it returned any rows at all ...

SQL> declare
2 type r262 is table of t262%rowtype;
3 l_data r262;
4 cursor c_data is
5 select * from t262;
6 n pls_integer := 0;
7 begin
8 open c_data;
9 loop
10 fetch c_data bulk collect into l_data limit 100;
11 dbms_output.put_line('rowcount='||l_data.count());
12 exit when c_data%notfound;
13 for i in l_data.first()..l_data.last() loop
14 n := n+1;
15 end loop;
16 end loop;
17 dbms_output.put_line('total rows processed ='||n);
18 close c_data;
19 end;
20 /
rowcount=100
rowcount=100
rowcount=62
total rows processed =200

PL/SQL procedure successfully completed.

SQL>

So if we want to process the whole resultset we need to use the collection COUNT() built-in to check whether any rows have been found.

SQL> declare
2 type r1000 is table of t262%rowtype;
3 l_data r1000;
4 cursor c_data is
5 select * from t262;
6 n pls_integer := 0;
7 begin
8 open c_data;
9 loop
10 fetch c_data bulk collect into l_data limit 100;
11 dbms_output.put_line('rowcount='||l_data.count());
12 exit when l_data.count() = 0;
13 for i in l_data.first()..l_data.last() loop
14 n := n+1;
15 end loop;
16 end loop;
17 dbms_output.put_line('total rows processed ='||n);
18 close c_data;
19 end;
20 /
rowcount=100
rowcount=100
rowcount=62
rowcount=0
total rows processed =262

PL/SQL procedure successfully completed.

SQL>

This interaction between %NOTFOUND and the LIMIT clause is not entirely intuitive so it is unfortunate that it is not explained in the documentation. The PL/SQL Developer's Guide gives an example which checks for %NOTFOUND right after the FETCH and so falls into the trap of not processing the result set's loose change. I discovered this behaviour the hard way: investigating a bug which was caused by this precise problem. Did my original unit test fixture use a set of data which returned an exact multiple of the LIMIT clause? Yes it did. Should that data set have contained an irregular number of rows? Of course it should have!

Wednesday, December 27, 2006

Programming with Oracle SQL TYPE constructs, Part 2

In this two-part series I am following up on my UKOUG Annual conference presentation in an attempt to answer a question from Sue Harper on why more people don't program with Oracle's SQL Types. In the previous article I showed how Types still lack some crucial features which expereinced object-oriented programmers would expect. In this article I am going to address the other half of the question: whether PL/SQL programmers don't use Types because they do not understand when they would be appropriate. I hope to show why Types can still be a useful addition to the PL/SQL programmer's arsenal.

TYPE: what is it good for?


Let's look at the places where Types are used:

  • Collections in PL/SQL
  • Message payloads in Advanced Queuing
  • XML DB

These have one thing in common: Types are used to allow programs to handle different kinds of data in a generic fashion. For instance, a collection can be a table of NUMBER or a table of a Complex Data Type. In either case we can manipulate the set of records using standard collection methods such as count(), first(), last(), next(), prior, extend() and delete(). This flexibility has seen programmers to adopt collections in a wide variety of situations: Steven Feuerstein has a whole presentation devoted to nothing but collections. The application of Types in AQ and XML DB similarly provide a framework to handle data as an undifferentiated splodge which we can pass to consuming procedures which understand the data's actual structure.

This gives us a clue as to the kind of application which is suited to a Type application: when we have data with different structures which we want to process in the same general fashion. At the UKOUG conference I discussed my current project. This is a datawarehouse. Source systems provide daily feeds which have to be loaded in to a history schema. Each table has a different structure and so has its own set of DML statements but the whole process is entirely generic. This means that the decision making logic can be stored and executed in a supertype, which calls on the subtypes' overriding implementations to execute specific logic for each individual table. As I discovered at the conference the details of the process are too complex to explain in a presentation. Even in an article like this I don't think it is practical. Instead I am going to use a simpler example which we all understand: customers.

Customers: a worked example


I recently got told off for using contrived examples in my code so I hope the following business scenario doesn't seem too spurious (in real life I would not expect addresses to be stored in quite the way I show here). The business is a mail order company selling something like artists' supplies. It keeps a record of every customer. Some regular customers have accounts; these can either be retail customers (probably professional artists) or wholesalers. The system keeps additional information about its account customers. Wholesale customers may have multiple contact addresses, e.g. for payments and deliveries.

I am not going to build a whole implementation but just enough code to print the shipping address for all the customers. Furthermore, in order to avoid irrelevant complexity I am going to sidestep the whole issue of persistence and just focus on the Types I need to create an API. The first step is to create the Types for addresses and contacts. The ADDRESS Type has a couple of member functions to print the address's components as formatted string.

SQL> create or replace type address as object (
2 line_1 varchar2(100)
3 , line_2 varchar2(100)
4 , post_town varchar2(50)
5 , post_code varchar2(10)
6 , member function formatted_address return varchar2
7 , member function formatted_address
8 ( name in varchar2, fao in varchar := null)
9 return varchar2
10 );
11 /

Type created.

SQL> create or replace type body address as
2 member function formatted_address
3 return varchar2
4 is
5 return_value varchar2(4000);
6 begin
7 return_value := self.line_1;
8 if self.line_2 is not null then
9 return_value := return_value
10 ||chr(10)|| self.line_2;
11 end if;
12 if self.post_town is not null then
13 return_value := return_value
14 ||chr(10)|| self.post_town;
15 end if;
16 if self.post_code is not null then
17 return_value := return_value
18 ||chr(10)|| self.post_code;
19 end if;
20 return return_value;
21 end formatted_address;
22 member function formatted_address
23 ( name in varchar2, fao in varchar := null)
24 return varchar2
25 is
26 return_value varchar2(4000);
27 begin
28 return_value := name||chr(10);
29 if fao is not null then
30 return_value := return_value
31 || 'FAO: '||fao||chr(10);
32 end if;
33 return_value := return_value||self.formatted_address();
34 return return_value;
35 end formatted_address;
36 end;
37 /

Type body created.

SQL>

Wholesale customers may have multiple addresses, which I am representing as a collection. The collection type has methods to retrieve a specific contact address.

SQL> create or replace type contact as object (
2 cntct_code varchar2(10)
3 , name varchar2(50)
4 , cntct_address address
5 );
6 /

Type created.

SQL> create or replace type contact_nt as table of contact
2 /

Type created.

SQL> create or replace type contacts_list as object (
2 contacts contact_nt
3 , member function get_contact_by_code
4 (cntct_type varchar2) return contact
5 , member procedure get_contact_by_code
6 (self in contacts_list, cntct_type in varchar2, addr out address, fao out varchar2)
7 );
8 /

Type created.

SQL> create or replace type body contacts_list as
2 member function get_contact_by_code
3 (cntct_type varchar2) return contact
4 is
5 return_value contact;
6 begin
7 if self.contacts.count() > 0 then
8 for i in self.contacts.first()..self.contacts.last()
9 loop
10 if self.contacts(i).cntct_code = upper(trim(cntct_type)) then
11 return_value := self.contacts(i);
12 exit;
13 end if;
14 end loop;
15 end if;
16 return return_value;
17 end get_contact_by_code;
18 member procedure get_contact_by_code
19 (self in contacts_list, cntct_type in varchar2, addr out address, fao out varchar2)
20 is
21 l_cntct contact;
22 begin
23 l_cntct := self.get_contact_by_code(cntct_type);
24 addr := l_cntct.cntct_address;
25 fao := l_cntct.name;
26 end get_contact_by_code;
27 end ;
28 /

Type body created.

SQL>

Now I create the base type of Customer. This Type is used to represent the non-account customers. I have overloaded the get_mailing_address() function to provide a hook for extending the functionality in the subtypes.

SQL> create or replace type customer as object (
2 id number
3 , name varchar2(50)
4 , legal_address address
5 , final member function get_mailing_address return varchar2
6 , member function get_mailing_address
7 (addr_code in varchar2) return varchar2
8 ) not final;
9 /

Type created.

SQL> create or replace type body customer as
2 final member function get_mailing_address
3 return varchar2
4 is
5 begin
6 return self.legal_address.formatted_address(self.name);
7 end get_mailing_address;
8 member function get_mailing_address
9 (addr_code in varchar2) return varchar2
10 is
11 begin
12 return self.get_mailing_address();
13 end get_mailing_address;
14 end;
15 /

Type body created.

SQL>

To model the account customers I have an Account_Customer Type. It has member functions to get the payment and delivery addresses. This Type is not instantiable, which means that I will have to implement subtypes derived from it.

SQL> create or replace type account_customer under customer (
2 acct_no varchar2(20)
3 , credit_limit number
4 , member function get_billing_address return varchar2
5 , member function get_shipping_address return varchar2
6 , overriding member function get_mailing_address
7 (addr_code in varchar2) return varchar2
8 ) not final not instantiable;
9 /

Type created.

SQL> create or replace type body account_customer as
2 member function get_billing_address return varchar2
3 is
4 begin
5 return null;
6 end get_billing_address;
7 member function get_shipping_address return varchar2
8 is
9 begin
10 return null;
11 end get_shipping_address;
12 overriding member function get_mailing_address
13 (addr_code in varchar2) return varchar2
14 is
15 begin
16 case addr_code
17 when 'SHIPPING' then
18 return self.get_shipping_address();
19 when 'BILLING' then
20 return self.get_billing_address();
21 else
22 return self.get_mailing_address();
23 end case;
24 end get_mailing_address;
25 end;
26 /

Type body created.

SQL>

Now I will create two subtypes of Account_Customer to model retail and wholesale customers. These need to have code to implement the member functions specified in the parent Type.

SQL> create or replace type retail_customer under account_customer(
2 overriding member function get_billing_address return varchar2
3 , overriding member function get_shipping_address return varchar2
4 );
5 /

Type created.

SQL> create or replace type body retail_customer as
2 overriding member function get_billing_address return varchar2
3 is
4 begin
5 return self.get_mailing_address();
6 end get_billing_address;
7 overriding member function get_shipping_address return varchar2
8 is
9 begin
10 return self.get_mailing_address();
11 end get_shipping_address;
12 end;
13 /

Type body created.

SQL> create or replace type wholesale_customer under account_customer (
2 contact_addresses contacts
3 , overriding member function get_billing_address return varchar2
4 , overriding member function get_shipping_address return varchar2
5 , member procedure find_address
6 (self in wholesale_customer, cntct_type in varchar2
, addr out address, fao out varchar2)
7 );
8 /

Type created.

SQL> create or replace type body wholesale_customer
2 as
3 overriding member function get_billing_address
4 return varchar2
5 is
6 l_fao varchar2(50);
7 l_addr address;
8 begin
9 find_address('BILLING', l_addr, l_fao);
10 return l_addr.formatted_address(self.name, l_fao);
11 end get_billing_address;
12 overriding member function get_shipping_address
13 return varchar2
14 is
15 l_fao varchar2(50);
16 l_addr address;
17 begin
18 find_address('SHIPPING', l_addr, l_fao);
19 return l_addr.formatted_address(self.name, l_fao);
20 end get_shipping_address;
21 member procedure find_address
22 (self in wholesale_customer, cntct_type in varchar2, addr out address, fao out varchar2)
23 is
24 begin
25 if self.contact_addresses.contacts.count() = 0 then
26 addr := self.legal_address;
27 fao := null;
28 else
29 contact_addresses.get_contact_by_code(cntct_type, addr, fao);
30 end if;
31 end find_address;
32 end;
33 /

Type body created.

SQL>

In order to avoid having duplicated code in the get_billing_address() and get_shipping_address() functions I have introduced the find_address() procedure to hold the shared code. As discussed in Part 1 I must include this procedure in the Type specification, which clutters the interface but that is just the way it is.

Finally I need to create a nested table type which I can use for holding a collecton of customers.

SQL> create or replace type customer_nt as table of customer
2 /

Type created.

SQL>

The actual procedure to print the delivery addresses is very simple, with a straightforward interface. It takes a list of Customer objects and calls the get_mailing_address() member function to display the address. Because this function was overloaded in the Customer Type the procedure does not need to distinguish betweeen account and non-account customers.

SQL> create or replace procedure print_delivery_addresses (p_list in customer_nt)
2 is
3 begin
4 for k in p_list.first()..p_list.last()
5 loop
6 dbms_output.put_line(p_list(k).get_mailing_address('SHIPPING'));
7 end loop;
8 end print_delivery_addresses;
9 /

Procedure created.

SQL>

To use this procedure I instantiate four customers of three different Types. I pass them as a collection of Customer objects to my procedure.

SQL> set serveroutput on size 1000000
SQL> declare
2 -- sample address objects
3 a1 address :=
4 new address('1 Lister Road', 'Balham', 'London', 'SW12 8MM');
5 a2 address :=
6 new address('34 William St', 'Tooting', 'London', 'SW17 8YY');
7 a3 address :=
8 new address('124 Legal Road', null, 'London', 'NE4 7AA');
9 a4 address :=
10 new address('The Old Warehouse', '23 Museum Street', 'London', 'WC1 8UU');
11 a5 address :=
12 new address('Piggybank House', '86 Bill Row', 'London', 'WC2 8CC');
13 a6 address :=
14 new address('Stapler Mansions', '124 Monet Road', 'London', 'SW4 7GG');
15 c1 contacts_list := new contacts_list(contact_nt());
16 c2 contacts_list := new contacts_list(
17 contact_nt(contact('SHIPPING', 'Reg', a4)
18 , contact('BILLING', 'Raj Pasanda', a5)));
19 -- sample customer objects
20 cus1 customer :=
21 new customer(1111, 'Mr O Knox', a1);
22 rcus1 retail_customer :=
23 new retail_customer(2222, 'Ms Cecilia Foxx', a2, 'A/C 1234P', 1000);
24 wcus1 wholesale_customer :=
25 new wholesale_customer(3333, 'Cornelian '||chr(38)||' Sons', a3
, 'A/C 7890C',10000, c1);
26 wcus2 wholesale_customer :=
27 new wholesale_customer(4444, 'Brushes R Us', a6, 'A/C 9876C',200000, c2);
28 mailing_list customer_nt := new customer_nt (cus1, rcus1, wcus1, wcus2);
29 begin
30 print_delivery_addresses(mailing_list);
31 end;
32 /
Mr O Knox
1 Lister Road
Balham
London
SW12 8MM

Ms Cecilia Foxx
34 William St
Tooting
London
SW17 8YY

Cornelian & Sons
124 Legal Road
London
NE4 7AA

Brushes R Us
FAO: Raj Pasanda
Piggybank House
86 Bill Row
London
WC2 8CC

PL/SQL procedure successfully completed.

SQL>

Note that I edited the DBMS_OUTPUT output for clarity.

How it works as a Type implementation


The procedure print_delivery_addresses issues a call on the method Customer.get_mailing_address(): that is encapsulation. This method is implemented in different fashions in the Customer and Account_Customer Types. The version defined in Account_Customer is the one used by the Reatil_Customer anmd Wholesale_Customer subtypes: that is inheritance. The procedure takes a list of Customer Types but executes the code appropriate for the actual subtype: that is polymorphism.

Conclusion


Assembling this example was an interesting exercise. The starting idea was simple enough but modelling it in objects proved tricky. For instance I am not altogether happy about overloading the get_mailing_address() function in the Customer Type. But the only alternative seemed to be putting an IS OF test in the print_delivery_addresses() procedure to check each Customer object to see whether it is an account customer.

This may seem like a lot of complexity to do something so simple. What needs to be borne in mind is that this complexity is largely overhead. Writing a procedure to print out the billing addresses is another single call. We can submit a list of any or all kinds of Customer objects and be confident that the correct address will be printed. We can extend Customer or AccountCustomer with different implementations of get_mailing_address() and the original procedure will consume them quite happily.

Whether these benefits are sufficient to justify using Types is debatable. Modelling in Types requires a different approach from modelling with packages. Moreover it requires perseverence and flexibility. It is very easy to go off down one route and then find oneself derailed by some limitation of Oracle's Type implementation, or just by some unanticipated aspect of object behaviour. But it is worth having a play around with Types. Because some day you might find yourself designing an application with some complicated generic logic and lots of repetitious specific behaviours and Types may just fit the scenario.

Tuesday, December 19, 2006

What is the point of SQL%NOTFOUND?

A recent post on Oracle Brains reminded me of how the above question used to puzzle me. Then one day I stumbled across the answer in the documentation while I was looking for an answer to some other problem. There are no secrets, only parts of the manual we have yet to read.

%NOTFOUND is one of the four cursor attributes (%FOUND, %ISOPEN and %ROWCOUNT are the others) which give us information about the state of our cursors. The more common situation is to monitor the status of an explicit cursor. In the following example (all examples were run on Oracle 9.2.0.6) I use the %NOTFOUND attribute to determine whether my fetch has returned a row:

SQL> declare
2 cursor cur is
3 select *
4 from emp
5 where ename = 'APC';
6 rec cur%rowtype;
7 begin
8 open cur;
9 fetch cur into rec;
10 if cur%notfound then
11 dbms_output.put_line('There is no employee called APC');
12 end if;
13 close cur;
14 exception
15 when no_data_found then
16 dbms_output.put_line('EXCEPTION: no data found');
17 end;
18 /
There is no employee called APC

PL/SQL procedure successfully completed.

SQL>

%FOUND does the reverse of %NOTFOUND, returning true if the fetch succeeded. %ISOPEN allows us to test whether a cursor is open before we attempt to close it. And %ROWCOUNT gives us a running total of the number of rows fetched so far.

The attributes SQL%NOTFOUND, SQL%FOUND, SQL%ISOPEN and SQL%ROWCOUNT perform exactly the same functions but for implicit cursors. Except that SQL%NOTFOUND is ignored, because the NO_DATA_FOUND exception takes precedence:

SQL> declare
2 rec emp%rowtype;
3 begin
4 select * into rec
5 from emp
6 where ename = 'APC';
7 if sql%notfound then
8 dbms_output.put_line('There is no employee called APC');
9 end if;
10 exception
11 when no_data_found then
12 dbms_output.put_line('EXCEPTION: no data found');
13 end;
14 /
EXCEPTION: no data found

PL/SQL procedure successfully completed.

SQL>

So what is the point of SQL%NOTFOUND? Well, selects are not the only type of statement which open a cursor. DML statements do too. In the following example I use that cursor attribute to see whether my statement updated any rows:

SQL> begin
2 update emp
3 set sal = 10000
4 where ename = 'APC';
5 if sql%notfound then
6 dbms_output.put_line('There is no employee called APC ...');
7 end if;
8 exception
9 when no_data_found then
10 dbms_output.put_line('EXCEPTION: no data found');
11 end;
12 /
There is no employee called APC ...

PL/SQL procedure successfully completed.

SQL>

I could have used if sql%rowcount = 0 to achieve the same effect.

The PL/SQL documentation has more information on these attributes, including some useful advice on the positioning of calls in relation to implicit cursors.

Thursday, December 14, 2006

Why DUAL?

One of my colleagues has just started reading Oracle Insights: Tales From The Oak Table. He is halfway through Dave Ensor's chapter on the history of Oracle. This explains many things - why that ubiquitous file is called afiedt.buf, why Oracle had the world's first commercial RDBMS even though it was based on IBM's System-R, why nobody ever used MTS. One thing it doesn't cover is why the DUAL table is so named.

Now the reason for this appeared in an Oracle Magazine column a few years back. I clicked on my bookmark so I could forward the URL only to get a 404 error. Turns out there's been a recent purge of the Oracle Magazine online archive. I presume this is because of a desire to retire stale and potentially misleading information rather than because OTN is running out of disk space.

Fortunately the Wayback Machine still has a copy of the web page in question. But it is the Internet Archive only works if we know what and when we know what we are looking for. So, s a public service and to keep this information in a Google-isable domain, I reproduce the text here.


The History of Dual
In the November/December issue of Oracle Magazine you recount the history of the company, so I'm wondering if you can tell me how the DUAL table got its name. People I've asked, including seasoned Oracle gurus, have not been able to answer this.
Sean O'Neill

I created the DUAL table as an underlying object in the Oracle Data Dictionary. It was never meant to be seen itself, but instead used inside a view that was expected to be queried. The idea was that you could do a JOIN to the DUAL table and create two rows in the result for every one row in your table. Then, by using GROUP BY, the resulting join could be summarized to show the amount of storage for the DATA extent and for the INDEX extent(s). The name, DUAL, seemed apt for the process of creating a pair of rows from just one.
Chuck Weiss, Oracle

Source: Oracle Magazine "Sendmail column" January 2002

Update


In the Comments Marco Gralike linked to a thread on AskTom. That URL was broken by the migration of AskTom to a new version of ApEx. Here is the new URL for the thread.

Tuesday, December 12, 2006

Title is NULL

Gary Myers has just written about NULL, making the great point that NULL does not mean "unknown". He cites his middle name as proof of this. His parents never gave him one. Hence, a NULL for his middle name does not represent an unknown value because Gary knows his middle name does not exist. But how can we tell that from a SELECT on the CUSTOMER_NAMES table? This is why NULL is such a slippery customer: it is not just the absence of value, it is the absence of meaning too. A NULL in a column is fundamentally uninterpretable.

For instance, a NULL in the column MIDDLE_NAME might represent non-existence, someone like Gary who doesn't have a middle name. Alternatively it might mean inapplicability, because the entity we are recording is an organisation for which the concept of middle names is nonsensical. It might indicate data withheld because the person has an embarrassing middle name and declined to tell us what it is. It might portend cultural incomprehension, when the respondee literally does not understand the concept of "middle name". It might simply be a case of slack input: the operator forgot to ask or forgot to enter the value.

Hugh Darwen, in his UKOUG 2005 presentation, said that this aspect of NULL really bothered Ted Codd and that in his later life Codd devised a scheme for representing all these different flavours of NULL. Apparently Codd had identified twelve or thirteen distinct "meanings" of NULL. A project that surely invites an index finger twirling by one's temple.

The fact that NULL is not just an absence of value but an absence of meaning is one that often escapes people. Once on a project I had the task of editing a whole slew of Forms modules, setting every mandatory Y/N flag to be optional and changing the default value to be NULL instead of N. For the customer it was quite obvious that the default value for the question "Have we received the letter?" should be NULL because the answer is unknowable until the operator actually gets to that page and sets a value. So for them the meaning of NULL was "This question has not been answered". Unfortunately, in practice NULL could also mean "The operator doesn't know whether we have received the letter". Or "The operator skipped this question". Or "The customer has not sent us a letter." Or ... well, you get the drift.

Unfortunately the solution is not as simple as adopting Fabian Pascal's policy of no NULLs in the database. Because it is not just enough to slap a NOT NULL constraint on every column: that path leads to magic values and they are worse than NULLs. We need to rejig our data models to properly handle those legitimate situations for which no value exists. Which ultimately means having a MIDDLE_NAMES table, consisting of keys and a solitary MIDDLE_NAME column. But we also need better joining syntax to handle all those SELECT statements attempting to retrieve data from the MIDDLE_NAMES table. Outer joins are no good: they just re-introduce null values into the result set, so we are no better off. Modern SQL databases - not just Oracle - are not capable of representing all the different nuances of NULL.

Given the current state of SQL I think we have no choice but to live with NULLs. We just need to remember that when we find a NULL in our data it is not just that we don't have a value in this instance; we also don't know why we don't have a value in this instance.

Further reading


'NULL: Nothing to worry about' by Lex de Haan and Jonathan Gennick
'How to Handle Missing Information without Using NULL' by Hugh Darwen

Wednesday, December 06, 2006

Back to the Future of Forms Pt.94

An old chestnut from the UKOUG Development SIG has appeared in a thread on the ODTUG listserv: the Future of Forms. Specifically whether it is worthwhile doing new development work in Oracle Forms. The Tools Statement Of Direction states that extended support for Forms 11g will be available until 2013. There is no statement beyond that, but seven years is a long time in technology.

We do know that by the time Project Fusion has finished in 2013 the whole of the Oracle E-Business Suite will be all Java web stuff, no Forms whatsoever. But the next version of Oracle Applications (12) will still have a hefty slice of Forms in it. Whilst Apps is a consumer of Forms there will be support.

What happens to Forms after Apps no longer uses them is a different matter. Presumably as long as enough people are prepared to keep paying for the licences I guess Oracle will provide support, maybe even enhancements. Which is, I suppose, the real reason for the de-support of client/server Forms.

In the old days Oracle could afford to just licence the Forms IDE, as a way of subsidising the development environment for Oracle Apps. But once Oracle had decided to take the Java open road that stopped being such a big concern. It became more important that Forms customers pay their own way. By requiring Forms users to stump up for Application Server licences Oracle have turned the product into a sustainable revenue stream. Or killed it off. Take your pick.

Update: 19-DEC-2006


I have just noticed that Forms Product Manager Grant Ronald recently wrote on The 2013 Conspiracy.

Friday, December 01, 2006

Repetition

"This is the three Rs:
Repetition Repetition Repetition"

Repetition - The Fall

Bob Baillie's most recent blog entry reminds us of the Pragmatic Programmers' DRY principle: Don't Repeat Yourself. In other words, don't have duplicated code. Repetitious code is harder to maintain. It can also be harder to understand, because it is more verbose. Code shorn of duplication tends to be more modular and so lends itself to reuse.

These are all noble aims. The trick is to identify the duplicated code.

If you're using cut'n'paste you're probably making a design error


It is relatively easy to spot the duplication in a chunk of code like this:

...
x := get_some_value(4);
y := my_array.next();
call_some_routine(x,y,z);
if z = 0 then
x := get_some_value(5);
y := my_array.next();
call_some_routine(x,y,z);
if z = 0 then
x := get_some_value(6);
y := my_array.next();
call_some_routine(x,y,z);
...
end if;
end if;

There has to be a better way to manipulate that array. And those nested IF statements are the Suck! We can extract some of the duplicated code into a common function and call it from inside a loop

function dry (p1 in number, p2 in number) return number
is
n number;
return_value number;
begin
n := get_some_value(p1);
call_some_routine(n,p2,return_value);
return return_value;
end;
...
for i in my_array.first()..my_array.last()
loop
dry(i,my_array(i), z);
if z != 0 then
exit;
end if;
end loop;

Spotting repetitious code like that is fairly easy because the duplication is co-located and the same shape. Repetition can be harder to spot when the problem is code in different PL/SQL packages. Or when the duplication occurs in different shapes. For instance we might write an insert statement which assigns a sequence.nextval to the primary key column when the table owner has already created a before insert trigger to do precisely the same thing.

Digging out repetition


Repetition can arise in two ways: we are writing a new piece of functionality and we find ourselves doing cut'n'paste a lot. Or when related pieces of functionality get written by different people or at different times.

When we find repeating code in existing systems we can decide to leave it be or to remove it. Normally this happens because we are working on the program, during a bug fix or enhancement. So it makes sense to tackle it now by (dread word) refactoring the code. There are a number of different approaches to refactor the code, depending upon the precise form of the repetition. The important thing is to ensure that deduplicated code works exactly the same as the duplicated code. This means we need to have tests - preferably automated ones - which we can run before and after we change the code.

It is preferable to avoid repetitious code in the first case. Easier said than done. It can be hard to write shared code and get it right first time. Carl Manaster has written a very interesting article on a technique he calls Triangulation. The basic principle is:
  1. Write the code for the first case in full, to get the solution right.
  2. Write the code for the second case in full, to identify the common features.
  3. Extract the duplication into a common function and refactor the two cases to use it.

In other words, to avoid repetition we must first introduce it. Otherwise we cannot be sure that our common function correctly and completely captures the duplicated functionality. Carl's article again emphasises the importance of having automated unit tests to demonstrate that removing the repetition has not broken our code. There are just some things we can't repeat often enough.