SQL server? No grazie!

SQL server? No, grazie!

Obbligato dalle circostanze ad affrontare un pesante lavoro di conversione di una serie di applicazioni da un database serio - ancorché di livello medio - al suddetto SQL server, non ho trovato di meglio - per sublimare le quantità industriali di stress che tale lavoro ha prodotto in me - che raccogliere qui per iscritto quelle che per me sono le principali critiche a SQL server.

Tutti i lettori sono ovviamente *benvenuti*, ma temo che buona parte del testo risulterà assai poco digeribile a chi non abbia un minimo di dimestichezza con i databases SQL. In altre parole: se sei un SQL-profano la tua ultima possibilità di sopravvivenza è cliccare QUI.

A tutti, comunque, auguro una buona lettura!



FOREIGN KEYS e INTEGRITA' REFERENZIALE

La gestione delle FK (Foreign Keys) e della RI (Referential Integrity, Integrità Referenziale) di SQL server contiene gravi manchevolezze e ancor più gravi limitazioni.

create TABLE A(A_key integer not null IDENTITY PRIMARY KEY);

create TABLE B(B_KEY integer not null IDENTITY PRIMARY KEY,
B_KEY_A1 integer NOT NULL,
B_KEY_A2 integer NOT NULL);

ALTER TABLE B add constraint [fk_b1] FOREIGN KEY ([B_lo_key_A1])
                    REFERENCES A ON UPDATE CASCADE ON DELETE CASCADE;

ALTER TABLE B add constraint [fk_b2] FOREIGN KEY ([B_lo_key_A2])
                    REFERENCES A ON UPDATE CASCADE ON DELETE CASCADE;

I TRIGGERS INSTEAD OF

(ovvero: della mancanza dei TRIGGERS BEFORE)

I triggers instead of svolgono una funzione utile ed interessante (soprattutto in relazione al trattamento delle views), ma non sono in grado di supplire efficacemente, come di fatto si trovano a dover fare, alla *clamorosa* mancanza dei triggers 'before'.

L'uso dei triggers instead of per governare l'insert e l'update di una tabella con molte colonne è estremamente faticoso perché dopo aver effettuato i controlli e le modifiche del caso si è costretti di fatto a riscrivere completamente l'istruzione INSERT o UPDATE, con greve onere di scrittura e, soprattutto, difficoltà di manutenzione e possibilità di errore.

La presenza di un trigger BEFORE -- che tipicamente consente di intervenire esclusivamente sui valori dei campi che si desidera modificare nel trigger lasciando invariati tutti gli altri -- sarebbe *assolutamente* gradita, soprattutto perché solleverebbe il programmatore dallo svolgimento di lavoro lungo, inutile ed inutilmente pericoloso.

Tanto più che, come dicevo prima, i triggers 'instead of' sono incompatibili con la presenza sulla stessa tabella di una FK 'cascade', e la necessità di effettuare un trattamento sul trigger 'instead of' obbliga di fatto il programmatore a trasferire tutte le FKs 'cascade' all'interno del trigger.

ALIAS

Ma (lo so che non si comincia un periodo con una avversativa; MA qui ci vuole!!!) la cosa più incredibilmente assurda che ho trovato in SQL server, quella che più di ogni altra mi ha fatto disperare di poter mai davvero usare SQL server per qualche lavoro decente, è la sua incapacità di riutilizzare gli alias assegnati alle colonne:

Si è deliberatamente obbligati a scrivere codice stupidamente ripetitivo e soprattutto assai pericoloso da manutenere.

Se poi l'alias è definito su un calcolo pesante o addirittura su una funzione (che può essere computazionalmente molto pesante) intervengono anche possibili problemi di performance, dato che il calcolo o la funzione devono essere eseguiti più volte.

Bontà Sua e incommensurabile miracolo: è possibile riutilizzare gli alias nella clausola ORDER BY. Ma allora, anche solo perché gli è venuto in mente, a chi ha scritto SQL server, che possa in una qualunque misura essere utile dare ai poveri forzati di SQL server la possibilità di riutilizzare gli alias, forse che non era il caso di ipotizzare di estendere la funzionalità anche alle altre clausole? (si capisce sufficientemente bene, che la domanda è una domanda retorica?)
Perché se è utile, è utile. E se non è utile, non è utile. E benignamente concedere l' immaginifica possibilità del riutilizzo degli alias sull'ORDER BY non sposta il problema né avanti né indietro, dato che il problema è fermo alla clausola WHERE (che è presente su *TUTTE* le queries vere di questo mondo, ad eccezione di quelle false) e sulla clausola GROUP BY (che è presente su una buonissima percentuale delle più complesse). Ma forse se ne sono dimenticati.

