Alla base del nostro universo digitale si trova una mole di dati in costante aumento. Elementi centrali di Internet e del mondo in­ter­con­nes­so sono i Database Ma­na­ge­ment System come MySQL che con­sen­to­no di elaborare elet­tro­ni­ca­men­te grandi quantità di dati, di me­mo­riz­zar­li lo­gi­ca­men­te e di con­ser­var­li per­ma­nen­te­men­te. Così le banche dati complesse vengono suddivise in quantità più piccole e messe in relazione tra di loro, se ne­ces­sa­rio. Nel nostro tutorial MySQL per prin­ci­pian­ti vi forniamo le basi per la gestione dei database e vi in­di­chia­mo grazie a degli esempi come ot­ti­miz­za­re la gestione del vostro progetto web con MySQL.

Che cos’è MySQL?

MySQL fa parte dei sistemi di gestione di basi di dati re­la­zio­na­li più popolari al mondo, insieme a Oracle e Microsoft SQL Server (trovate una pa­no­ra­mi­ca recente sui DBMS su db-engines.com). Il software, svi­lup­pa­to nel 1994 dall’azienda svedese MySQL AB, è oggi sotto l’ala pro­tet­tri­ce della Oracle Cor­po­ra­tion e viene gestito sulla base di un sistema di licenza duale: oltre a una variante En­ter­pri­se pro­prie­ta­ria, Oracle ne mette a di­spo­si­zio­ne anche un’altra con licenza GPL e quindi open source. Questa doppia licenza dà alle aziende la pos­si­bi­li­tà di svi­lup­pa­re delle proprie ap­pli­ca­zio­ni uti­liz­zan­do MySQL senza che queste debbano sot­to­sta­re alla licenza open source. Nella community open source l’acquisto di MySQL da parte di Oracle ha sollevato però pre­va­len­te­men­te delle critiche. MySQL è scritto in C e C++ e dispone di un parser SQL basato su Yacc con un tokenizer au­to­svi­lup­pa­to (scanner lessicale). Il Database Ma­na­ge­ment System si con­trad­di­stin­gue inoltre per un ampio supporto del sistema operativo.

Fatto

L’ab­bre­via­zio­ne SQL sta per “Struc­tu­red Query Language”, un lin­guag­gio in­for­ma­ti­co che serve per la gestione delle strutture di database. Tra le ope­ra­zio­ni possibili con questo lin­guag­gio rientrano la ricerca, l’in­ser­zio­ne, l’ag­gior­na­men­to e l’eli­mi­na­zio­ne di banche dati.

MariaDB: il fork di MySQL con po­ten­zia­le

L’in­te­gra­zio­ne del progetto MySQL nel portfolio dei prodotti Oracle si è imbattuta so­prat­tut­to nei sospetti e nelle critiche degli svi­lup­pa­to­ri. Ciò dipende in­nan­zi­tut­to dal fatto che le dif­fe­ren­ze tra una versione MySQL con licenza GPL e il prodotto En­ter­pri­se a pagamento sono in costante aumento. Le nuove funzioni del Database Ma­na­ge­ment System sono sempre più spesso ri­la­scia­te solo per la versione pro­prie­ta­ria. I database degli errori non resi pubblici e i test necessari che, invece, vengono tra­scu­ra­ti, fanno pensare alla community che si dia poca rilevanza al progetto open source, passato sotto l’ala pro­tet­tri­ce del software Goliath della Oracle; ne consegue così un distacco della community.

Già dal 2009 il team prin­ci­pa­le degli svi­lup­pa­to­ri intorno all’ideatore di MySQL Michael “Monty“ Widenius ha voltato le spalle al popolare sistema di database e ha avviato con MariaDB un fork open source del software. Alla fine del 2012 sono state in­tro­dot­te con Fedora, OpenSUSE, Slackware e Arch Linux le prime di­stri­bu­zio­ni Linux che pre­ve­de­va­no il passaggio da MySQL a MariaDB nell’in­stal­la­zio­ne standard. In­nu­me­re­vo­li progetti open source e illustri aziende di software e piat­ta­for­me web hanno seguito il loro esempio, come Mozilla, Ubuntu, Google, Red Hat En­ter­pri­se Linux, Web of Trust, Team Speak, la Wikimedia Foun­da­tion e il software XAMPP, di cui si tratterà in seguito.

Già da ora emerge che MariaDB, a dif­fe­ren­za della versione MySQL open source, viene co­stan­te­men­te svi­lup­pa­ta. Tutti gli indizi lasciano perciò supporre che il fork supererà ben presto il progetto da cui ha preso le mosse.

Consiglio

Nell’ambito dei software viene indicato come fork un ramo di sviluppo che nasce dalla divisione di un progetto (il più delle volte open source). Un fork si basa sul codice sorgente del progetto prin­ci­pa­le e lo sviluppa in un altro progetto in­di­pen­den­te.

Sistemi di database

Og­gi­gior­no la gestione elet­tro­ni­ca dei dati avviene per la maggior parte nei sistemi di database (DBS), che si com­pon­go­no prin­ci­pal­men­te di due com­po­nen­ti: il database stesso (DB) e il Database Ma­na­ge­ment System (DBMS), ne­ces­sa­rio alla sua gestione.

  • Il Database Ma­na­ge­ment System: il DBMS è un software come MySQL che serve per la gestione del DBS. Tra i compiti di questo software di gestione rientrano la strut­tu­ra­zio­ne dei dati secondo un modello di banca dati pre­de­fi­ni­to. Così il DBMS controlla i permessi di scrittura e lettura sul database, gestisce grandi quantità di dati e gli accessi si­mul­ta­nei al database, oltre che occuparsi che vengano ri­spet­ta­te le linee guida per l’integrità, la pro­te­zio­ne e la sicurezza dei dati.
  • Il database: un database è formato da banche dati correlate tra loro a livello con­te­nu­ti­sti­co, ad esempio i dati del cliente o del CMS. Un DBMS può gestire con­tem­po­ra­nea­men­te più database.

La seguente il­lu­stra­zio­ne mostra una rap­pre­sen­ta­zio­ne sche­ma­ti­ca di un sistema di database:

Il database re­la­zio­na­le

Per de­fi­ni­zio­ne MySQL è un DBMS re­la­zio­na­le. Ciò significa che la gestione dei dati si basa su un modello di database tabellare. Tutti i dati che MySQL elabora vengono salvati nelle tabelle che si possono mettere in relazione tramite chiavi (keys).

Chiariamo questo concetto grazie a un semplice esempio. Di seguito si trovano le due tabelle autori e opere:

Tutte le tabelle di un database re­la­zio­na­le sono composte da colonne e righe. Ogni colonna di una tabella rap­pre­sen­ta un attributo specifico. Nella tabella autori si trovano ad esempio gli attributi id, nome e cognome. Le righe di una tabella vengono indicate come rows e con­ten­go­no ognuna dei record. Un record viene so­li­ta­men­te iden­ti­fi­ca­to uni­vo­ca­men­te, ovvero numerato, tramite una chiave primaria. Al momento della creazione della tabella viene definito quale attributo funge da chiave primaria. Il pre­re­qui­si­to è che la chiave primaria consenta un’as­se­gna­zio­ne univoca. Così ogni chiave primaria può essere uti­liz­za­ta una sola volta all’interno di una colonna. Si consiglia una nu­me­ra­zio­ne tramite ID.

La tabella opere presenta oltre alla chiave primaria id_opere, anche id_autori come chiave esterna (foreign key) e crea così una relazione tra le tabelle, con­sen­ten­do di collegare i record di una tabella con quelli dell’altra. Se si mettono in relazione due tabelle di un database re­la­zio­na­le, si parla di un join, che si può rea­liz­za­re tramite la seguente in­ter­ro­ga­zio­ne del database: “Ricerca tutte le opere dell’autore John Ronald Reuel Tolkien com­pren­si­ve dell’anno di pub­bli­ca­zio­ne”.

Tolkien è elencato nella tabella autori con la chiave primaria id_autori. Per ricercare tutte le opere dell’autore, viene uti­liz­za­to nella tabella opere come chiave esterna. Vengono così ri­chia­ma­te tutte le rows, che sono collegate con id_autori 1.

Abi­tual­men­te le ope­ra­zio­ni del database MySQL vengono rea­liz­za­te ri­cor­ren­do ai comandi SQL standard come SELECT, INSERT, UPDATE e DELETE. Ap­pro­fon­di­re­mo questi comandi nei prossimi paragrafi del nostro tutorial MySQL.

Ov­via­men­te si sarebbero potuti salvare i dati sui due autori e le loro opere in un'unica tabella. Una gestione dei dati di questo tipo comporta che un database contenga una grande quantità di voci ri­don­dan­ti, perché ad esempio i dati delle colonne nome e cognome sarebbero dovuti essere pre­sen­ta­ti sin­go­lar­men­te per ogni opera. Una ri­don­dan­za simile non so­vrac­ca­ri­ca solo la memoria, ma porta anche al fatto che debbano essere ef­fet­tua­ti degli ag­gior­na­men­ti in più parti del database. Lavorando con i sistemi di database re­la­zio­na­li, ci si limita perciò a rap­pre­sen­ta­re un dato per tabella. In questo caso si parla di una nor­ma­liz­za­zio­ne dei dati.   

Il campo di ap­pli­ca­zio­ne prin­ci­pa­le di MySQL è la me­mo­riz­za­zio­ne dei dati nell’ambito delle pagine web dinamiche. La com­bi­na­zio­ne di MySQL con il web server Apache e il lin­guag­gio di scripting PHP o Perl si è affermato come struttura software classica nello sviluppo web. Lo stack per il web si può rea­liz­za­re con tutti i sistemi operativi comuni per il server, prendendo quindi il nome di LAMP (Linux), MAMP (macOS) o WAMP (Windows).

Per i prin­ci­pian­ti di MySQL con­si­glia­mo di uti­liz­za­re l’ambiente di test locale XAMPP, così da ac­cu­mu­la­re delle prime espe­rien­ze con il sistema di gestione di database. Da notare però che la sua ultima versione si basa su MariaDB.

In­stal­la­zio­ne del Database Ma­na­ge­ment System

Per farvi com­pren­de­re meglio le basi MySQL, uti­liz­ze­re­mo degli esempi concreti. La nostra in­tro­du­zio­ne a MySQL si serve dell’ambiente di test XAMPP. Gli snippet del codice e gli screen­shot si con­cen­tra­no sulle ope­ra­zio­ni del database che sono state rea­liz­za­te tramite PHP grazie a un server Apache in locale su un computer Windows. Al posto del classico database MySQL viene uti­liz­za­to il fork MariaDB. Al momento i due sistemi di database di­mo­stra­no una totale com­pa­ti­bi­li­tà per quanto riguarda l'e­se­cu­zio­ne delle ope­ra­zio­ni. Per voi, in quanto utenti finali, nell’ambito di un tutorial per prin­ci­pian­ti non fa dif­fe­ren­za lavorare con un database MySQL o MariaDB. Come in­stal­la­re un simile ambiente di test locale sul vostro computer Windows è l’argomento prin­ci­pa­le del nostro tutorial su XAMPP. Se volete fare un po’ di gavetta per imparare a usare i database re­la­zio­na­li, vi con­si­glia­mo di optare di­ret­ta­men­te per MariaDB. Un ambiente di test al­ter­na­ti­vo basato su MySQL è di­spo­ni­bi­le gra­tui­ta­men­te con AMPPS. Inoltre c’è la pos­si­bi­li­tà di impostare uno stack per­so­na­liz­za­to. Se ne­ces­sa­rio, MySQL e MariaDB si possono combinare con diversi sistemi operativi, web server e linguaggi di scripting. I pacchetti di download gratuiti, con licenza GPL, sono di­spo­ni­bi­li su mysql.it e mariadb.com. Trovate una guida det­ta­glia­ta per l’in­stal­la­zio­ne sulle diverse piat­ta­for­me nella do­cu­men­ta­zio­ne in inglese di MySQL e MariaDB.

