mercoledì 6 luglio 2011

To Dual or not to Dual?

Una delle differenze fra PostgreSQL e Oracle e' nel funzionamento sintattico e semantico dell'istruzione SELECT. Agli utenti Oracle potrebbe risultare strano, ma la clausola FROM di una istruzione SELECT in PostgreSQL e' opzionale:

Command:     SELECT
Description: retrieve rows from a table or view
Syntax:
[ WITH [ RECURSIVE ] with_query [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
    * | expression [ [ AS ] output_name ] [, ...]
    [ FROM from_item [, ...] ]
...


Questo significa che, qualora non sia necessario recuperare le tuple con accesso ad una tabella (ad esempio perche' le tuple vengono generate al volo da una stored procedure), la clausola FROM puo' essere omessa.
In altre parole il comando

SELECT 'W PostgreSQL';

fa quello che ci aspetta:

  ?column?  
--------------
 W PostgreSQL
(1 row)


mentre in Oracle l'istruzione

SELECT 'W Oracle';

non funziona poiche' la SELECT in Oracle si aspetta obbligatoriamente la clausola FROM. La soluzione che spesso si trova nei manuali e nella guida ufficiale e' di usare la tabella speciale "dual" nella clausola FROM, ossia l'istruzione funzionante diventa:

SELECT 'W Oracle' FROM dual;

Che cosa e' dual? E' una tabella (come tutte le altre) creata al momento di inizializzazione dell'istanza Oracle, assieme al catalogo di sistema. La tabella contiene esattamente una tupla con una singola colonna (di nome dummy) con valore 'X'. In PostgreSQL si puo' ricreare una tabella dual Oracle-like con i seguenti comandi:

# CREATE TABLE dual( dummy char(1) );
CREATE TABLE
# INSERT INTO dual(dummy) VALUES('X');
INSERT 0 1

Se sull'istanza PostgreSQL ora si esegue la query in stile Oracle-like si ottiene il risultato voluto:

# SELECT 'W Oracle' FROM dual;
 ?column?
----------
 W Oracle
(1 row)


Quindi e' possibile su PostgreSQL simulare il comportamento Oracle creando una tabella dual in modo molto semplice.
E' bene comunque spendere qualche considerazione circa la tabella dual di Oracle. Anzitutto e' bene capire come mai la query con la clausola "FROM dual" funziona su entrambi i sistemi. Il linguaggio SQL prevede che un join senza filtro riporti in uscita il prodotto cartesiano delle tuple delle relazioni coinvolte; questo significa che per ogni record presente in dual (unica relazione coinvolta) viene riportato in uscita un record con i valori selezionati di dual (in questo caso nessuno) e eventuali altri valori (in questo caso la stringa). E' facile verificare questo aggiungendo un record a dual ed eseguendo nuovamente la query:

# INSERT INTO dual(dummy) VALUES('Y');
INSERT 0 1
# SELECT 'W Oracle' FROM dual;
 ?column?
----------
 W Oracle
 W Oracle
(2 rows)


Spiegato quindi il mistero della clausola "FROM dual" si puo' analizzare in dettaglio cosa comporti questo approccio.
Anzitutto l'accesso alla tabella richiede un sequential scan per ogni query. La cosa non impatta sulle prestazioni, poiche' la tabella e' piccola e sicuramente non viene mai paginata, tuttavia il motore di esecuzione deve comunque effettuare l'accesso come per ogni normale tabella.
In secondo luogo qualora il contenuto di dual sia modificato (ad esempio aggiungendo un record) si otterra' che molte query non funzioneranno piu' nel modo voluto. Si pensi ad esempio a query del tipo

SELECT laMiaStoredProcedureDistruttiva() FROM dual;

che vengono eseguite piu' volte a causa di una manomissione di dual.
Analogamente, la rimozione del record da dual produce una query funzionante ma con output nullo. Entrambe le situazioni sono pericolose, ma la seconda (nessun record in dual) e' difficilmente individuabile nel caso di test automatici, poiche' la query di fatto esegue correttamente.

Quale comportamento e' corretto?
Personalmente ritengo che Oracle applichi un concetto di uniformita': tutte le query devono avere una relazione (esistente) nella clausola FROM. PostgreSQL e' piu' flessibile in questo, ammettendo che le tuple potrebbero essere generate al volo anche senza partire da un prodotto cartesiano.
Lato sintattico la sintassi di PostgreSQL e' sicuramente migliore: perche' scrivere sempre "FROM dual" nelle proprie query quando non serve l'output di dual? Lato semantico la metodologia di Oracle e' sicuramente piu' uniforme.
Infine occorre tenere presente che PostgreSQL garantisce che query SELECT senza clausola FROM funzioneranno sempre, anche in presenza di manomissioni allo schema (a patto di non rovinare gli operatori!), Oracle potrebbe mostrare comportamenti differenti fra le istanze a seconda del contenuto di dual.

Nessun commento: