AUSOUG Conference 2009 Day One

It was great to catch up with former colleagues at the conference today. As I go from site to site I’m gradually collecting more and more former colleagues and it’s great to hear how they’re going and what they’re working on.

Howard Ong gave a good review of Oracle’s new Data Modeller. Sounds like it’s got a lot of promise – but it’s very new and misses some features (e.g. support for functions, procedures with parameters, packages, and DDL change script generation) that I’d expect in an Oracle CASE tool.

Ray Tindall with Penny Cookson showed how a variety of javascript libraries (Apexlib, ExtJS, JQuery) can be integrated into APEX, and what features they provide. Personally I’d prefer to wait until they come out of the box in APEX 4.0, but for now the options available look good.

I was happy to see a good level of interest in Python at my talk after lunch. If you’re here because you want to have a peek at all the slides I skipped, you can view or download it from here [Python_and_Oracle_Fast.pdf].

Unfortunately I had to leave soon after that to pick up my car from the autoelectrician, tomorrow I should be able to stay all day.


Google indexes duplicate pages from my APEX site – problem solved

Problem: when Google indexes my APEX web site, it considers the following URLs to be different pages:

http://www.site.com/apex/f?p=100:1:1234567890::::
http://www.site.com/apex/f?p=100:1:0::::
http://www.site.com/apex/f?p=MYAPP:1:46346346346::::
http://www.site.com/apex/f?p=MYAPP:1:34634634636::::
http://www.site.com/apex/f?p=MYAPP:HOME:46346346346::::
http://www.site.com/apex/f?p=MYAPP:HOME:0::::

Notice how my application with ID 100 has an alias of MYAPP, and page 1 has an alias of HOME; also, more duplicates happen due to the session ID; all these URLs point to pretty much the same content, but Google indexes them all as separate pages.

Google provides two features that help webmasters solve the duplicate page problem.

Solution #1: Parameter Handling – not very useful (for us)

This solution involves telling Google which parameters to ignore when indexing URLs. This doesn’t help us with APEX, because apex only uses one parameter – “p”; if we were to tell Google to ignore the “p” parameter it would consider ALL pages in our site to be identical, which is not correct.

Solution #2: Specify Your Canonical – very useful!

Example:

<head>
<link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish" />
</head>

This works nicely for us – for any page that we want we can tell Google what URL should be the “canonical” or “official” URL for that page. We can use this in our APEX applications in a number of ways. Each has advantages and disadvantages and YMMV, and it depends on how many different kinds of pages you have and whether you want the same canonical form for all pages, or if you want it customised for individual pages.

A. Custom canonical URL for each page.

This option will probably be the most generally useful, since some pages (e.g. multi-row paged results) won’t work so well with a canonical URL, so you’ll want to specify a canonical URL for just some key pages on your site.

To do this, go to the Page editor and edit the Page Attributes, edit the HTML Header and add the following:

<link rel="canonical" href="/apex/f?p=&APP_ID.:&APP_PAGE_ID.:0"/>

  • You can add the full URL instead of a relative one if you want, but note if you do that it must be on the same domain (e.g. if your site is www.mysite.com, you can’t have a canonical URL pointing to myothersite.com). Anyway, Google don’t mind if you use relative URLs here, so that’s what I do.
  • You don’t have to use the &APP_xxx. substitution variables if you don’t want to – e.g. you could specify another application or page entirely if that makes sense for your app.
  • If your application has an alias, you could use that as the canonical URL:

    <link rel="canonical" href="/apex/f?p=&APP_ALIAS.:&APP_PAGE_ID.:0"/>
    Unfortunately, if APEX has a substitution variable for the Page Alias, I don’t know what it is. UPDATE 2017: APEX now provides the substitution variable APP_PAGE_ALIAS as of APEX 5.0.

B. Global canonical URL for all pages in an application.

This option works well if you want all the pages to have the same form of canonical URL. Because we’ll use the &APP_PAGE_ID. substitution variable, it will still correctly give the correct URL for each page in the application.

To do this, go to the Shared Components, and open Themes. Open the theme in use by your application, then find the Page themes. Next to each Page Theme is a number that indicates how many pages use that Page Theme; those are the only ones you need to edit (although there’s nothing stopping you from editing all of them if you wish).

Click the Page Theme name to edit it. In the Header definition, add the canonical link – it must be inserted after the <head> tag, and prior to the </head> tag. For example:

<html lang="&BROWSER_LANGUAGE." xmlns:htmldb="http://htmldb.oracle.com">
<head>
<title>#TITLE#</title>
<link rel="stylesheet" href="#IMAGE_PREFIX#themes/theme_16/theme_V3.css" type="text/css" />
<!--[if IE]><link rel="stylesheet" href="#IMAGE_PREFIX#themes/theme_16/ie.css" type="text/css" /><![endif]-->
<link rel="canonical" href="/apex/f?p=&APP_ALIAS.:&APP_PAGE_ID.:0"/>
#HEAD#
</head>
<body #ONLOAD#>#FORM_OPEN#