Gestione del database con ph­p­MyAd­min

Nella gestione di MySQL ci im­bat­tia­mo nell’ap­pli­ca­zio­ne web gratuita ph­p­MyAd­min, già contenuta nel pacchetto di in­stal­la­zio­ne di XAMPP, ma che è anche di­spo­ni­bi­le se­pa­ra­ta­men­te sul sito del progetto ufficiale come pacchetto da scaricare. ph­p­MyAd­min si presenta come un software standard per l’am­mi­ni­stra­zio­ne di database MySQL nel World Wide Web. L’ap­pli­ca­zio­ne web scritta in PHP e Ja­va­Script consente di eseguire ope­ra­zio­ni di database tramite un’in­ter­fac­cia grafica. Così create e gestite le tabelle del vostro database re­la­zio­na­le fa­cil­men­te con un click sul browser. La co­no­scen­za dei comandi SQL non è ini­zial­men­te ne­ces­sa­ria.

Aprire ph­p­MyAd­min

Una volta che il pacchetto software XAMPP è stato in­stal­la­to, avviate dal pannello di controllo il Database Ma­na­ge­ment System (MySQL o MariaDB), in maniera analoga agli altri com­po­nen­ti dello stack di test. Per fare ciò uti­liz­za­te l’apposito pulsante “Start” sotto “Actions”. Per poter aprire ph­p­MyAd­min dal browser, dovete avviare prima di tutto il web server Apache. I moduli attivati sono segnalati in verde nel pannello di controllo di XAMPP. In aggiunta vi­sua­liz­za­te lo stato attuale dei moduli di XAMPP tramite degli avvisi co­mu­ni­ca­ti nella finestra di testo.

Fatto

XAMPP è stato svi­lup­pa­to nell’ambito del progetto software Apache Friends come sistema di test compatto per essere uti­liz­za­to sul computer locale. Il pacchetto software non è pensato per mettere a di­spo­si­zio­ne i servizi web su Internet. Per via di in­nu­me­re­vo­li li­mi­ta­zio­ni in ambito della sicurezza, XAMPP non è adatto a essere usato come sistema pro­dut­ti­vo.

Nel fun­zio­na­men­to di test locale avete a di­spo­si­zio­ne l’in­ter­fac­cia utente del software di am­mi­ni­stra­zio­ne su http://localhost/ph­p­myad­min/.

Se avete definito nell’in­stal­la­zio­ne MySQL una password per l’account root, ph­p­MyAd­min ve la richiede in una schermata di login. Se uti­liz­za­te ph­p­MyAd­min nell’ambito di un prodotto di web hosting, i cor­ri­spet­ti­vi dati di login vi vengono assegnati dal ri­spet­ti­vo provider. È raro che in questo caso si disponga dei permessi di root.

Dopo che il login è andato a buon fine, ph­p­MyAd­min vi presenta la pagina iniziale dell’ap­pli­ca­zio­ne, che vi dà la pos­si­bi­li­tà di mo­di­fi­ca­re le im­po­sta­zio­ni di base per i set di caratteri (col­la­zio­ne) della con­nes­sio­ne MySQL e di scegliere la modalità di vi­sua­liz­za­zio­ne de­si­de­ra­ta (lingua, design e di­men­sio­ne del font). Inoltre nella pagina destra trovate una pa­no­ra­mi­ca dei dati più im­por­tan­ti del vostro server database, del web server uti­liz­za­to, così come di in­for­ma­zio­ni sulla versione attuale di ph­p­MyAd­min. La barra del menu della pagina iniziale è strut­tu­ra­ta sotto forma di schede, come tutte le altre dell’ap­pli­ca­zio­ne. Quindi si hanno a di­spo­si­zio­ne le tab Databases, SQL, Status, User accounts, Export, Import, Settings, Re­pli­ca­tion, Variables e More.

Sull’angolo sinistro dell’in­ter­fac­cia utente trovate il pannello di na­vi­ga­zio­ne. Qui sono pre­sen­ta­te tutte le tabelle a cui potete accedere dal vostro database grazie a ph­p­MyAd­min. Sotto il logo del programma nell’angolo sinistro in alto, il software mostra i link alla pagina iniziale e mette a di­spo­si­zio­ne la do­cu­men­ta­zio­ne ufficiale. Inoltre avete la pos­si­bi­li­tà di con­fi­gu­ra­re il pannello di na­vi­ga­zio­ne e ag­gior­na­re la sua vi­sua­liz­za­zio­ne.

Iniziamo il nostro corso crash MySQL, creando il nostro primo database.

Creare un database

Per creare un database con ph­p­MyAd­min, se­le­zio­na­te per prima cosa la tab “Databases“ nella barra del menu della pagina iniziale.

Inserite il nome scelto per il vostro database nel campo apposito sotto “Create database“ (crea database) e scegliete un set di caratteri. Vi con­si­glia­mo la col­la­zio­ne utf8mb4_unicode_ci. Con la scelta di un set di caratteri co­mu­ni­ca­te al server database quale codifica deve essere uti­liz­za­ta per i dati da inviare e da ricevere. Le varianti mb4 con­sen­to­no anche l’uso di caratteri esotici, come simboli o emoticon che si trovano al di là dello standard Unicode (Basic Mul­ti­lin­gual Plane), e sono perciò con­si­glia­te.

Con­fer­ma­te quanto inserito con un click su “Create“ (crea). Il database creato verrà vi­sua­liz­za­to nel pannello di na­vi­ga­zio­ne sulla pagina sinistra dello schermo. I nuovi database sono ini­zial­men­te vuoti. Per inserire dei dati, create nel prossimo passaggio una tabella.

Creare delle tabelle

Per creare una nuova tabella, se­le­zio­na­te il database ap­pro­pria­to e navigate nella barra del menu nella tab “Structure“ (Struttura).

Create una tabella, inserendo il nome (ad esempio users) e il numero di colonne richieste nell’in­ter­fac­cia “Create table” (crea tabella). Ri­cor­da­te­vi che ogni colonna rap­pre­sen­ta un attributo di un record. Se avete bisogno di altre colonne, potete ag­giun­ger­le suc­ces­si­va­men­te.

Se, ad esempio, volete creare un database per gli utenti del vostro sito, le seguenti voci si prestano a essere uti­liz­za­te per le colonne della tabella:

Colonna De­scri­zio­ne
id Numero di iden­ti­fi­ca­zio­ne univoco per ogni utente
forename Il nome dell’utente
surname Il cognome dell‘utente
email L’indirizzo e-mail dell‘utente
password La password dell‘utente
created_at La data in cui è stata creata la voce
updated_at La data in cui è stata ag­gior­na­ta la voce

Per il vostro database per gli utenti create così una tabella users con sette colonne. Con­fer­ma­te quanto inserito premendo il pulsante “Go”.

Dopo che la tabella è stata creata, ph­p­MyAd­min vi offre la pos­si­bi­li­tà di definire le voci per le colonne della tabella e di decidere le im­po­sta­zio­ni di formato per i dati previsti.

Nella seguente tabella trovate una de­scri­zio­ne della struttura della tabella e delle possibili for­mat­ta­zio­ni.

Opzione De­scri­zio­ne
Name A ogni colonna di una tabella del database viene assegnato un nome, che può essere scelto a piacere (seppur con alcune li­mi­ta­zio­ni). Sono ammesse le lettere latine (maiuscole e minuscole, ma senza altri caratteri speciali), cifre, il simbolo del dollaro e l’un­der­sco­re. Quest’ultimo può essere uti­liz­za­to come al­ter­na­ti­va ai caratteri non con­sen­ti­ti (errato: user id; giusto: user_id). Il nome delle colonne non può essere composto solo da cifre. Inoltre nel lin­guag­gio di database SQL si trovano diverse keyword, che sono riservate per compiti precisi. Una lista di tutte le keyword è di­spo­ni­bi­le sulla do­cu­men­ta­zio­ne di MySQL. Potete aggirare la maggior parte di queste li­mi­ta­zio­ni, ma così dovete sempre mettere sulla relativa colonna i backtick (``). Valgono le stesse regole anche per i nomi delle tabelle e gli altri nomi su. Si consiglia di uti­liz­za­re dei nomi delle colonne ap­pro­pria­ti che si addicano all’attributo cor­ri­spon­den­te.
Type Il tipo di dati indica quali dati sono salvati in una colonna. MySQL e MariaDB vi con­sen­to­no di definire i dati come numeri interi e in virgola mobile, di inserire in­di­ca­zio­ni temporali e date, oltre che stringhe testuali e dati binari. Trovate una de­scri­zio­ne nella tabella dei tipi di dati.
Length/Values Per alcuni tipi di dati (ad esempio per le stringhe testuali) potete assegnare ai valori di una colonna una lunghezza massima. Questa for­mat­ta­zio­ne è però opzionale.
Default L’opzione “Default“ vi consente di definire un valore standard per una colonna, che viene così sempre inserito au­to­ma­ti­ca­men­te quando un record non contiene nessun valore per la relativa colonna.
Collation Con l’opzione “Collation“ definite un tipo di carattere specifico per una colonna che può variare dalle im­po­sta­zio­ni globali del database. Potete cambiare la codifica a livello tabellare anche per tutte le colonne.
At­tri­bu­tes Alcuni tipi di dati si possono spe­ci­fi­ca­re meglio tramite attributi opzionali. Così potete ad esempio stabilire con gli attributi signed e unsigned, se un numero intero o in virgola mobile può accettare anche valori negativi (signed) o solo positivi (unsigned).
Index Con l’opzione “Index“ definite le regole per l’in­di­ciz­za­zio­ne. Se scegliete per una colonna l’im­po­sta­zio­ne dell’indice PRIMARY, allora questa sarà valida come chiave primaria della tabella. L’im­po­sta­zio­ne UNIQUE sta­bi­li­sce che i valori all’interno di questa colonna possono essere salvati solo una volta. Così, se ne­ces­sa­rio, si possono evitare i doppioni.
A_I L’ab­bre­via­zio­ne “A_I“ sta per AUTO_INCREMENT e incarica il Database Ma­na­ge­ment System di con­teg­gia­re un valore au­to­ma­ti­ca­men­te, quando non viene indicato al momento della creazione di un record. Questa opzione viene uti­liz­za­ta nell’in­di­ciz­za­zio­ne dei record.
Comments Il campo “Comments“ consente di inserire dei commenti nelle colonne delle tabelle.

Le opzioni pre­sen­ta­te qui com­pren­do­no le im­po­sta­zio­ni più im­por­tan­ti per la for­mat­ta­zio­ne delle colonne delle tabelle. Se con l’aiuto della barra di scor­ri­men­to scorrete ancora la pagina a destra, trovate altre im­po­sta­zio­ni a cui non abbiamo fatto ri­fe­ri­men­to in questo tutorial MySQL.

La tabella qui sotto presenta i diversi tipi di dati ela­bo­ra­bi­li con MySQL e MariaDB, così come i relativi domini di valori e lo spazio richiesto.