Funzioni definite dall'utente

Solo nella fantasia malata di chi pensa che la perfezione sia di questo mondo (no, adesso non sto parlando né degli hegeliani né dei comunisti) è possibile pensare di escludere dalle funzioni definite dall'utente l'accesso a valori non deterministici e l'accesso e la modifica di tabelle; con il risultato che, ad esempio, in SQL server non è possibile creare la banale funzione TODAY() che restituisce la data corrente (magari formattata secondo una certa modalità di mio gradimento), ma bisogna per forza chiamare qualcosa come convert(varchar, getdate(), 112) che oltre ad essere espressione inutilmente oscura e faticosa è soprattutto molto più difficile da manutenere; né è possibile creare la funzione MY_RANDOM() che restituisce un numero casuale di un certo tipo (ad esempio: in un certo range).
In specifico non capisco:

TABELLE TEMPORANEE [temporary tables]

Nei database serî le temporary tables sono tabelle persistenti (create una sola volta) dotate della caratteristica peculiare di non condividere i dati tra le connessioni: ciascuna connessione vede solo i dati che ha inserito. Al termine di ciascuna connessione i suoi dati caricati sulle temporary tables vengono automaticamente eliminati.
Le local tables sono delle temporary tables non persistenti: vengono create all'interno di una connessione (tipicamente all'interno di una stored procedures) e al termine della connessione vengono eliminate insieme ai dati (non vengono eliminati solo i dati, ma anche la stessa tabella, che cessa di esistere).

SQL server non gestisce le temporary tables. O meglio: lui dice di gestirle, ma quelle che spaccia per tali in effetti sono propriamente le local tables, dato che devono essere create ogni volta che vengono utilizzate. Le local tables sono comodissime per uso strettamente locale: ad esempio per particolari elaborazioni all'interno di stored procedure risulta spesso comodissimo poter usare particolari tabelle di supporto, che possono essere utilmente create sotto la forma di local tables. Le temporary tables trovano invece la loro specifica utilità in contesti tipicamente diffusi (non locali), e servono ad esempio per contenere e trasmettere dati in differenti parti di una applicazione.
In SQL server l'obbligo di verificare ogni volta, in ogni punto del programma in cui serve, di aver già creato la cosiddetta temporary table (e, peggio, di non averla ancora creata pena l'errore di esecuzione) è così pesante da consigliare di fare addirittura a meno delle temporary tables e di usare, come ho scelto di fare io, tabelle normali con un identificatore univoco per ciascuna connessione. Tale pratica, di per sé scomoda anche solo per l'obbligo di garantire l'univocità degli identificatori utilizzati e per il fatto di doversi ricordare, al termine, di cancellare i dati inseriti, tende ad influire negativamente sia sulla concorrenza tra connessioni (come sempre avviene quando si lavora sulla stessa tabella) sia sulle prestazioni del database (poiché le operazioni di inserimento e ricerca non vengono eseguite su piccole tabelle fisicamente separate per ogni connessione come capita con le true temporary tables, ma su una stessa grande tabella dove confluiscono i dati di tutte le connessioni; e - come ricordava Virgilio: divide et impera - lavorare su tabelle grandi è molto più costoso che su tabelle piccole).

COMMENTI

Manca la possibilità di inserire commenti sui vari oggetti che costituiscono il database: tabelle, views, colonne, indici, foreign keys, stored procedures, eccetera. La possibilità di inserire commenti, ancorché poco utilizzata da moltissimi (leggi: troppi) programmatori, è *fondamentale* per una buona manutenzione di databases complessi.

ALTRE FUNZIONI MANCANTI

PARAMETRI STORED PROCEDURES

L'impossibilità di passare una espressione o una funzione come parametro ad una stored procedure ha dell' *incredibile*. Io semplicemente non ci volevo credere. (Se sapete che non è vero e mi spiegate come funziona, vi offro minimo una pizza.) Ogni volta che bisogna passare ad una stored procedure la più semplice delle espressioni bisogna creare una variabile, assegnarle il valore dell'espressione e passare la variabile alla stored procedure. Se serviva un modo altamente efficace per rendere più oscuro, più lungo e più faticoso sia da scrivere che da leggere il codice delle stored procedures, bè, l'hanno trovato.

Sembra di essere ritornati alle asperità sintattiche dei linguaggi di quarant'anni fa, con la piccola differenza che mentre il Fortran e i suoi fratellini vivevano spartanamente in pochi k-bytes di memoria centrale, SQL server si ciba indecorosamente di MIPS e di Giga bytes di ram. (Salvo non sapere come dividere una query troppo complessa tra più processori, obbligando il sistemista a farlo lavorare con un processore per volta) (si, è incredibile, ma mi è capitato anche questo).