Now, it’s important to test your changes thoroughly because many syntax errors you enter will not manifest in any obvious problems when browsing the site. Open your pages and View Source – check that the header section of the HTML includes the correct <link rel="canonical" ...> tag, and ensure that the URL resolves to the same page by copying it out and pasting it into your brower’s address bar.

Once that’s done that’s it! When Google next indexes your site it should honour your canonical URLs and remove duplicate pages from its indexes.


AUSOUG 2009

Finally the AUSOUG 2009 conference program is out!

Looks good – I’m looking forward to a number of sessions (10-11 Nov in Perth), including the new “ODTUG Oracle ACE” stream:

  • Howard OngOracle Designer RIP? – A First Look at Oracle Data Modeler
  • Penny Cookson & Ray Tindall – Developing a Really Cute Application Using Ajax
  • Scott WesleyTrials & Tribulations of an Oracle Forms -> Apex Conversion
  • Connor McDonald SQL-ectric!
  • Tim HallPL/SQL: It’s all in the Presentation

I’ll be speaking again, this time doing an “Intro to Python” for Oracle’rs. If you’ve never played with this wonderful language before I hope you’ll come along. Alternatively, if you’re keen to hear Penny talk about “Best Practices in Apex Development”, or Scott Hollows give his “History of Oracle – the early years”, I hope you’ll tell me all about it afterwards, because my talk is scheduled at the same time.

If you’re attending the Perth conference, say hooroo.


Two questions about stored outlines

Out of a discussion with a colleague at work I had two questions to answer:

  1. Can a stored outline change a query – e.g. the FROM or WHERE clauses?

  2. If a stored outline hints the query to use a function-based index, and a user doesn’t have execute privilege on the function, what happens if the user runs the query?

1. Can a stored outline change a query?

The answer was simple to find – no, stored outlines only comprise hints; if the hint doesn’t make sense for a particular query, the optimiser ignores the hint just as it does if you add the hint directly to the query. For example:


SQL> CONN SYS AS SYSDBA

SQL> ALTER SYSTEM SET USE_STORED_OUTLINES = TRUE;

SQL> GRANT CREATE ANY OUTLINE TO SCOTT;

SQL> CONN SCOTT

SQL> CREATE OUTLINE testoutline1 ON
SELECT * FROM EMP WHERE EMPNO = :1;

SQL> CREATE OUTLINE testoutline2 ON
SELECT * FROM DEPT WHERE DEPTNO = :1;

SQL> CONN SYS AS SYSDBA

–swap the outlines
SQL> UPDATE OUTLN.ol$hints
SET ol_name =
CASE ol_name
WHEN ‘TESTOUTLINE1’ THEN ‘TESTOUTLINE2’
WHEN ‘TESTOUTLINE2’ THEN ‘TESTOUTLINE1’
END
WHERE ol_name IN (‘TESTOUTLINE1′,’TESTOUTLINE2’);

SQL> CONN SCOTT

SQL> SELECT * FROM EMP WHERE EMPNO = :1;

SELECT STATEMENT
TABLE ACCESS BY INDEX ROWID (EMP)
INDEX UNIQUE SCAN (PK_EMP)

2. What if a user doesn’t have execute privilege on a function used in a function-based index, but the stored outline tells them to use the index?

The scenario is where a table has an index on a column and a function. A stored outline could tell the optimiser to use this index, but a particular user doesn’t have execute privilege on the function; will the the optimiser still allow the user to query the table using the index? It should, since you shouldn’t need to execute the function to do a range scan on the first column, ignoring the function-based portion of the index.


SQL> CONN SCOTT

SQL> CREATE TABLE SCOTT.testtable
(c1 NUMBER NOT NULL
,c2 NUMBER NOT NULL
);

SQL> GRANT SELECT ON SCOTT.testtable TO LARRY;

SQL> CREATE OR REPLACE
FUNCTION SCOTT.testfunction (i NUMBER)
RETURN NUMBER DETERMINISTIC IS
BEGIN
RETURN I * 3.14159;
END;

–note: don’t grant execute on testfunction to LARRY

SQL> CREATE INDEX SCOTT.testindex
ON SCOTT.testtable (c1, SCOTT.testfunction(c2));

SQL> CONN LARRY

SQL> SELECT * FROM SCOTT.testtable
WHERE c1 = TO_NUMBER(:1);

SELECT STATEMENT
TABLE ACCESS FULL (TESTTABLE)

SQL> CREATE OUTLINE testoutline3 ON
SELECT * FROM SCOTT.testtable x
WHERE c1 = TO_NUMBER(:1)
AND SCOTT.testfunction(c2) = TO_NUMBER(:2);