Tipo De­scri­zio­ne Dominio di valori Spazio richiesto
TINYINT Un numero intero molto piccolo Senza segni an­te­ce­den­ti: da 0 fino a 255 Con segni an­te­ce­den­ti: da -128 fino a +127 1 Byte
SMALLINT Un numero intero piccolo Senza segni an­te­ce­den­ti: da 0 fino a 65.535 Con segni an­te­ce­den­ti: da -32.768 fino a +32.767 2 Byte
MEDIUMINT Un numero intero di di­men­sio­ne media Senza segni an­te­ce­den­ti: da 0 fino a 16.777.215 Con segni an­te­ce­den­ti: da -8.388.608 fino a +8.388.607 3 Byte
INT/INTEGER Un numero intero di di­men­sio­ne normale Senza segni an­te­ce­den­ti: da 0 fino a 4.294.967.295 Con segni an­te­ce­den­ti: da -2.147.483.648 fino a +2.147.483.647 4 Byte
BIGINT Un numero intero grande Senza segni an­te­ce­den­ti: da 0 fino a 18.446.744.073.709.551.615 Con segni an­te­ce­den­ti: da -9.223.372.036.854.775.808 fino a +9.223.372.036.854.775.807 8 Byte
FLOAT Un numero in virgola mobile a pre­ci­sio­ne singola Senza segni an­te­ce­den­ti: da 0 fino a 3,4e+38 Con segni an­te­ce­den­ti: da -3,4e+38 fino a 3,4e+38 4 Byte
DOUBLE Un numero in virgola mobile a pre­ci­sio­ne doppia Senza segni an­te­ce­den­ti: da 0 fino a 3,4e+38 Con segni an­te­ce­den­ti: da -3,4e+38 fino a 3,4e+38 8 Byte
DATE Data nel formato 'YYYY-MM-DD' Da '1000-01-01' fino a '9999-12-31' 3 Byte
TIME In­di­ca­zio­ne dell’ora nel formato 'HH:MM:SS.ssssss' Da '-838:59:59.999999' fino a '838:59:59.999999' 3 Byte
DATETIME In­di­ca­zio­ne della data nel formato 'YYYY-MM-DD HH:MM:SS.ssssss' Cor­ri­spon­de a DATE e TIME (fino a 23:59:59.999999 ore) 8 Byte
TIMESTAMP Timestamp nel formato 'YYYY-MM-DD HH:MM:DD' Da '1970-01-01 00:00:01' (UTC) fino a '2038-01-19 05:14:07' (UTC) 4 Byte
YEAR Anni compresi dal 1901 al 2155 Dal 1901 fino al 2155 e 0000 1 Byte
CHAR Sequenza di caratteri di lunghezza fissa; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 255 caratteri M Byte
VARCHAR Sequenza di caratteri di lunghezza variabile; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 65.535 caratteri Max. M + 2 Byte
TINYTEXT Sequenza di caratteri molto breve di lunghezza variabile; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 255 caratteri M + 1 Byte
TEXT Sequenza di caratteri di lunghezza variabile; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 65.535 caratteri M + 2 Byte
ME­DIUM­TEXT Sequenza di caratteri media di lunghezza variabile; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 16.777.215 caratteri M + 3 Byte
LONGTEXT Sequenza di caratteri lunga di lunghezza variabile; il numero dei caratteri cor­ri­spon­de a M Per M: da 0 fino a 4.294.967.295 caratteri (4 GB) M + 4 Byte
BLOB Un BLOB (Binary Large Object) è un oggetto binario con dati di lunghezza variabile (ad esempio immagini e file audio) Lunghezza max. di M: 65.535 Byte M + 2 Byte
TINYBLOB Piccolo oggetto binario con dati di lunghezza variabile Lunghezza max. di M: 255 Byte M + 1 Byte
ME­DIUM­BLOB Oggetto binario medio con dati di lunghezza variabile Lunghezza max. di M: 16.777.215 Byte M + 3
LONGBLOB Oggetto binario grande con dati di lunghezza variabile Lunghezza max. di M: 4.294.967.295 Byte (4 GB). M + 4 Byte
ENUM (Enu­me­ra­zio­ne) Una stringa i cui valori con­sen­ti­ti sono definiti durante la creazione delle colonne Massimo 65,535 elementi diversi 1 o 2 byte, a seconda del numero dei valori possibili
SET Una stringa i cui valori con­sen­ti­ti sono definiti durante la creazione delle tabelle. È possibile una scelta multipla Massimo 64 valori diversi 1, 2, 3, 4, o 8 byte, a seconda del numero dei valori possibili

Per la tabella di esempio users abbiamo optato per le seguenti im­po­sta­zio­ni:

i possibili valori per la colonna id sono stati definiti come numeri interi (Integer, INT) e sono con­tras­se­gna­ti dall’attributo UNSIGNED. La colonna id può accettare solo valori numerici positivi. Alla voce “Index“ abbiamo scelto per id l’opzione PRIMARY. Il numero di iden­ti­fi­ca­zio­ne funge così da chiave primaria per la tabella users. La spunta su “A_I“ (Auto_Increment) segnala al Database Ma­na­ge­ment System che gli ID per ogni voce devono essere generati au­to­ma­ti­ca­men­te con numeri pro­gres­si­vi.

I valori per le colonne forename, surname, email e password sono stati definiti come tipo di dati VARCHAR. Si tratta così di sequenze di caratteri variabili, di cui abbiamo limitato la lunghezza (M) con l’opzione “Length/Value“ a 50 caratteri. Per la colonna email è stata anche attivata l’opzione Index UNIQUE. In questo modo ci as­si­cu­ria­mo che ogni indirizzo e-mail nella nostra tabella sia salvato solo una volta.

Per le colonne created_at e updated_at abbiamo scelto il tipo di dati TIMESTAMP. Il Database Ma­na­ge­ment System salva le in­di­ca­zio­ni temporali per la creazione e l’ag­gior­na­men­to dei record nel formato YYYY-MM-DD HH:MM:DD. Visto che il sistema per ogni nuova voce deve creare au­to­ma­ti­ca­men­te un timestamp, scegliamo per la colonna created_at il valore standard CURRENT_TIMESTAMP. La colonna updated_at diventa rilevante solo quando ag­gior­nia­mo una voce. Perciò con­sen­tia­mo i valori Null per questa colonna e im­po­stia­mo NULL come valore standard.

Fatto

Il valore NULL nel lin­guag­gio PHP indica un campo vuoto. Un campo di dati ha il valore NULL, se non gli è ancora stato assegnato alcun valore.

Come Storage Engine (motore di ar­chi­via­zio­ne) uti­liz­zia­mo il formato standard delle tabelle MySQL, InnoDB.

Tutte le im­po­sta­zio­ni delle tabelle che avete in­tra­pre­so tramite l’in­ter­fac­cia grafica vengono con­ver­ti­te da ph­p­MyAd­min nel codice SQL. Se ne­ces­sa­rio, questo codice si può ri­pro­dur­re tramite il pulsante “Anteprima SQL”.

CREATE TABLE test.users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT , forename VARCHAR(50) NOT NULL , surname VARCHAR(50) NOT NULL , email VARCHAR(50) NOT NULL , password VARCHAR(50) NOT NULL , created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , updated_at TIMESTAMP NULL DEFAULT NULL , PRIMARY KEY (id), UNIQUE (email)) ENGINE = InnoDB;

Una de­scri­zio­ne det­ta­glia­ta della sintassi SQL verrà fornita nel paragrafo dedicato alle in­ter­ro­ga­zio­ni del database.

Un click su “Save“ salva le im­po­sta­zio­ni prese. La tabella users viene mostrata nel pannello di na­vi­ga­zio­ne sotto il database test.

Gestire le tabelle

Per gestire una tabella creata, cliccate sul nome della tabella nel pannello di na­vi­ga­zio­ne. ph­p­MyAd­min vi indica nella tab “Browse” (Vi­sua­liz­za) una pa­no­ra­mi­ca dei dati salvati nella ri­spet­ti­va tabella. Nel caso della nostra tabella di esempio, la richiesta non dà nessun risultato, visto che non ci sono ancora dei record nella tabella users.

Per la gestione di tabelle di dati avete a di­spo­si­zio­ne nella barra del menu diverse tab. Se volete cambiare la struttura di una tabella di dati, se­le­zio­na­te la tab “Structure”. Ag­giun­ge­te nuovi record alla tabella dalla tab “Insert” (aggiungi). Inoltre ph­p­MyAd­min consente di ricercare dati nelle tabelle, di gestire i permessi e di esportare i record o di im­por­tar­li da altre tabelle.

Mo­di­fi­ca­re la struttura della tabella

Se volete ag­giun­ge­re suc­ces­si­va­men­te altre colonne alla vostra tabella, eliminare quelle già esistenti o mo­di­fi­car­le, spo­sta­te­vi sulla tab “Structure”.

Ag­giun­ge­te le colonne tramite il pulsante “Add columns”, indicando il numero de­si­de­ra­to delle nuove colonne, così come la ri­spet­ti­va posizione.

Nella seguente il­lu­stra­zio­ne viene aggiunta una nuova colonna dopo updated_at:

Se volete eliminare le colonne già esistenti, se­le­zio­na­te la spunta nel riquadro apposito e cliccate infine su “Drop”.

La modifica di una colonna avviene tramite il pulsante “Change“. Arrivate a una finestra di modifica che as­so­mi­glia a quella che già conoscete per la creazione delle tabelle:

Le modifiche alla struttura delle tabelle possono portare in alcune cir­co­stan­ze alla perdita di dati. Prima di elaborare o eliminare le colonne delle tabelle già esistenti, dovreste in ogni caso creare un backup del vostro database. Spo­sta­te­vi nella tab “Export” (Esporta), scegliete il formato di dati de­si­de­ra­to per il backup e con­fer­ma­te con “Go”. Si apre una finestra di dialogo in cui il vostro browser richiede la de­sti­na­zio­ne di ar­chi­via­zio­ne per il download. Rap­pre­sen­ta un’al­ter­na­ti­va al backup del database da ph­p­MyAd­min il programma di sicurezza gratuito My­SQL­Dum­per.

Creare i record della tabella

Per riempire la vostra tabella con i dati tramite ph­p­MyAd­min, avete due pos­si­bi­li­tà: importare i record da un file esterno (ad esempio da un backup) o creare ma­nual­men­te le voci della tabella. Se­le­zio­na­te la tabella di esempio users e cliccate sulla tab “Insert“ (aggiungi).

ph­p­MyAd­min vi mostra la seguente maschera di in­se­ri­men­to dati:

Sotto “Column“ sono elencate le colonne che abbiamo definito per la tabella users. Sotto “Type” trovate in­for­ma­zio­ni su quale tipo di dati è previsto per la relativa colonna, insieme a un limite di caratteri ap­pros­si­ma­ti­vo indicato tra parantesi. Tra­la­scia­mo la sezione “Function” e andiamo di­ret­ta­men­te su “Value”, dove definiamo i valori per le singole colonne della nostra tabella di esempio.

Nel paragrafo pre­ce­den­te abbiamo con­fi­gu­ra­to la tabella users di modo tale che i campi dei dati per le colonne id, created_at e updated_at attingano au­to­ma­ti­ca­men­te ai contenuti del Database Ma­na­ge­ment System. Nella colonna id as­se­gnia­mo tramite AUTO_INCREMENT un numero di iden­ti­fi­ca­zio­ne pro­gres­si­vo per ogni nuova voce. Il campo created_at viene dotato au­to­ma­ti­ca­men­te di un timestamp attuale e per updated_at il sistema assegna prima di tutto un valore standard NULL. Così dobbiamo inserire ma­nual­men­te i dati per le colonne forename, surname, email e password. Chiariamo questo concetto uti­liz­zan­do dei dati utente fittizi:

  • forename: John
  • surname: Doe
  • email: john@doe.com
  • password: qwertz