CONCATENAZIONE DI STRINGHE

La mancanza di uno specifico operatore di concatenazione di stringhe impedisce una semplice ed efficace distinzione tra somma aritmetica e concatenazione di stringhe. Ad esempio, la seguente banale istruzione 

select 'VALORE = ' + 123

viene rifiutata da SQL server perché SQL server cerca di effettuare la somma aritmetica e trova che la stringa 'VALORE = ' non è un valore numerico.
Se esistesse (come in altri databases) uno specifico operatore per l'operazione 'concatenazione di stringhe' (mettiamo l'operatore || ), l'operazione potrebbe essere eseguita, semplicemente e senza fraintendimenti di sorta, come:

select 'VALORE = ' || 123

SQL server invece obbliga ad effettuare una conversione esplicita

select 'VALORE = ' + cast(123 as varchar(10))

MA SIAMO PAZZI????? Ogni volta che concateno un numero ed una stringa devo stare a fare tutta 'sta manfrina? Non ho mica tempo da perdere, io!

NB: sì, lo so che esiste anche anche la possibilità di usare STR(123), ma la formattazione del numero è *insoddisfacente*.

AMBIGUITA' NON RISOLTE

Consideriamo due semplicissime tabelle così definite e popolate:

CREATE TABLE a (
            A_lo_key int NOT NULL PRIMARY KEY,

           
A_i_qta int);

CREATE TABLE b (
            B_lo_key int NOT NULL IDENTITY PRIMARY KEY,
            B_lo_key_A int,
            B_i_qta int);

INSERT INTO a (A_lo_key, A_i_qta) VALUES (1000, 0);

INSERT INTO b (B_lo_key_A, B_i_qta) VALUES (1000, 1);
INSERT INTO b (B_lo_key_A, B_i_qta) VALUES (1000, 2);
INSERT INTO b (B_lo_key_A, B_i_qta) VALUES (1000, 3);
INSERT INTO b (B_lo_key_A, B_i_qta) VALUES (1000, 4);

Consideriamo inoltre la seguente istruzione:

UPDATE a SET A_i_qta = A_i_qta + B_i_qtaFROM a JOIN b ON (A_lo_key = B_lo_key_A);

L'istruzione è evidentemente ambigua e per questo non troverei poi così strano se il database si rifiutasse di eseguirla. Tuttavia, una volta che il database abbia accettato la sintassi dell'istruzione e l'abbia eseguita, io non ho alcun dubbio su quello che mi aspetto sia il valore del campo A_i_qta al termine dell'esecuzione: deve essere pitagoricamente pari a 10 (ovvero: 1+2+3+4).
SQL server considera legittima l'istruzione, ma la esegue in modo parziale e assurdo: al termine dell'esecuzione il campo A_i_qta della riga inserita nella tabella A vale 1 perché di fatto SQL server esegue l'operazione solo con la prima riga (apparentemente la prima in ordine di inserimento) che incontra sulla tabella B, trascurando tutte le altre.
Dato che in SQL la buona teoria prescrive che l'ordine di inserimento (ma in generale qualunque ordine e ordinamento, a meno che non sia dichiarato esplicitamente) sia da considerarsi totalmente trascurabile, lo stato del database al termine dell'esecuzione di questa banale istruzione è sostanzialmente *impredicibile*: un altro bel successo di SQL server!!!
(E poi si rifiuta di includere getdate() in una funzione perché rende un valore indeterminato!!!!!!!!)

DATE AMBIGUE

