Newsgroup: it.comp.software.database
Titolo: La normalizzazione non e` tutto

Mi e` capitato di meditare a lungo su uno schema: d'istinto sapevo che
fosse sbagliato, ma non sapevo esplicitare perche' lo fosse. La cosa
e` interessante anche perche' non l'ho mai vista spiegata. Invece ho
visto consigliare il comportamento errato persino da uno dei massimi
esperti di db.
Il discorso parte da una discussione sui metodi per evitare l'uso
dei null in alcune colonne concettualmente legate. Ne riporto qui
un frammento, dato che lo schema reale e` molto piu` complicato.
Vendite {FatturaNum, Porto, FormaPagamentoTrasporto}
100, Franco Destino, contrassegno
101, Franco Destino, Ric.Banc.
102, Franco Fabbrica, NULL
Lo schema dava origine ai null nelle vendite franco fabbrica, in
quanto l'attributo Forma di pagamento trasporto non si applica.
Applicando la normalizzazione, la colonna FormaPagamentoTrasporto
andrebbe spostata in tabella a parte, complicando lo schema con una
tabella in piu`.
Nel tentativo di semplificare, il programmatore ha cercato di
collassare le colonne in una sola, usando una codifica, come segue:
1 = Franco Fabbrica
2 = Franco Destino Contrassegno
3 = Franco Destino Ric.Banc.
Il nuovo schema risulta cosi`:
Vendite {FatturaNum, Porto}
100, 2
101, 3
102, 1
Piu` compatto, piu` semplice e niente NULL. Tutto a posto dunque?
Immediatamente feci notare che l'informazione in due colonne era
stata sovrapposta su una.
Risposta: eppure lo schema e` equivalente, non c'e`
perdita di informazione, i dati sono semplici, la tabella e`
normalizzata, non ci sono dati ridondanti, ne' null. Tutto
apparentemente perfetto.
Dovetti rifletterci un po' per arrivare alla conclusione che
1) mentre e` vero che i valori sono atomici, QUESTO NON E`
ALTRETTANTO VERO PER IL LORO SIGNIFICATO. Il valore "1" porta una
informazione, i valori "2" e "3" portano DUE informazioni.
2) L'informazione "franco destino" non e` piu' UNIVOCAMENTE
INDIRIZZABILE: adesso devo testare la colonna per DUE valori
(... WHERE Porto=2 OR Porto=3). Questo vale anche per il codice
del programma che li usa.
L'obiezione a quanto sopra era "preferisco scrivere un po' piu'
di codice ma avere un db piu' compatto". Di per se' plausibile, il
che faceva sembrare la questione un futile esercizio teorico a fronte
di chiari vantaggi pratici. Ma problema piu' negativo doveva ancora
venire...
3) Quello schema NON E' ESTENSIBILE. Se in seguito volessi
aggiungere una nuova forma di pagamento, es.
4 = Franco Destino Carta di Credito
vanifico TUTTE le query gia` scritte che testavano la clausola
"franco destino" usando ...where porto=2 or porto=3, per il motivo
che adesso dovrei invece scrivere ...where porto=2 or porto=3 OR PORTO=4.
In pratica ho creato una breccia che non permette di essere sicuri
che il db rispondera` sempre correttamente. Questo vale soprattutto
per i programmi scritti precedentemente alla modifica, i quali DEVONO
essere modificati, pena comportamenti errati.
Questo va contro i buoni principi di design, che vorrebbero che il db
sia estensibile ed indipendente dai programmi che vi accedono.
bye 

 che adesso dovrei invece scrivere ...where porto=2 or porto=3 OR PORTO=4.
Altra conseguenza e` che, anche se volessi aggiornare in maniera
automatica il listato, ad esempio con una sostituzione di stringhe
da "where porto=2 or porto=3" "where porto=2 or porto=3 or porto=4"
non potrei farlo.
Il motivo e` che non sono piu` sicuro di cosa volesse significare
quella query, ossia volevo testare che la spedizione fosse "franco
destino" oppure mi interessavano le relative causali "contrassegno
e ricevuta bancaria"?
L'unico modo sarebbe far eseguire un esame da parte del programmatore,
di ogni query di quel tipo, nel contesto del programma, perche` ora
anche quella query ha DUE SIGNIFICATI. 


ma così facendo la tabella non è più in 1NF, infatti inserendo due
attributi in un un'unica colonna si contravviene alla regola secondo
la quale ogni attributo deve rappresentare valori atomici appartenenti
ad un unico dominio. Vedi: 
http://it.wikipedia.org/wiki/Normalizzazione_del_database#Prima_Forma_Normale

 
http://it.wikipedia.org/wiki/Normalizzazione_del_database#Prima_Forma_Normale
Io del contenuto dei wiki mi fido relativamente... Vedi ad esempio
qui, ove dice che "Si definiscono database realazionali
i database che consentono di creare relazioni tra i record di piů
tabelle" :-(
http://it.wikibooks.org/wiki/Strutture_dati_e_database/Creare_e_gestire_un_database