Con un click su “Go” riportate i dati nella vostra tabella. ph­p­MyAd­min passa au­to­ma­ti­ca­men­te alla tab “SQL” e vi mostra l’ope­ra­zio­ne del database eseguita come statement secondo la sintassi SQL:

INSERT INTO users (id, forename, surname, email, password, created_at, updated_at) VALUES (NULL, 'John', 'Doe', 'john@doe.com', 'qwertz', CURRENT_TIMESTAMP, NULL);

Prin­ci­pal­men­te tutte le ope­ra­zio­ni del database, che eseguite fa­cil­men­te tramite ph­p­MyAd­min at­tra­ver­so un’in­ter­fac­cia grafica, si possono anche annotare nel lin­guag­gio di database SQL. Tale pro­ce­di­men­to è un processo standard nell’ambito dello sviluppo web.

Le così chiamate queries SQL (in­ter­ro­ga­zio­ni) si trovano nel codice sorgente di tutte le ap­pli­ca­zio­ni web dinamiche e con­sen­to­no al web server l’in­te­ra­zio­ne con il database. Il lin­guag­gio di database SQL si basa quindi su comandi, ad esempio per ri­chia­ma­re i dati e uti­liz­zar­li nell’ambito dell’ese­cu­zio­ne dei programmi. I comandi SQL più im­por­tan­ti, cioè SELECT, INSERT, DELETE e UPDATE, così come la sintassi delle ope­ra­zio­ni di database es­sen­zia­li, verranno trattati nei prossimi paragrafi del nostro tutorial MySQL.

Prima di tutto riempiamo la nostra tabella users con altri dati utente e guardiamo il riepilogo generale della tabella nella tab “Browse”:

Un click sul nome della colonna ordina la tabella secondo la sequenza de­si­de­ra­ta.

Stabilire una con­nes­sio­ne al database

Dopo aver riempito la tabella di esempio users con i record, nei prossimi paragrafi vedremo come si possono in­ter­ro­ga­re i dati immessi tramite PHP at­tra­ver­so il web server Apache.

Per fare ciò, sta­bi­lia­mo nel primo passaggio una con­nes­sio­ne al database. Nel lin­guag­gio PHP sono a di­spo­si­zio­ne tre in­ter­fac­ce: MySQL Extension, MySQL Improved Extension (MySQLi) e PHP Data Objects (PDO).

  • MySQL Extension: MySQL Extension è un’in­ter­fac­cia mi­glio­ra­ta di MySQL che prima era molto popolare, ma oggi risulta antiquata. Rispetto a MySQLi e PDO la vecchia MySQL Extension ha lo svan­tag­gio di non sup­por­ta­re né i prepared sta­te­men­ts, né i parametri indicati.
  • MySQLi: MySQLi è una versione mi­glio­ra­ta della classica esten­sio­ne PHP per l’accesso ai database MySQL. L’in­ter­fac­cia lavora sia in maniera pro­ce­du­ra­le che orientata agli oggetti. Viene uti­liz­za­ta solo sui database MySQL e MariaDB.
  • PDO: nel caso di PHP Data Objects (PDO) si tratta di un’in­ter­fac­cia orientata agli oggetti che mette a di­spo­si­zio­ne un livello di astra­zio­ne per l’accesso ai dati. Così non solo si possono integrare tramite PDO i database MySQL, ma anche altri sistemi di database come Post­gre­SQL, Oracle, MSSQL o SQLite nel PHP.

Di seguito ci li­mi­te­re­mo alle con­nes­sio­ni database tramite PDO.

Per poter ef­fet­tua­re delle richieste al database nell’ambito di uno script PHP, bisogna prima di tutto au­ten­ti­fi­car­si. Una con­nes­sio­ne database tramite PDO viene in­stau­ra­ta ri­cor­ren­do alla seguente stringa di codice:

<?php
$pdo = new PDO('DSN', 'username', 'password');
?>

Si consiglia di inserirla all’inizio di ogni script che contiene ope­ra­zio­ni del database.

Uti­liz­zia­mo la parola chiave PHP new, per creare un’istanza della classe base PDO. I suoi co­strut­to­ri prevedono es­sen­zial­men­te tre parametri, cioè il Data Source Name (DSN), un nome utente e la password per il database, se presente. Nel nostro caso il DSN è composto dai seguenti parametri:

  • Driver PDO del database: mysql
  • Server database (host=): localhost
  • Nome del database (dbname=): test
  • Set di caratteri (charset=): utf8

Se non avete definito i dati di accesso per il vostro database, uti­liz­za­te il nome utente root e lasciate vuoto lo spazio riservato alla password:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
?>

La con­nes­sio­ne al database viene salvata in una variabile $pdo, così nel corso del codice del programma potete rimandare alla con­nes­sio­ne del database.

Se è stata stabilita una con­nes­sio­ne al database, potete inviare con il codice di script seguente un numero il­li­mi­ta­to di richieste al database. Una volta terminato lo script, si in­ter­rom­pe anche la con­nes­sio­ne al database.

In­ter­ro­ga­re i dati con SELECT, INSERT, UPDATE e DELETE

Per ri­chia­ma­re i dati dal nostro database, ri­cor­ria­mo al lin­guag­gio di database SQL, che si basa se­man­ti­ca­men­te sull’inglese e viene mantenuto vo­lon­ta­ria­men­te semplice. La sintassi SQL è am­pia­men­te au­toe­spli­ca­ti­va.

Nel lin­guag­gio SQL si lavora con statement, al­tri­men­ti de­no­mi­na­ti come in­ter­ro­ga­zio­ni o richieste.

Una semplice query SELECT è composta, ad esempio, dai seguenti com­po­nen­ti:

SELECT colonna FROM tabella;

Prima di tutto definite il comando SQL SELECT e le ri­spet­ti­ve colonne e tabelle alle quali deve fare ri­fe­ri­men­to il comando; un punto e virgola chiude lo statement.

Inoltre avete la pos­si­bi­li­tà di ampliare lo statement con una con­di­zio­ne opzionale e una funzione di clas­si­fi­ca­zio­ne o di rag­grup­pa­men­to:

SELECT colonna FROM tabella WHERE condizione ORDER BY ordine_della_sequenza;

In questo caso per con­ven­zio­ne i comandi SQL si scrivono in maiuscolo, mentre il nome del database, delle tabelle e degli altri campi sono minuscoli, per motivi di leg­gi­bi­li­tà. SQL è prin­ci­pal­men­te un lin­guag­gio non for­mat­ta­to e non fa perciò dif­fe­ren­za tra maiuscolo e minuscolo.

Se uti­liz­za­te i nomi delle tabelle e delle colonne che cor­ri­spon­do­no alle parole chiave SQL già definite (non con­si­glia­to), dovete inserire i backtick (``).

Vi chiariamo la sintassi di semplici statement SQL portando ad esempio i comandi SELECT, INSERT, UPDATE e DELETE.

SELECT

Uti­liz­za­te il comando SELECT per ri­chia­ma­re le serie di dati (rows) se­le­zio­na­te da un numero in­de­fi­ni­to di tabelle. Se ad esempio volete che vengano vi­sua­liz­za­ti sul browser il nome, il cognome e gli indirizzi e-mail di tutti gli utenti presenti nella tabella di esempio, create nella cartella htdocs del vostro ambiente XAMPP un nuovo file PHP text.php e inserite il seguente script:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT forename, surname, email FROM users";
foreach ($pdo->query($sql) as $row) {
    echo $row['forename'] . " " . $row['surname'] . "<br />";
    echo "E-Mail: " . $row['email'] . "<br /><br />";
}
?>

Il codice di esempio si legge così: prima iniziamo lo script con il tag di apertura PHP <?php. Nella riga 2 in­stau­ria­mo la con­nes­sio­ne al nostro database test su localhost e la salviamo nella variabile $pdo. Lo statement SQL con il comando SELECT si trova nella riga 3. Qui si rimanda al Database Ma­na­ge­ment System per ri­chia­ma­re le colonne forename, surname e email dalla tabella users. Salviamo questo statement nelle variabili $sql.

Le righe da 4 a 7 indicano un ciclo foreach, che ci offre la pos­si­bi­li­tà di iterare un array qualsiasi, cioè di at­tra­ver­sa­re per gradi una struttura di dati. Quale array vogliamo iterare e come i dati richiesti devono essere salvati, lo definiamo tra parentesi dopo il costrutto foreach:

$pdo->query($sql) as $row

La variabile $pdo prende in con­si­de­ra­zio­ne il database de­si­de­ra­to tramite la con­nes­sio­ne definita nella riga 2, che inviamo con la funzione query() salvata nello statement SQL della variabile $sql.

Il web server annuncia così le colonne forename, surname e email della tabella users dal database test e at­tra­ver­sa, nell’ambito di un ciclo foreach, ogni singola riga della tabella. La parola chiave PHP as definisce dove devono essere salvati i dati nella variabile array $row.

Nel primo passaggio del ciclo foreach questo array si presenta così.

$row = array (
    forename => John,
    surname => Doe,
    email => john@doe.com
)

Parte del ciclo foreach è inoltre nell’esempio attuale la ri­pro­du­zio­ne del testo tramite echo in ogni passaggio del ciclo. At­tra­ver­sia­mo così ogni riga della tabella users sin­go­lar­men­te, leggiamo i dati creati per le colonne definite nello statement SQL e le vi­sua­liz­zia­mo sul browser.

Se tutte le colonne devono essere lette da una tabella di dati, uti­liz­za­te nello statement SQL l’asterisco (*) come se­gna­po­sto.

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT * FROM users";
foreach ($pdo->query($sql) as $row) {
    echo $row['forename'] . " " . $row['surname'] . "<br />";
    echo "E-Mail: " . $row['email'] . "<br />";
    echo "Created at: " . $row['created_at'] . "<br /><br />";
}
?>

Così abbiamo la pos­si­bi­li­tà di uti­liz­za­re tutti i dati creati nella tabella users nell’ambito dello script. Nel seguente screen­shot è stato aggiunto nel testo un timestamp relativo alla creazione del record:

In entrambi gli esempi pre­sen­ta­ti pre­ce­den­te­men­te il web server re­sti­tui­sce i dati utente nell’ordine in cui sono stati inseriti nella tabella users (in base all’ID). Se volete vi­sua­liz­za­re i dati secondo un certo ordine, definite la sequenza con la parola chiave SQL ORDER BY. Nel seguente esempio i dati vengono vi­sua­liz­za­ti in ordine al­fa­be­ti­co per nome:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT forename, surname, email FROM users ORDER BY forename";
foreach ($pdo->query($sql) as $row) {
    echo $row['forename'] . " " . $row['surname'] . "<br />";
    echo "E-Mail: " . $row['email'] . "<br /><br />";
}
?>

INSERT

La creazione di record nel database raramente avviene ma­nual­men­te tramite ph­p­MyAd­min. Di solito i dati vengono scritti nel database nell’ambito dell’ese­cu­zio­ne degli script da parte del web server, ad esempio nel momento in cui un utente compila un modulo online su un sito o un cliente lascia un commento su un negozio online. In entrambi i casi viene uti­liz­za­to in back­ground il comando SQL INSERT. Uno statement SQL con il comando INSERT viene creato secondo lo schema seguente:

INSERT INTO tabella (colonna1, colonna2, colonna3) VALUES (valore1, valore2, valore3);