Atteso che uso un database su cui è impostata la lingua italiana e che DATEFORMAT vale 'ymd' non sono riuscito a capire perché '2005-12-01' interpretato come data rende il 12 di gennaio (creando fra l'altro una gravissima confusione con la notazione più generale '20051201' che invece indica correttamente il primo di dicembre). Il comportamento mi sembra poco documentato e complessivamente assurdo.

Inoltre mi sembra incomprensibile e insensato il risultato della seguente query, le cui due colonne dovrebbero essere assolutamente identiche:

declare @s varchar(20);
set @s =   convert(datetime, getdate(), 121);
select @s, convert(datetime, getdate(), 121);

@s convert(datetime, getdate(), 121)
giu 4 2008 11:57AM  2008-06-04 11:57:50.833

MANUALE E DOCUMENTAZIONE

Insieme a SQL server non viene fornita alcuna forma di documentazione cartacea. O almeno io non l'ho ricevuta. A parziale compensazione esiste un vastissimo HELP-ON-LINE che è possibile consultare per attingere tutte le informazioni necessarie.
Fatta salva la comodità di trovare con facilità un aiuto immediato su specifici argomenti (a patto di sapere con una certa precisione ciò che si sta cercando, il che a dire il vero è tutt'altro che scontato soprattutto quando ciò che si sta cercando è un errore o una anomalia), tale help-on-line a mio avviso è prolisso, poco organizzato e poco 'navigabile'.
A titolo di esempio: io ho fatto *veramente* fatica a trovare conferma nell'help-on-line di quanto da me contestato nel paragrafo PARAMETRI STORED PROCEDURES (e fors'anche non l'ho mai trovato). Nè ho mai trovato documentazione sulla data ambigua di cui sopra.

Manca completamente un manuale di riferimento (magari cartaceo) organizzato per argomento in cui si possa trovare un inquadramento complessivo dei vari argomenti. Chissà, forse organizzare [costosissimi] corsi di formazione rende meglio che scrivere manuali ben fatti ...

ALLA FINE

Alla fine, per fortuna, non uso né molto né spesso SQL server, e conseguentemente non lo conosco neppure granché bene. E per fortuna, perché altrimenti invece che un paio di paginette di commenti e critiche, avrei scritto i Promessi Sposi!

L'impressione complessiva che ho derivato dall'approccio con SQL server è che si tratti di un prodotto di fascia medio bassa che per motivi banalmente commerciali è stato in tutta fretta (versioni 6 e 7, fine anni '90) rattoppato e rappatchato e improvvidamente gettato nell'arena dei databases SQL veri. Né le pezze tardive e parziali (versione 8/2000) che sono state messe per cercare faticosamente di coprirne le carenze più significative hanno potuto cambiare la sostanza dei fatti.

Tutte le critiche da me portate al funzionamento di SQL server potrebbero banalmente tradursi in una sola contestazione e constatazione: il prodotto è semplicemente e strutturalmente inadeguato all'uso per il quale viene proposto; non è una piattaforma in grado di sostenere lo sviluppo di applicazioni vaste, solide e ben strutturate; non può essere messo a confronto con prodotti del calibro di Oracle e Sybase, non può gareggiare nella categoria dei 'pesi massimi' del mondo dei database, tutt'al più in quella dei pesi welter.

Non posso peraltro non rilevare che, in un contesto in cui una considerevolissima parte di aziende pervicacemente persiste ad utilizzare Visual Basic come linguaggio di sviluppo per ogni genere di applicazione, forse non è poi così illogico che il database di riferimento sia SQL server, perché il livello di inadeguatezza al proprio compito di entrambi i prodotti è perfettamente compatibile e allineato.
E poi, diciamoci la verità: chi usa Visual Basic come ambiente di sviluppo, come fa a notare la differenza tra SQL server e un database serio?

Quello che invece, ahimè, non si può contestare a SQL server, e che rappresenta certamente il suo punto di forza, è la sua clamorosa potenza commerciale, dinanzi alla quale troppo spesso i tecnici sono costretti a soccombere. Anche se a volte, la sera, si ritirano nel segreto della loro cameretta, e confidano al fido web-diario ciò che sentono nel profondo del cuore: SQL server? No, grazie!


NB: tutto quanto scritto in questa pagina vuole essere la manifestazione della mia personale opinione (prevalentemente tecnica) nei confronti di un prodotto commerciale pubblicamente disponibile e valutabile. Chiunque legga questa pagina è ovviamente libero di effettuare una diretta valutazione del prodotto e di formarsi una propria idea indipendentemente dalle mie opinabili opinioni, tanto più che stasera forse ho mangiato un po' pesante e il mio umore non è dei migliori. O forse è che ho passato gli ultimi mesi su SQL server e che ......

Mi scuso in anticipo per ogni eventuale scorrettezza in cui sia incorso, che sono pienamente disponibile ad emendare.

Sono molto interessato a pareri di altri utilizzatori di SQL server, sia che si trovino nel campo degli entusiasti che in quello dei detrattori. Per ogni comunicazione sarò lieto di ricevere messaggi su SQLserver.nograzie@feaci.it

PS: la versione di SQL server da me utilizzata per questa allegra recensione è la "Microsoft SQL Server2000 - 8.00.818 (Intel X86) May 31 2003 16:08:15 Copyright (c) 1988-2003 Microsoft CorporationStandard Edition on Windows NT 5.2 (Build 3790: )".

PS/2 (ma niente a che vedere con l'IBM): se mi scrivete che ho sbagliato e mi spiegate come fare (in modo ragionevole!) ad eseguire le operazioni che io credo essere ineseguibili, la pizza (minimo, a crescere) ve la offro davvero!!!! E volentieri!!!!