SQL> CREATE OUTLINE testoutline4 ON
SELECT * FROM SCOTT.testtable x
WHERE c1 = TO_NUMBER(:1);

SQL> CONN SYS

–swap the outlines
SQL> UPDATE OUTLN.ol$hints
SET ol_name =
CASE ol_name
WHEN ‘TESTOUTLINE3’ THEN ‘TESTOUTLINE4’
WHEN ‘TESTOUTLINE4’ THEN ‘TESTOUTLINE3’
END
WHERE ol_name IN (‘TESTOUTLINE3′,’TESTOUTLINE4’);

SQL> CONN LARRY

SQL> SELECT * FROM SCOTT.testtable
WHERE c1 = TO_NUMBER(:1);

SELECT STATEMENT
TABLE ACCESS BY INDEX ROWID (TESTTABLE)
INDEX RANGE SCAN (TESTINDEX)

The answer is yes – just as Larry could have hinted his query to use the index, and it would have worked just as well (using the non-function-based part of the index), it works just the same if stored outlines are used instead. The use of stored outlines is compatible with indexes on function-based indexes and will not muck around with queries run by users who don’t have all the privileges on the functions used by those indexes.


Create Excel file from SQL query using Python

This will create a file called “emp.xls” with a nicely formatted spreadsheet based on the query specified. The header row is based on the column names, and is frozen. The column widths are set according to the max width of the data.

import cx_Oracle
from table2Excel import write_cursor_to_excel

orcl = cx_Oracle.connect('scott/tiger')
curs = orcl.cursor()
curs.execute("""
  SELECT e.ename "Employee",
         e.job "Job",
         e.hiredate "Hire Date",
         e.sal "Salary",
         e.comm "Commission",
         d.dname "Department",
         (SELECT ename FROM emp WHERE emp.empno = e.mgr) "Manager"
  FROM   emp e, dept d
  WHERE  e.deptno = d.deptno
""")
write_cursor_to_excel(curs, 'emp.xls', 'Employees')

You can get the source for table2Excel from here [table2Excel.py.txt]. Feel free to adapt it to your needs.



Python: it’s been done before

I’ve learned that with Python, if something takes more than a few lines to write, there’s a very good chance that there’s another way to do it but with one or two lines of code. Chances are someone else has already come across a similar problem, and written a module to solve it – and it’s just a matter of importing their module and reusing it.

The other day I blogged about some functions that query a database and return a list or dictionary. Today I was browsing further in the python docs and learned a much simpler way of achieving the same effect, using named tuples:

>>> from collections import namedtuple

>>> def tableTuples(source, connection, name = None):
        curs = connection.cursor()
        curs.execute('SELECT * FROM (%s)' % (source))
        query_fields = [desc[0] for desc in curs.description]
        Row = namedtuple(name or source, query_fields)
        return [Row(*row) for row in curs]

>>> emp = tableTuples('EMP', orcl)

>>> emp[0]
EMP(EMPNO=7369, ENAME='SMITH', JOB='CLERK', MGR=7902,
HIREDATE=datetime.datetime(1980, 12, 17, 0, 0),
SAL=800.0, COMM=None, DEPTNO=20)

>>> emp[1].ENAME
'ALLEN'

One limitation is that the query must provide names for each column (easy enough with aliases). This limitation is surmountable in Python 2.7 and 3.1, wherein namedtuples support a rename attribute.


Empythoning Oracle

Quite a few months ago I read Wrapping Your Brain Around Oracle + Python and was somewhat interested but too busy to look into it in depth.

Recently I’ve had more free time than I’d like, and wanted to make the most of it by learning a new language. As they say, “Master a few [programming languages]”. I thought about a few languages, remembering several articles that cast Python in a good light. In the end it was a choice between C# on .Net, which seems to be quite popular around this area (at least if job ads are anything to go by), or Python. Python won.

I must say I am very happy with this choice. I love this little language! It has everything, plus much more, and then some. Object-oriented programming, functional programming, multi-threaded programming, list processing, you name it, it’s built in or just an “import x” away. I’ve learned a new concept – duck typing.

Monday – day one.
From www.python.org, downloaded and installed Python 2.6.2 for Windows, and cx_Oracle 5.0.2 since I had no intention of wasting time with Python unless it integrated well with Oracle. Followed the aforementioned article by Catherine Devlin line by line, and was happy to find that everything worked exactly as stated, saw a lot of promise in terms of integration with Oracle, and enjoyed it too.

Tuesday – day two.
Started going through the Python Tutorial. Every few minutes I’d come across some little feature or quirk that just delighted. Started raving about it to whoever happened to come within shouting distance of my cubicle.