E si legge così: apri la tabella citata e aggiungi i valori 1, 2 e 3 nelle colonne 1, 2 e 3.

Un semplice script PHP per ag­giun­ge­re alla nostra tabella di esempio users un altro record, potrebbe essere così:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "INSERT INTO users (forename, surname, email, password) 
VALUES ('Paddy', 'Irish', 'paddy@irish.com', 'qaywsx')";
if ($pdo->exec($sql) === 1)
    echo "New record created successfully";
?>

Prima di tutto sta­bi­lia­mo la con­nes­sio­ne al database e sal­via­mo­la nella variabile $pdo. Infine definiamo lo statement SQL e sal­via­mo­lo nella variabile $sql. Nella riga 5 uti­liz­zia­mo l’operatore freccia (->), per ricorrere alla variabile $pdo ed eseguire con la funzione exec() lo statement SQL salvato su $sql.

Per essere sicuri che il nostro script aggiunga solo un record nella tabella users, ve­ri­fi­chia­mo il numero delle righe coinvolte con una con­di­zio­ne if. In questo modo la stringa New record created suc­ces­sful­ly viene vi­sua­liz­za­ta sul browser solo se il numero dei record aggiunti ammonta a 1. Se lo script viene eseguito di nuovo, non viene mostrato il messaggio. Le voci doppie im­pe­di­sco­no così che il valore email venga definito come UNIQUE.

Aprendo la pa­no­ra­mi­ca della nostra tabella di esempio users nel database test, notiamo che la tabella è stata ampliata del record 5. Il numero di iden­ti­fi­ca­zio­ne pro­gres­si­vo e il timestamp sono stati aggiunti in­ten­zio­nal­men­te in au­to­ma­ti­co.

UPDATE

Se volete ag­gior­na­re un record già esistente, uti­liz­za­te il comando SQL UPDATE secondo lo schema seguente:

UPDATE tabella SET colonna1 = valore1, colonna2 = valore2 WHERE colonna3 = valore3

Espresso a parole questo statement SQL significa: seleziona la tabella indicata e so­sti­tui­sci il valore nella colonna1 con il valore1 e il valore nella colonna2 con il valore2, a patto che la colonna3 contenga il valore3. At­ten­zio­ne: se di­men­ti­ca­te di inserire la con­di­zio­ne, MySQL so­vra­scri­ve i campi coinvolti in tutti i record.

Abbiamo così a che fare con uno statement SQL che collega un’ope­ra­zio­ne database a una con­di­zio­ne. Trasposto alla nostra tabella di esempio, l’indirizzo e-mail dell’utente John Doe si può ag­gior­na­re tramite lo script PHP seguente:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "UPDATE users 
SET email = 'john.doe@example.com', updated_at = NOW() WHERE id = 1";
if ($pdo->exec($sql) === 1)
    echo "Update successful";
?>

Nello statement SQL definiamo che il valore attuale nella colonna email deve essere so­sti­tui­to tramite il nuovo valore john.doe@example.com, a con­di­zio­ne che il valore nella colonna id sia pari a 1. Ag­gior­nia­mo così solo il record con la chiave primaria 1. Inoltre ag­gior­nia­mo nello stesso statement SQL il valore per la colonna updated_at con la funzione MySQL now(), che re­sti­tui­sce il timestamp attuale. Lo statement SQL viene eseguito come prima con la riga di codice $pdo->exec($sql) all’interno di una con­di­zio­ne if.

Se l’update è andato a buon fine, su ph­p­MyAd­min nella tab “Browse” vi verrà mostrata la tabella ag­gior­na­ta:

Nell’esempio abbiamo ag­gior­na­to un indirizzo e-mail e so­sti­tui­to il valore standard NULL nella colonna updated_at con un timestamp. Il comando UPDATE consente inoltre di tra­sfe­ri­re i valori da una colonna a un’altra. Ad esempio si può mettere in atto questa ope­ra­zio­ne, se ampliamo la nostra tabella di esempio users con la colonna email_re­gi­stra­tion. Ciò dà la pos­si­bi­li­tà di di­stin­gue­re tra due indirizzi e-mail: uno che è stato inviato durante la re­gi­stra­zio­ne e un indirizzo di contatto attuale che si può cambiare con il tempo. Prima di tutto entrambi gli indirizzi sono però uguali, di modo che i valori possono essere spostati da un campo a un altro. Così per co­min­cia­re creiamo la nuova colonna email_re­gi­stra­tion con ph­p­MyAd­min tramite il pulsante “Add columns” nella tab “Structure”:

Per tra­sfe­ri­re i valori, uti­liz­zia­mo il seguente statement UPDATE:

UPDATE users SET email_registration = email

Visto che vogliamo ag­gior­na­re tutti i record, non for­mu­lia­mo alcuna con­di­zio­ne per l’update

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "UPDATE users SET email_registration = email";
$affected = $pdo->exec($sql);
echo "$affected rows updated<br>";
?>

Se lo script viene eseguito dal web server, il Database Ma­na­ge­ment System ottiene i valori delle colonne email per tutti i record nella colonna email_re­gi­stra­tion.

DELETE

Si possono eliminare i record del database grazie al comando SQL DELETE, che uti­liz­za­te secondo il seguente schema:

DELETE FROM tabella WHERE colonna = valore

Se nel vostro database lavorate con ID, grazie a questi è possibile iden­ti­fi­ca­re i record da can­cel­la­re. Nel caso in cui vogliate ad esempio eliminare il record 5 dalla nostra tabella di esempio, procedete come segue:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "DELETE FROM users WHERE id = 5";
$affected = $pdo->exec($sql);
echo "$affected rows updated<br>";
?>

Il comando SQL DELETE elimina sempre un’intera riga di database. Se volete eliminare solo i valori in colonne spe­ci­fi­che di un record, rea­liz­za­te questo intento con uno statement UPDATE. Con UPDATE tabella SET colonna = NULL WHERE …potete assegnare a una colonna il valore NULL, premesso ov­via­men­te che per la colonna coinvolta abbiate con­sen­ti­to l’uso del valore NULL.

Prepared sta­te­men­ts

Con PDO si possono impostare le ope­ra­zio­ni del database come prepared statement. Queste “richieste già pronte” sono oggi normale routine nello sviluppo web e vengono sup­por­ta­te da tutti i moderni Database Ma­na­ge­ment System.

Negli esempi fatti fino a qui abbiamo pre­sen­ta­to i valori dei parametri di­ret­ta­men­te nello statement SQL. Invece, i prepared statement lavorano con i se­gna­po­sto, che vengono so­sti­tui­ti solo suc­ces­si­va­men­te con dei valori. Ciò consente al Database Ma­na­ge­ment System di ve­ri­fi­ca­re la validità dei parametri, prima di ela­bo­rar­li. I prepared statement offrono una pro­te­zio­ne efficace contro la SQL injection, a patto che vengano inseriti in maniera regolare nel codice sorgente. In questo schema di attacco gli hacker creano o cambiano i comandi SQL per arrivare ai dati sensibili, per so­vra­scri­ver­li o inserire abu­si­va­men­te i propri comandi in un sistema.

La SQL injection si basa su una vul­ne­ra­bi­li­tà co­no­sciu­ta quando si lavora con i database SQL: se gli input utente vengono tra­sfe­ri­ti ad esempio con parametri statici tramite $_GET, ciò offre la pos­si­bi­li­tà agli hacker di ar­ric­chi­re l’input con i me­ta­ca­rat­te­ri, com­por­tan­do così degli effetti in­de­si­de­ra­ti, a patto che giungano all’in­ter­pre­te SQL senza ma­sche­ra­tu­ra. Tramite richieste pa­ra­me­triz­za­te si può evitare ef­fi­ca­ce­men­te tutto questo. I prepared statement fungono così da template per i comandi SQL che possono essere co­mu­ni­ca­ti se­pa­ra­ta­men­te dai parametri effettivi al database. Questi con­va­li­da­no i dati trasmessi, ma­sche­ra­no au­to­ma­ti­ca­men­te i me­ta­ca­rat­te­ri e ag­giun­go­no i parametri al posto del se­gna­po­sto nello statement SQL.

I prepared statement offrono, oltre all’aspetto sicurezza, anche un mi­glio­ra­men­to della per­for­man­ce. Ciò è di­mo­stra­to quando lo stesso comando SQL deve venire eseguito in un ciclo con parametri diversi. Dopo che un prepared statement è stato ana­liz­za­to una volta, sarà presente nel sistema di database e deve essere sem­pli­ce­men­te eseguito con nuovi parametri. Così facendo, si possono no­te­vol­men­te ac­ce­le­ra­re le richieste che risultano più com­pli­ca­te.

Nel PDO i prepared statement vengono messi in atto grazie alla funzione prepare(), che prepara uno statement per l’ese­cu­zio­ne e re­sti­tui­sce un oggetto di statement. Come se­gna­po­sto per i ri­spet­ti­vi valori vengono uti­liz­za­ti un punto in­ter­ro­ga­ti­vo (?) o un parametro no­mi­na­ti­vo (Named Parameter).

Prepared statement con parametri senza nome