ciò non fa altro che spostare il problema sulla tabella di codifica, a
quel punto è la tabella di codifica a non essere più in 1NF. Ma dal
punto di visto del modello relazionale il risultato non cambia e le
conseguenze di quella scelta sbagliata rimangono inalterate.
Una fonte più autorevole di wikipedia:
Le 5 regole definite da Date per le quali una tabella è in 1NF sono:
1) There's no top-to-bottom ordering to the rows.
2) There's no left-to-right ordering to the columns.
3) There are no duplicate rows.
4) Every row-and-column intersection contains exactly one value from
the applicable domain (and nothing else).
5) All columns are regular [i.e. rows have no hidden components such
as row IDs, object IDs, or hidden timestamps].
(da Chris Date, "What First Normal Form Really Means", pp. 127-8)
la tabella di codifica non rispetta la regola n.4

 colonne in una, ha usato uno schema di codifica.
Non è cosi: non basta avere una colonna chiave univoca perchè la tabella 
sia 1NF altrimenti (al limite) sarebbe 1NF una tabella di due colonne e 
circa 50 mln di righe di cui la prima è il codice fiscale e la seconda è 
una stringa tipo "Sig. xxxx yyyy sposato con wwww zzzz e con tre figli 
di nome qui quo e qua " eccetera ...

 sia 1NF
Se ti riferisci alla colonna Porto, non e` chiave di niente.
Non e` che anche tu hai guardato la descrizione testuale invece
della tabella? La tabella VENDITE e` senza ombra di dubbio alcuno in 1NF. 

 la tabella di codifica non rispetta la regola n.4
Ma quella non e` una tabella del database! Quella e` una mia
descrizione testuale. Questa e` la tabella nel database:
Vendite {FatturaNum, Porto}
100, 2
101, 3
102, 1
...ed e` senza dubbio alcuno in 1NF.
Questo e` il punto del mio messaggio: la normalizzazione considera
solo i VALORI, ma non cio` non basta. 


Ma lo sbaglio è concettuale: l'attributo [porto] in realtà è un 
attributo complesso formato dal [porto] e [modalità pagamento trasporto] 
quindi la relazione modificata non è 1NF mentre lo era originariamente.
Il problema del NULL in realtà non esiste perché la modalità di 
pagamento per "porto franco" può essere definita "gratuita" o "a ns carico".
Inoltre fermarsi alla prima forma normale non è sufficiente.


pur non volendo entrare nella discussione accademica sulla forma
normale,
direi che sono d'accordo con  quest'ultima osservazione, il null non è
certo un problema,
se vuoi fare una cosa fatta bene devi usare una tabella di decodifica,
ovverò definisci
l'entità "porto". Ho capito male o la soluzione
Vendite {FatturaNum, Porto}
100, 2
101, 3
102, 1
implica il cablaggio del significato  dei codici 1, 2, 3
nell'applicazione?
Saluti
 Cristian

 Inoltre fermarsi alla prima forma normale non è sufficiente.
Gia', ma la 1NF e` speciale, in quanto e` l'unica obbligatoria
per il funzionamento del modello relazionale.
bye 

 
Se avessi letto solo questa tabella ti avrei risposto certamente "SI, lo 
è...". Mi sarei posto qualche quesito mentale ma alla fine se hai la 
necessità di postare il contenuto di una e una sola tabella perchè 
chiedere il resto?
Tornando al primo esempio. Quando ho visto come era stata compattata la 
tabella, 2 colonne in una, ho spalanato gli occhi. Scherzi ma 
logicamente è errato. Ma se disgraziatamente, mo xche voi ricercate per 
"Franco Destino" o "Franco Fabbrica", ma se doveste effettuare la 
ricerca dell'ID in base alla modalità di pagamento? Ma come avreste 
fatto? Nel punto dove non c'è il valore come lo testavi? se la colonna 
contiene 3 parole allora lo prendo altrimenti era sicuramente NULL... No 
per carità. Al max se proprio devo complicarmi la vita avrei fatto una 
"select id from Vendite where (instr(campo, "Franco Destino") > 0)". 
Forse e dico forse così ti salvi da qualsiasi modifica futura. Ma quanti 
DB supportano questa funzione?
Per il resto concordo con voi... Il problema del NULL in questo caso non 
si pone, basterebbe sostituirlo con un valore congruo come "gratis" 
(appunto) ma ponendo il caso che il valore non sia un char ma sia un 
numero? Con che lo sostituisci con Zero? E ma in alcuni casi mettere il 
valore può essere considerato un Errore.
E allora al posto del NULL che ci metto?

 informazione, i valori "2" e "3" portano DUE informazioni.
Per arrivare ad una conclusione, direi che avere valori atomici
e` desiderabile ai fini di una corretta manipolazione, ma non
sufficiente; per una corretta interpretazione bisogna che anche
l'informazione data sia solo una.
Per completezza bisogna dire che negli ultimi anni la prima
forma normale e` data ampliata da Date con l'inclusione di
domini complessi, le cosiddette tabelle dentro altre tabelle; questo
mi sembra essere in contrasto con quello che Codd aveva in mente,
che mi sembra piu` orientato al "one fact in one place".
Questa nuova revisione della normalizzazione non e` unanimamente
condivisa da tutti. Oltretutto richiedono scomodi operatori di
NEST per ricomporre la tabella in forma manipolabile. Mi sa
molto (troppo) di db gerarchico; ci vedo pochi guadagni pratici
e un deciso aumento della complessita`.
Noto infine una somiglianza nella comune pratica di surrogare una
chiave complessa (quando non addirittura un'intera riga) con una
chiave artificiale; in alcuni di questi casi si possono
applicare le stesse considerazioni di "compattamento" indesiderato
di piu` significati in una sola colonna; in questi casi un join
si rende necessario per decodificarne il "vero" significato
(ammesso che tutte le informazioni originali siano ancora presenti).