Wednesday – day three.
Finished the tutorial. Started browsing through the Standard Library, and got sidetracked down a lot of alleys, trying different ideas out. Got a basic HTTP server running with a few lines of code. Heh.

Thursday – day four.
Decided I need to get a better handle on the various list data types and their syntax. Got a better handle. Also, accidentally flooded one of the CPUs and a whole lot of memory when I queried a table full of large BLOBs, and then multiplied it by some integer (100, from memory)… just to see what would happen.

Friday – day five.
A lot of playing around with Oracle integration. Wrote two functions, tableList and tableDict. I don’t know if these are useful or not, but I learned a lot by writing them.

tableList accepts a table/view name or SQL query and a connection to an Oracle database, and returns a list, each entry is a row, represented as a Python “dictionary”. e.g.

>>> import cx_Oracle
>>> import mystuff
>>> orcl = cx_Oracle.connect('scott/tiger@orcl')
>>> dept = mystuff.tableList('DEPT', orcl)
>>> for row in dept: print row
{'DNAME': 'ACCOUNTING', 'LOC': 'NEW YORK', 'DEPTNO': 10}
{'DNAME': 'RESEARCH', 'LOC': 'DALLAS', 'DEPTNO': 20}
{'DNAME': 'SALES', 'LOC': 'CHICAGO', 'DEPTNO': 30}
{'DNAME': 'OPERATIONS', 'LOC': 'BOSTON', 'DEPTNO': 40}
>>> print dept[0]['LOC']
NEW YORK

tableDict is similar to tableList except that it returns a dictionary instead of a list. It assumes that the first column is a unique identifier, and uses it as the index into the dictionary. Each entry is also a dictionary, each of which represents a row (as for tableList). e.g.:

>>> dept = mystuff.tableDict('DEPT', orcl)
>>> for key, row in dept.iteritems(): print key, '=', row
40 = {'DNAME': 'OPERATIONS', 'LOC': 'BOSTON', 'DEPTNO': 40}
10 = {'DNAME': 'ACCOUNTING', 'LOC': 'NEW YORK', 'DEPTNO': 10}
20 = {'DNAME': 'RESEARCH', 'LOC': 'DALLAS', 'DEPTNO': 20}
30 = {'DNAME': 'SALES', 'LOC': 'CHICAGO', 'DEPTNO': 30}
>>> print dept[20]
{'DNAME': 'RESEARCH', 'LOC': 'DALLAS', 'DEPTNO': 20}
>>> print dept[20]['LOC']
DALLAS

For the morbidly curious, here’s the source code [mystuff.py] (WARNING: this is example code for fun, it does not protect against SQL injection!).

Fun, isn’t it? What I find especially great about Python with cx_Oracle is that good habits like using bind variables and bulk binding are easy and intuitive.

Saturday – day six.
Got the monthly email from the missions team at church, with a list of all the “prayer requests” in a roughly consistent format that I need to upload to the church’s web site (some time ago I tried to give them a web-based data entry screen to enter them in, without success). I think, “hey, this is a perfect candidate for automation using Python.” – in the past I’ve formatted it into INSERT statements by hand, which takes about half an hour or so. A few hours later and I’ve written a short script (81 lines*) that does the job very nicely. That’ll save me a bit of time every month from now on, plus I learned even more about Python in the process. Could I have done this with PL/SQL? Yes, but the code would have been quite a bit more complex – which is why I’ve never bothered to automate it before.

* not including comments, doc strings and error-checking code

Sunday – day seven.
Day of rest.

Now, for some things that worry me a bit about this language:

  1. Class members are all public. My instinct is to want to hide all the details of my classes so that all callers are forced to only use my public interface. In Python it’s all to easy to bypass the “standard” API and access anything at all.
  2. Member attributes and methods can be added, modified or removed. Any time, anywhere. This is even worse than point #1, I think – I might write a class called Employee, which exposes a function called promote(). This function might implement some checks and balances, but they can just be overridden by the caller – by simply writing a new function, myBadPromote(), then simply Employee.promote = myBadPromote! Sure, a good dev team will have standards, but I’m just not sure what benefit this particular feature has. It has a “GOTO” smell about it.

At this early stage I’ll assume that I don’t understand it all well enough yet, I have a feeling that I’ll eventually learn these are not problems at all, but make prefect sense for this language. Maybe.

Next Steps: Try out Build a Rapid Web Development Environment for Python Server Pages and Oracle, and read How to Write a Spelling Corrector until I understand it perfectly.



How to tell if someone’s a programmer

“A woman asks her husband, a programmer, to go shopping:
– Dear, please, go to the nearby grocery store to buy some bread. If they have eggs, buy 6.
– O.K., hun.
Twenty minutes later the husband comes back bringing 6 loaves of bread. His wife is flabbergasted:
– Dear, why on earth did you buy 6 loaves of bread?
– They had eggs.”

Source (deleted)