L’esempio di codice seguente indica l’ope­ra­zio­ne del database INSERT come prepared statement con parametri senza nome:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
// Preparare lo statement SQL
$statement = $pdo->prepare("INSERT INTO users (forename, surname, email, password) 
VALUES (?, ?, ?, ?)");
// Unire i parametri
$statement ->bindParam(1, $forename);
$statement ->bindParam(2, $surname);
$statement ->bindParam(3, $email);
$statement ->bindParam(4, $password);
// Inserire i record
$forename = "Anders";
$surname = "Andersen";
$email = "anders@andersen.com";
$password = "mnbvc";
if ($statement->execute())
    echo "New record $forename created successfully<br>";
$forename = "Matti";
$surname = "Meikäläinen";
$email = "matti@meikäläinen.com";
$password = "jhgfd";
// Visualizzazione dello stato
if ($statement->execute())
    echo "New record $forename created successfully<br>";
?>

Per prima cosa generiamo grazie alla funzione prepare() un oggetto di statement della richiesta de­si­de­ra­ta e lo salviamo nell’array $statement. Al posto di valori di parametri concreti viene usato il punto in­ter­ro­ga­ti­vo come se­gna­po­sto.

Se uno statement SQL comprende solo il se­gna­po­sto, i valori trasmessi se­pa­ra­ta­men­te devono essere collegati a questo nel codice seguente. Nel PHP si utilizza anche la funzione bindParam(). Uti­liz­zia­mo l’operatore freccia (->) per ricorrere al metodo dell’oggetto $statement e as­se­gnar­lo a questa variabile (1 cor­ri­spon­de al primo punto in­ter­ro­ga­ti­vo, 2 al secondo, ecc.).

Il template SQL così creato può ora essere eseguito con i parametri de­si­de­ra­ti tutte le volte che si vuole. Nell’esempio corrente definiamo i valori delle variabili per due record. L’ese­cu­zio­ne dello statement SQL già pronto avviene per ogni record tramite execute().

I prepared statement con parametri no­mi­na­ti­vi

Più chiaro di un se­gna­po­sto con il punto in­ter­ro­ga­ti­vo sono i parametri no­mi­na­ti­vi. Si tratta così di parametri per­so­na­liz­za­ti a cui si può dare un nome, se ne­ces­sa­rio, secondo il seguente schema:

:esempio

I parametri no­mi­na­ti­vi non devono però contenere spazi vuoti o trattini (-). Uti­liz­za­te al loro posto un un­der­sco­re (_).

Il seguente esempio indica l’ope­ra­zio­ne di database INSERT come prepared statement con parametri no­mi­na­ti­vi:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
// Preparare lo statement SQL
$statement = $pdo->prepare("INSERT INTO users (forename, surname, email, password) 
VALUES (:forename, :surname, :email, :password)");
// Unire i parametri
$statement ->bindParam(':forename', $forename);
$statement ->bindParam(':surname', $surname);
$statement ->bindParam(':email', $email);
$statement ->bindParam(':password', $password);
// Inserire i record
$forename = "Anders";
$surname = "Andersen";
$email = "anders@andersen.com";
$password = "mnbvc";
if ($statement->execute())
    echo "New record $forename created successfully<br>";
$forename = "Matti";
$surname = "Meikäläinen";
$email = "matti@meikäläinen.com";
$password = "jhgfd";
$statement->execute();
// Visualizzazione dello stato
if ($statement->execute())
    echo "New record $forename created successfully<br>";
?>

Nel prepared statement si trovano i parametri no­mi­na­ti­vi :forename, :surname, :email e :password, che uniamo tramite bindParam() alle variabili $forename, $surname, $email e $password. Nell’esempio appena riportato abbiamo scelto di far coin­ci­de­re il nome dei parametri e delle variabili con quello uti­liz­za­to per le varie colonne nella nostra tabella di esempio. Questo però non avviene in au­to­ma­ti­co. Una de­no­mi­na­zio­ne unitaria è tuttavia con­si­glia­ta, così da avere un codice sorgente di facile lettura. L’as­se­gna­zio­ne dei valori delle variabili e l’ese­cu­zio­ne degli statement SQL avviene ana­lo­ga­men­te all’esempio proposto pre­ce­den­te­men­te.

Funzioni per la data e il tempo su MySQL

MySQL e MariaDB sup­por­ta­no diverse funzioni per lavorare con le in­di­ca­zio­ni temporali e la data. Trovate una lista completa a questo indirizzo. Nel nostro tutorial MySQL per prin­ci­pian­ti ci limitiamo a esporre solo alcuni di questi.

Funzione per la data e il tempo De­scri­zio­ne
CURRENT_TIMESTAMP() / NOW() Nell’esempio del comando SQL UPDATE avete già in­con­tra­to la funzione NOW(). In questo caso si tratta solo di un sinonimo della funzione CURRENT_TIMESTAMP(). La funzione viene applicata sempre, quando nell’ambito di un’ope­ra­zio­ne di database deve essere inserita la data corrente com­pren­si­va di ora.
CURDATE() / CURRENT_DATE() La funzione CURDATE() fornisce la data corrente.
CURTIME() / CURRENT_TIME() La funzione CURTIME() fornisce l’ora attuale.
DAY() / DAYO­F­MONTH() Fornisce il giorno del mese (0–31); necessita di una data o di un timestamp come base di partenza.
DAYOFWEEK() Fornisce il giorno della settimana (1 = domenica); necessita di una data o di un timestamp come base di partenza.
MONTH() Fornisce il mese (1–12); necessita di una data o di un timestamp come base di partenza.
YEAR() Fornisce un anno (1000–9999, 0); necessita di una data o di un timestamp come base di partenza.
DATE() Estrae la data dall’ora o dalla data indicata; necessita di una data o di un timestamp come base di partenza.
TIME() Estrae la data dall’ora o dalla data indicata; necessita di una data o di un timestamp come base di partenza.
DATE_FORMAT() Formatta l’in­di­ca­zio­ne dell’ora o della data in base ai parametri inseriti; necessita di una data o di un timestamp come base di partenza.

Un possibile scenario di ap­pli­ca­zio­ne per le funzioni per l’ora e la data su MySQL è dato ad esempio dalle in­ter­ro­ga­zio­ni del database, in cui devono essere ana­liz­za­ti tutti i record che sono stati creati in un giorno preciso.

Il seguente script fornisce tutti i record della nostra tabella di esempio users, che è stata creata oggi:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT forename, surname, email FROM users WHERE DATE(created_at) = CURDATE()";
foreach ($pdo->query($sql) as $row) {
    echo $row['forename'] . " " . $row['surname'] . "<br />";
    echo "E-Mail: " . $row['email'] . "<br /><br />";
}
?>

Per vi­sua­liz­za­re solo le voci di oggi, uti­liz­zia­mo la seguente con­di­zio­ne nello statement SQL:

DATE(created_at) = CURDATE()

Per prima cosa estraiamo la data con l’aiuto della funzione DATE() dal timestamp salvato nella colonna created_at e lo sin­cro­niz­zia­mo con la data attuale nel prossimo passaggio. Il comando SELECT seleziona così solo le voci in cui il timestamp coincide con la data corrente.

In al­ter­na­ti­va possiamo scegliere la voce che abbiamo ag­gior­na­to al 16.12.2016. Così dobbiamo solo adattare la con­di­zio­ne del nostro statement SQL:

SELECT forename, surname, email FROM users WHERE DATE(updated_at) = '2016-12-16'

In questo caso l’in­di­ca­zio­ne della data estratta dal timestamp viene ag­gior­na­ta con una data concreta. Inoltre le richieste vengono de­li­mi­ta­te al­l'in­ter­no di un  anno, di un mese o di un giorno in par­ti­co­la­re.

Il seguente statement fa ri­fe­ri­men­to a tutte le voci nella tabella users, che sono state create a dicembre:

SELECT forename, surname, email FROM users WHERE month(created_at) = 12";

Oltre al segno dell’uguale, SQL supporta anche nelle con­di­zio­ni i seguenti operatori di confronto:

Operatore di confronto De­scri­zio­ne
= È uguale
È più piccolo di
È più grande di
<= È più piccolo o uguale
>= È più grande o uguale
!= Non è uguale

Inoltre si possono collegare più con­di­zio­ni con gli operatori logici:

Operatore logico De­scri­zio­ne
OR o || O logica
AND o && E logica

Il seguente statement seleziona ad esempio tutte le voci che sono state create dopo febbraio e prima di aprile:

SELECT forename, surname, email FROM users WHERE MONTH(created_at) > 2 AND MONTH(created_at) < 4";

Finora abbiamo salvato nel nostro database le in­di­ca­zio­ni della data e quelle temporali nel formato indicato. Con MySQL e MariaDB non siete però obbligati ad uti­liz­za­re proprio quello. La funzione DATE_FORMAT() vi dà la pos­si­bi­li­tà di for­mat­ta­re i dati e l’ora a vostro pia­ci­men­to con i parametri opzionali.

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT forename, surname, DATE_FORMAT(created_at,'%D %M %Y') AS created_at FROM users WHERE id=1";
    foreach ($pdo->query($sql) as $row) {
    echo $row['forename'] . " " . $row['surname'] . " your profile was created at: " . $row['created_at'] . ".<br /><br />";
}
?>

La tabella seguente indica i possibili parametri per la funzione DATE_FORMAT() in base alla do­cu­men­ta­zio­ne MySQL.

Parametro De­scri­zio­ne Dominio dei valori/Esempi
%c In­di­ca­zio­ne numerica del mese con 1 o 2 cifre Da 0 a 12
%d In­di­ca­zio­ne numerica del giorno in un mese con 2 cifre Da 00 a 31
%e In­di­ca­zio­ne numerica del giorno in un mese con 1 o 2 cifre Da 0 a 31
%H Vi­sua­liz­za­zio­ne dell’ora a 2 cifre Da 00 a 23
%i In­di­ca­zio­ne numerica dei minuti a 2 cifre Da 00 a 59
%j In­di­ca­zio­ne numerica del giorno in un anno con 3 cifre Da 001 a 366
%k In­di­ca­zio­ne numerica dell’ora con 1 o 2 cifre Da 0 a 23
%M Nome del mese nello schema di ri­fe­ri­men­to corrente January, February, March, ecc.
%m In­di­ca­zio­ne numerica del mese con 2 cifre Da 00 a 12
%s In­di­ca­zio­ne numerica dei secondi con 2 cifre Da 00 a 59
%T Ora nel formato a 24 ore (ab­bre­via­to in '%H:%i:%S') HH:MM:SS
%u Numero della settimana in un anno, co­min­cian­do da lunedì Da 00 a 53
%W Nome del giorno della settimana nello schema di ri­fe­ri­men­to corrente Monday, Tuesday, ecc.
%w Giorno della settimana 0 = Domenica, 6 = Sabato
%Y Numero dell‘anno a 4 cifre Per esempio 2016
%y Numero dell‘anno a 2 cifre Per esempio 16

Messaggi di errore MySQL

Se non si può eseguire uno script come si vorrebbe, ciò dipende so­li­ta­men­te dagli errori sin­tat­ti­ci nel codice sorgente o dalle tabelle, colonne e variabili che pre­sen­ta­no dei nomi errati. Il server database non mostra però per forza un messaggio di errore. Spesso non si ottiene il risultato sperato e non seguono neanche in­di­ca­zio­ni di un’ope­ra­zio­ne errata.

Con errorInfo() PDO mette perciò a di­spo­si­zio­ne una funzione con cui si può ri­chia­ma­re in modo mirato in­for­ma­zio­ni avanzate sugli errori ve­ri­fi­ca­ti­si nell’ultima ope­ra­zio­ne di database, ad esempio per vi­sua­liz­zar­le sul browser.

Nel seguente script per l’ag­gior­na­men­to dell’indirizzo e-mail viene uti­liz­za­ta la funzione errorInfo() combinata a un ciclo if, la cui con­di­zio­ne è la corretta ese­cu­zio­ne dello statement SQL. Se questo viene eseguito senza errori, il web server re­sti­tui­sce la stringa Update suc­ces­sful. Al­tri­men­ti viene eseguito il codice pre­sen­ta­to sotto else.

Nell’esempio attuale in­for­mia­mo l’utente che si è ve­ri­fi­ca­to un errore SQL e forniamo, tramite errorInfo(), lo statement SQL e le in­for­ma­zio­ni avanzate sugli errori:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$statement = $pdo -> prepare("UPDATE user SET password = :password, updated_at = NOW() WHERE id = 1");
$statement ->bindParam(':password', $password);
$password = "YXDli_89%s";
if ($statement ->execute()){
    echo "Update successful";
} else {
    echo "SQL Error <br />";
    echo $statement->queryString."<br />";
    echo $statement->errorInfo()[2];
}
?>

Se eseguiamo lo script dal web server, riceviamo le seguenti in­for­ma­zio­ni:

SQL Error 
UPDATE user SET password = :password, updated_at = NOW() WHERE id = 1
Table 'test.user' doesn't exist

Il comando SQL UPDATE si riferisce a una tabella con il nome user, ma il nome che abbiamo dato alla nostra tabella di esempio è users. Il server SQL non riesce perciò a trovare la tabella richiesta e re­sti­tui­sce il messaggio Table 'test.user' doesn't exist. La causa dell’errore è così solamente un errore di di­gi­ta­zio­ne nello statement SQL; errore fa­cil­men­te ri­sol­vi­bi­le.

Nel caso dei valori che vengono dati dalla funzione errorInfo() si tratta di un array che comprende tre elementi:

[0] = Il codice di errore SQL

[1] = I driver specifici del codice di errore

[2] = I driver specifici del messaggio di errore

Quali in­for­ma­zio­ni ri­chia­ma­te tramite errorInfo(), lo definite tramite l’in­di­ca­zio­ne dell’elemento de­si­de­ra­to tra parentesi quadre.

Nella prassi le in­for­ma­zio­ni det­ta­glia­te sugli errori vengono però vi­sua­liz­za­te raramente sul browser. Gli utenti hanno poche pos­si­bi­li­tà di riuscire a risolvere il problema con queste in­for­ma­zio­ni. Invece, i po­ten­zia­li hacker sfruttano questi messaggi di errore per in­ter­cet­ta­re le richieste SQL e rilevare così le vul­ne­ra­bi­li­tà di un’ap­pli­ca­zio­ne. Perciò si consiglia di informare gli utenti in maniera generica in caso di errori e di salvare in­ter­na­men­te le in­for­ma­zio­ni relative a degli errori concreti. Si può ad esempio procedere nel modo seguente:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$statement = $pdo->prepare("UPDATE user SET password = :password, updated_at = NOW() WHERE id = 1");
$statement->bindParam(':password', $password);
$password = "YXDli_89%s";
if ($statement->execute()) {
     echo "Update successful";
} else {
    echo "Purtroppo si è verificato un errore nell’aggiornamento della password. Si prega di contattare il nostro amministratore all’indirizzo: admin@sito.it.";
    $error = DATE("Y-m-d H:i:s") . " - " . __FILE__ . " - " . $statement->queryString . " - " . $statement->errorInfo()[2] . " \r\n";
    file_put_contents("sqlerrors.log", $error, FILE_APPEND); 
}
?>

Al posto di re­sti­tui­re sul browser un valore di ritorno della funzione errorInfo() tramite echo, lo salviamo nella variabile $error con il timestamp attuale, il percorso al file e con lo statement SQL coinvolto.

La funzione PHP DATE() re­sti­tui­sce il timestamp UNIX nel formato inserito. La co­sid­det­ta costante magica __FILE__ ci fornisce l’intero percorso per il file test.php. Lo statement SQL corrente viene ri­chia­ma­to nell’esempio pre­ce­den­te tramite $statement->que­ry­String. Infine i dati salvati su $error vengono me­mo­riz­za­ti come testo nel file sqlerrors.log, contenuto nella cartella htdocs, grazie alla funzione file_put_contents().

JOIN

Come scritto nel paragrafo sui database re­la­zio­na­li, possiamo in­ter­ro­ga­re con­tem­po­ra­nea­men­te i dati di più tabelle. Dopo aver preso con­fi­den­za con le ope­ra­zio­ni di database fon­da­men­ta­li, ri­pren­dia­mo­le, perché vi di­mo­stria­mo a questo punto come collegare le diverse tabelle del vostro database in un join (in italiano “Con­nes­sio­ne“ o “Unione“).

L’unione delle tabelle nell’ambito di una richiesta avviene grazie al comando SQL JOIN. Così vengono collegate due o più tabelle nor­ma­liz­za­te at­tra­ver­so delle colonne in comune. Ciò si può rea­liz­za­re tramite chiavi esterne.

Di­mo­stria­mo il col­le­ga­men­to delle tabelle del database con il seguente esempio:

La tabella indica una selezione delle nostre canzoni preferite degli anni 60 e dovrebbe servire a rap­pre­sen­ta­zio­ne di un esempio negativo di una struttura del database sbagliata.

Di primo acchito si nota che le tabelle pre­sen­ta­no in­nu­me­re­vo­li campi di dati ri­don­dan­ti. Ri­sol­via­mo la si­tua­zio­ne, sud­di­vi­den­do i dati nell’ambito della nor­ma­liz­za­zio­ne in tabelle separate e col­le­gan­do­li con chiavi esterne.

Forme normali

Una buona struttura del database si con­trad­di­stin­gue per via della poca ri­don­dan­za. Si possono evitare le voci doppie tramite la nor­ma­liz­za­zio­ne delle tabelle. All’interno del modello di database re­la­zio­na­le si sono affermate tre forme normali che si basano le une sulle altre e che pre­sta­bi­li­sco­no regole fisse per la strut­tu­ra­zio­ne ottimale dei dati.

Prima forma normale

Una tabella cor­ri­spon­de alla prima forma normale, quando tutti i valori degli attributi sono atomici. I valori di un attributo si con­si­de­ra­no atomici, quando con­ten­go­no solo un’in­for­ma­zio­ne. Questo concetto si può chiarire grazie al nostro esempio negativo.

Con­si­de­ra­te, ad esempio, le colonne album_title e interpret nella tabella album. Al posto di eseguire le in­for­ma­zio­ni contenute nella tabella in una colonna separata, le abbiamo rese vo­lu­ta­men­te facili e abbiamo annotato le in­for­ma­zio­ni sull’anno di pub­bli­ca­zio­ne di un album e la presenza del disco sem­pli­ce­men­te tra parentesi tonde, dietro il titolo dell’album e le in­di­ca­zio­ni sull’in­ter­pre­te. Ma pagheremo le con­se­guen­ze della nostra scelta in un secondo momento, quando ad esempio vogliamo ri­chie­de­re tutti i titoli che sono stati pub­bli­ca­ti in un anno in par­ti­co­la­re.

Vi con­si­glia­mo perciò di creare le tabelle secondo le regole della prima forma normale. Per la nostra tabella di esempio sarebbe così:

Tutti i dati sono ora separati e ana­liz­za­bi­li quindi senza ulteriori indugi. La nostra tabella però contiene ancora, come in pre­ce­den­za, delle ri­don­dan­ze. Vi mostriamo nei prossimi paragrafi come si possono eliminare.

Seconda forma normale

Una tabella cor­ri­spon­de alla seconda forma normale quando tutte le con­di­zio­ni della prima forma normale sono sod­di­sfat­te e ogni attributo non chiave è, da un punto di vista delle funzioni, com­ple­ta­men­te di­pen­den­te dalla chiave primaria globale delle tabelle.

Spesso le tabelle sono composte solo da una colonna che svolge la funzione di chiave primaria. Le tabelle di questo tipo cor­ri­spon­do­no au­to­ma­ti­ca­men­te a quelle della seconda forma normale, quando le con­di­zio­ni della prima forma normale sono sod­di­sfat­te. A volte emerge che la chiave primaria di una tabella è composta da due colonne, come è il caso nella nostra tabella di esempio.

Per aprire un titolo de­si­de­ra­to dalla colonna title della tabella title, ci ser­vi­reb­be album_id come anche il numero del titolo abbinato nella colonna track. Il titolo Sympathy for the Devil si può aprire ad esempio tramite la chiave primaria album_ID = 3 AND track = 1. Abbiamo quindi a che fare con una chiave primaria composta, ne­ces­sa­ria però solo per le richieste che si ri­fe­ri­sco­no alla colonna title. Le colonne album_title, released, interpret e years_active dipendono esclu­si­va­men­te da album_id. Per queste colonne non esiste così nessuna di­pen­den­za fun­zio­na­le della chiave primaria in comune. Le con­di­zio­ni della seconda forma normale non sono perciò sod­di­sfat­te.

Ciò si può cambiare, me­mo­riz­zan­do la colonna title in una nuova tabella e col­le­gan­do­la tramite una chiave esterna (album_id) con la tabella di de­sti­na­zio­ne:

La tabella elaborata album comprende solo un’unica chiave primaria e soddisfa così au­to­ma­ti­ca­men­te le con­di­zio­ni della seconda forma normale. La nuova tabella title contiene solo la colonna non chiave title, che è da entrambi le parti di­pen­den­te dalla chiave primaria (album_id e track), dal punto di vista fun­zio­na­le, e cor­ri­spon­de così anche a quella della seconda forma normale.

Ma anche nella seconda forma normale la nostra tabella di dati album comprende voci ri­don­dan­ti.

Terza forma normale

Se una tabella dovesse cor­ri­spon­de­re alla terza forma normale, tutte le con­di­zio­ni della seconda forma normale devono essere sod­di­sfat­te (e quindi anche quella della prima forma normale). Inoltre vale la regola che nessun attributo non chiave deve trovarsi in una di­pen­den­za tran­si­ti­va a un attributo chiave. Questa con­di­zio­ne sembra com­pli­ca­ta, ma si può chiarire con poche parole: sussiste sempre una di­pen­den­za tran­si­ti­va quando un attributo non chiave dipende da un altro attributo non chiave.

Proprio questo coincide con la nostra tabella di esempio album. Qui si trovano le colonne interpret e years_active. Mentre l’in­ter­pre­te si può co­mu­ni­ca­re tramite album_id, l’in­di­ca­zio­ne dell’anno dell’uscita dell’album è di nuovo di­pen­den­te dall’in­ter­pre­te ed è quindi tran­si­ti­vo dall’ album_id. Questo porta con sé uno svan­tag­gio, ovvero che il Database Ma­na­ge­ment System salva au­to­ma­ti­ca­men­te un valore ri­don­dan­te nella colonna years_active, ogni qualvolta che viene inserito un nuovo album di un in­ter­pre­te già ri­pro­dot­to.

Per sod­di­sfa­re le con­di­zio­ni della terza forma normale e quindi rimuovere tutte le ri­don­dan­ze dalla nostra tabella, dobbiamo me­mo­riz­za­re anche la colonna interpret com­pren­si­va di years_active in una tabella separata e col­le­gar­la tramite chiave esterna (interpret_id) con la tabella di de­sti­na­zio­ne album.

Otteniamo così tre tabelle nor­ma­liz­za­te: interpret, album e title.

Se ora volessimo ri­pro­dur­re un titolo preciso nel nostro database com­pren­si­vo delle in­for­ma­zio­ni ri­guar­dan­ti album e inteprete, dovremmo collegare le tre tabelle separate con il comando SQL JOIN e la ri­spet­ti­va chiave esterna.

Definire la chiave esterna tramite ph­p­MyAd­min

Se avete scelto InnoDB come database engine, definite fa­cil­men­te le relazioni della chiave esterna tramite l’in­ter­fac­cia grafica del vostro software di am­mi­ni­stra­zio­ne ph­p­MyAd­min. Così può essere uti­liz­za­ta la chiave primaria di una tabella come chiave esterna in molte altre tabelle.

Nel nostro esempio abbiamo bisogno di due con­nes­sio­ni, per collegare le tre tabelle nor­ma­liz­za­te album, interpret e title:

  • Per la prima con­nes­sio­ne uti­liz­zia­mo la chiave primaria album_id della tabella album come chiave esterna nella tabella title.
  • Per la seconda con­nes­sio­ne uti­liz­zia­mo la chiave primaria interpret_id della tabella interpret come chiave esterna nella tabella album.

Con la seguente il­lu­stra­zio­ne si possono chiarire i rapporti della chiave esterna:

Per il col­le­ga­men­to di tabelle di dati è ne­ces­sa­rio che una colonna presenti sempre gli attributi UNIQUE o INDEX, così da poter fungere da chiave esterna.

La relazione tra chiave primaria e chiave esterna cor­ri­spon­de di solito al tipo di relazione 1:n. Ogni campo di dati nella colonna della chiave primaria della tabella A è collegato a molti (n) campi di dati nella colonna della chiave esterna della tabella B. Ma ogni campo di dati nella colonna della chiave esterna della tabella B si riferisce sempre proprio a un campo di dati nella colonna della chiave primaria della tabella A. Ad esempio, abbiamo quattro voci nella colonna della chiave primaria album_id, contenuta nella tabella album, che sono collegate tramite la chiave esterna title.album_id con otto voci della tabella title.

Per impostare i col­le­ga­men­ti de­si­de­ra­ti, creiamo le tabelle album, interpret e title su ph­p­MyAd­min e definiamo la nostra chiave primaria nell’ambito della creazione della tabella, come già descritto, tramite l’opzione “Index”. Inoltre fate at­ten­zio­ne al fatto che le colonne che devono fungere dopo da chiave esterna, vengano con­tras­se­gna­te ugual­men­te tramite l’opzione dell’indice come INDEX o UNIQUE. Per le relazioni 1:n si adatta però solo INDEX, perché i valori in un campo UNIQUE non si possono ripetere.

Nel prossimo passaggio definiamo la chiave esterna, prendendo ad esempio la tabella album. Perciò scegliamo la tabella nel pannello di am­mi­ni­stra­zio­ne e apriamo la tab Structure nella barra del menu. Qui si trova il pulsante “Relation view“ (vi­sua­liz­za­zio­ne delle relazioni):

Tramite il campo di input "Foreign key con­strain­ts“, nella schermata delle relazioni di una tabella, definite le relazioni della chiave esterna:

Nella tabella album la colonna interpret_id deve fungere da chiave esterna, che si basa sulla chiave primaria interpret_id della tabella interpret.

Se­le­zio­nia­mo così dal menu a tendina sotto “Column“ la colonna interpret_id  come chiave esterna. Fate at­ten­zio­ne che qua vengano eseguite solo le colonne che sono state con­tras­se­gna­te come INDEX, UNIQUE o PRIMARY. Nel campo di input “Foreign key con­straint (InnoDB)“, formato da tre parti, definiamo su quale chiave primaria di quale tabella di quale database si deve basare la nostra chiave esterna. Scegliamo come segue:

Database: test

Tabella: interpret

Chiave primaria: interpret_id

Il campo “Con­straint name“ può rimanere vuoto. In questo caso il Database Ma­na­ge­ment System assegna au­to­ma­ti­ca­men­te un nome. Tuttavia è ne­ces­sa­rio che definiate come si debba com­por­ta­re una tabella con la chiave esterna, nel caso in cui venga mo­di­fi­ca­ta o eliminata la chiave primaria che ne sta alla base.

Se ad esempio viene eliminato un in­ter­pre­te dalla tabella genitore interpret, scompare anche la chiave primaria connessa a questo record. Bisogna così chiarire cosa deve succedere con le voci, che si ri­fe­ri­sco­no a questo record tramite chiave esterna; nel nostro esempio si tratta quindi degli album di un in­ter­pre­te.

Per de­ter­mi­na­re il com­por­ta­men­to di una tabella con una chiave esterna nel caso di un UPDATE o DELETE, su MySQL o MariaDB sono a vostra di­spo­si­zio­ne quattro opzioni.

  • RESTRICT: l’opzione RESTRICT esclude una modifica della tabella genitore, a patto che esistano altre tabelle, che rimandino a questa. Nel nostro caso un record non potrebbe essere eliminato dalla tabella interpret, se a questo vi sono collegati dei record nella tabella album.
  • CASCADE: l’opzione CASCADE è re­spon­sa­bi­le del fatto che una modifica nella tabella genitore viene trasmessa a tutte le tabelle. Se ad esempio ci basiamo su interpret_id del gruppo The Rolling Stones da 2 a 8, questa modifica viene applicata nell’opzione Foreign key CASCADE anche per tutte le tabelle che uti­liz­za­no l’interpret_id come chiave esterna. Se viene eliminata una voce nella tabella genitore, questa porta al fatto che anche tutti i record collegati con questa voce vengano eliminati nelle altre tabelle. At­ten­zio­ne: in questo modo se si can­cel­la­no singole voci, è possibile che si verifichi l’eli­mi­na­zio­ne di mol­te­pli­ci record.
  • SET NULL: se se­le­zio­na­te l’opzione SET NULL, il valore nella colonna della chiave esterna viene impostato su NULL, non appena la chiave primaria nella tabella genitore viene cambiato o eliminato.
  • NO ACTION: su MySQL l’opzione NO ACTION è l’equi­va­len­te dell’opzione RESTRICT.

Se avete definito l’opzione de­si­de­ra­ta per la relazione della chiave esterna, con­fer­ma­te la selezione con un click su “Save”. Il Database Ma­na­ge­ment System assegna au­to­ma­ti­ca­men­te un nome per la nuova relazione creata.

Tipi di JOIN su MySQL e MariaDB

Le relazioni della chiave esterna vi con­sen­to­no di aprire con­tem­po­ra­nea­men­te dati da diverse tabelle con un unico statement SQL. Perciò su MySQL e MariaDB avete a di­spo­si­zio­ne quattro tipi di JOIN:

  • INNER JOIN: nel caso di un INNER JOIN il Database Ma­na­ge­ment System ricerca delle voci comuni in entrambe le tabelle collegate tramite JOIN. Vengono letti solo i record in cui ci sono delle cor­ri­spon­den­ze, ovvero laddove i valori nelle colonne collegate (chiave primaria e chiave esterna) coin­ci­do­no in entrambe le tabelle.
  • OUTER JOIN: in un OUTER JOIN si distingue tra tabella di sinistra e di destra. A dif­fe­ren­za di un INNER JOIN non vengono solo letti i record in cui si trovano delle cor­ri­spon­den­ze in entrambe le tabelle, ma anche tutti i record rimanenti della tabella di destra e/o di sinistra.
  • LEFT JOIN: vengono letti tutti i record della tabella sinistra e quelli della tabella destra dove vi sono delle cor­ri­spon­den­ze.
  • RIGHT JOIN: vengono letti tutti i record della tabella destra e quelli della tabella sinistra in cui si trovano delle cor­ri­spon­den­ze.

Nel nostro tutorial MySQL ci limitiamo all’INNER JOIN.

La sintassi di un INNER JOIN cor­ri­spon­de al seguente schema di base:

SELECT * FROM tabella1
INNER JOIN tabella2 ON tabella1.chiaveesterna= tabella2.chiaveprimaria
WHERE colonna = valore

Il comando SQL SELECT combinato con il se­gna­po­sto * dà l’istru­zio­ne al Database Ma­na­ge­ment System di ana­liz­za­re i valori di tutte le colonne per far valere le con­di­zio­ni delle clausole ON e WHERE.

Visto che si tratta di un INNER JOIN, vengono raccolti solo i record in cui si trova una cor­ri­spon­den­za tra la chiave esterna della tabella1 e la chiave primaria della tabella2, prese dal database. Inoltre grazie alla clausola WHERE si può definire un filtro opzionale.

Ve lo spie­ghia­mo con un esempio che fa ri­fe­ri­men­to alle nostre tabelle nor­ma­liz­za­te album, interpret e title:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT * FROM album
INNER JOIN interpret ON album.interpret_id = interpret.interpret_id";
foreach ($pdo->query($sql) as $row) {
    echo $row['album_title'] . " was released by " . $row['interpret'] . " in " . $row['released'] . ".<br /><br />";
}
?>

Lo script di esempio vi indica un INNER JOIN, dove la tabella album viene collegata con quella interpret. Ana­liz­zia­mo solo i record in cui sussiste una cor­ri­spon­den­za tra chiave primaria ed esterna.

album.interpret_id = interpret.interpret_id

Nella nostra banca dati tutti questi sono dei record (un LEFT o RIGHT JOIN avrebbe perciò portato allo stesso risultato). Infine vi­sua­liz­zia­mo sul browser i valori letti tramite un ciclo foreach e il costrutto lin­gui­sti­co echo.

In uno statement SQL abbiamo così ana­liz­za­to le in­di­ca­zio­ni sull’in­ter­pre­te dalla tabella interpret e le in­for­ma­zio­ni sul titolo dell’album e l’anno di pub­bli­ca­zio­ne del disco contenute nella tabella album.

Con una con­di­zio­ne nella clausola WHERE si può de­li­mi­ta­re quali record del join vengano vi­sua­liz­za­ti. Se ad esempio vogliamo solo vi­sua­liz­za­re gli album che sono stati pub­bli­ca­ti nel 1968, possiamo procedere nella seguente maniera:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT * FROM album
INNER JOIN interpret ON album.interpret_id = interpret.interpret_id WHERE released = 1968";
foreach ($pdo->query($sql) as $row) {
    echo $row['album_title'] . " was released by " . $row['interpret'] . " in " . $row['released'] . ".<br /><br />";
}
?>

Tramite la con­di­zio­ne WHERE released = 1968 limitiamo la ri­pro­du­zio­ne sul browser a un album. Beggars Banquet dei Rolling Stones è l’unico album nel nostro chiaro database, che è stato pub­bli­ca­to nel 1968.

Grazie al comando JOIN si possono unire molte tabelle a una serie di dati. Nell’esempio seguente col­le­ghia­mo la tabella album in un INNER JOIN con le tabelle interpret e title, per poter vi­sua­liz­za­re tutte le in­for­ma­zio­ni sui titoli delle canzoni salvate nel database.

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT * FROM album
INNER JOIN interpret ON album.interpret_id = interpret.interpret_id 
INNER JOIN title ON album.album_id = title.album_id";
foreach ($pdo->query($sql) as $row) {
    echo $row['title'] . " was released by " . $row['interpret'] . " as Track " . $row['track'] . " on " . $row['album_title'] . " in " . $row['released'] . ".<br /><br />";
}
?>

Anche qui, se ne­ces­sa­rio, possiamo definire una clausola WHERE con un filtro. Ad esempio quando vogliamo ri­pro­dur­re solo le in­for­ma­zio­ni sulla traccia 7 dell’album “Abbey Road“.

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$sql = "SELECT * FROM album
INNER JOIN interpret ON album.interpret_id = interpret.interpret_id 
INNER JOIN title ON album.album_id = title.album_id WHERE track = 7 AND album_title = 'Abbey Road'";
foreach ($pdo->query($sql) as $row) {
    echo $row['title'] . " was released by " . $row['interpret'] . " as Track " . $row['track'] . " on " . $row['album_title'] . " in " . $row['released'] . ".<br /><br />";
}
?>

Fate at­ten­zio­ne al fatto che nella tabella title abbiamo a che fare con una chiave primaria multipla. Se volessimo rein­di­riz­za­re a un titolo preciso, abbiamo bisogno, oltre che del numero della traccia, anche dell’album_id, che abbiamo creato insieme con il titolo dell’album nella tabella album.

Diventare degli esperti

Il nostro tutorial MySQL per prin­ci­pian­ti è solo un assaggio, che vi permette di av­vi­ci­nar­vi alle basi dei sistemi di database basati sul lin­guag­gio SQL e che getta luce sulle semplici ope­ra­zio­ni di database, grazie all’uso di esempi concreti. Se il vostro interesse tra­va­li­cas­se le pos­si­bi­li­tà di ap­pli­ca­zio­ne rap­pre­sen­ta­te, vi con­si­glia­mo di leggere la do­cu­men­ta­zio­ne dei DBMS MySQL e MariaDB, che potete trovare nel paragrafo in­tro­dut­ti­vo. Inoltre su Internet trovate in­nu­me­re­vo­li siti, che pro­pon­go­no tutorial ed esempi di ap­pli­ca­zio­ne per questo famoso Database Ma­na­ge­ment System. In più si consiglia la piat­ta­for­ma Internet Stack Overflow, dove un’attiva community di utenti di più 6,5 milioni di svi­lup­pa­to­ri ri­spon­do­no alle domande poste e si scambiano soluzioni ai problemi insorti durante lo sviluppo del software. Ov­via­men­te anche qui su IONOS Digital Guide avete a di­spo­si­zio­ne molti articoli di ap­pro­fon­di­men­to sui database, che trovate con­trol­lan­do tra i tag presenti.

Vai al menu prin­ci­pa­le