affected_rows

Un paio di cosette in questo post.

Per prima cosa, esternalizziamo la connessione/disconnessione al database. E' una seccatura riscrivere lo stesso codice tutte le volte, conviene piazzarlo in un altro file php e richiamarlo in ogni file php che richiede una connessione a mysql.

Avremo perciò un file php (qualcosa come mysql_utilities.php) che avrà al suo interno definite, come codice php, queste due funzioni:
function db_connect()
{
$mysqli = @new mysqli("host", "user", "password");
if(mysqli_connect_error())
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
echo "Connection to MySql succeeded<br />";

echo "Selecting the current database schema: select_db() ... ";
$mysqli->select_db("test") or die($mysqli->error());
echo "OK<br />";

return $mysqli;
}

function db_close($mysqli)
{
echo "<p>Closing the connection to MySql ... ";
if($mysqli->close())
echo "OK</p>";
else
echo "failed!</p>";
}
Già che ci siamo, mettiamo pure sempre in questo file anche una funzioncina che ci fa l'output dei dati nella tabella products, visto che é una funzionalità che tendiamo ad usare piuttosto spesso:
function list_products($mysqli)
{
$query = "SELECT sku, name, price FROM products";
if(($rs = $mysqli->query($query)) == false)
die('Invalid query: ' . $mysqli->error());

echo "<br />List of product items:<br />";
while($row = $rs->fetch_object())
printf("(%s) %s: \$%s<br />", $row->sku, $row->name, $row->price);
$rs->free();
}
Sistemati questi dettagli implementativi, passiamo a considerare il vero punto di questo post: l'uso di affected_rows di mysqli per vedere quante righe della tabella siano state coinvolte dallo statement sql appena esequito.

Quello che facciamo nel nostro nuovo file é, in primo luogo, la solita inizializzazione del database, che questa volta é molto più veloce del solito, grazie al fatto che deleghiamo le funzionalità al file php che abbiamo definito sopra. In pratica dichiariamo in che file andare a trovare le funzionalità esternalizzate, ci connettiamo al database, visualizziamo i dati presenti sulla tabella products:
require_once("mysql_utilities.php");

$mysqli = db_connect();
list_products($mysqli);
Ora facciamo una select, e mostriamo all'utente quante righe sono state selezionate, usando per l'appunto il valore di affected_rows, prima di mostrare il codice identificativo e il nome del prodotto:
$low = 4;
$query = "SELECT sku, name FROM products WHERE price < $low"; $result = $mysqli->query($query);
echo "<br />There are $mysqli->affected_rows product(s) priced less than €$low:<br />";
for($i = 0; $i < $mysqli->affected_rows; ++$i)
{
$row = $result->fetch_object();
echo "product ($row->sku) $row->name<br />";
}
Allo stesso modo opero per sapere quante righe sono state modificate da un update statement:
  $newPrice = 4.99;
$query = "UPDATE products SET price = $newPrice WHERE price < $low"; $result = $mysqli->query($query);
echo "<br />Price updated at $newPrice for $mysqli->affected_rows product(s)<br />";
list_products($mysqli);
E questo é tutto. Facciamo giusto un reset dei dati, in modo da poter replicare l'esecuzione, e chiudiamo la connessione al database:
echo "<br>Resetting the product prices ...<br />";
$query = "UPDATE products SET price = 2.99 WHERE sku = 'PO988932'";
$mysqli->query($query);

$query = "UPDATE products SET price = 3.99 WHERE sku = 'TY232278'";
$mysqli->query($query);
list_products($mysqli);

db_close($mysqli);
L'esecuzione del codice può essere vista qua.

Fetch

Una volta fatta una SELECT, occorre fare una fetch, ovvero estrarre dal risultato della query una riga, per poterla trattare.

Negli esempi cha abbiamo fatto nei post precedenti abbiamo usato la fetch_row(), ma esistono altri metodi equivalenti da usarsi sul resultset ottenuto da una SELECT: fetch_object() e fetch_array().

Riscriviamo la nostra paginetta php per vedere questi diversi metodi in azione.

Prima cosa, estraiamo la SELECT in una funzione a se stante. Se la SELECT funziona correttamente, ritorniamo il resultset ottenuto, altrimenti, segnalaliamo l'errore e chiudiamo qua i giochi:

function select_products($mysqli)
{
$query = "SELECT sku, name, price FROM products ORDER BY name";
if($rs = $mysqli->query($query))
return $rs;
else
die('Invalid query: ' . $mysqli->error());
}

La list_products_fetch_row() visualizza i dati ottenuti dalla SELECT come avevamo già visto fare in precedenza. Niente da aggiungere, direi:

function list_products_fetch_row($mysqli)
{
$rs = select_products($mysqli);

echo "<br />List of available items through fetch_row():<br />";
while(list($sku, $name, $price) = $rs->fetch_row())
printf("(%s) %s: \$%s<br />", $sku, $name, $price);
$rs->free();
}

list_products_fetch_object() usa fetch_object(), ritorna un puntatore ad un oggetto i cui membri data sono delle variabili il cui nome é quello delle colonne selezionate dalla SELECT:

function list_products_fetch_object($mysqli)
{
$rs = select_products($mysqli);

echo "<br />List of available items through fetch_object():<br />";
while(($row = $rs->fetch_object()))
printf("(%s) %s: \$%s<br />", $row->sku, $row->name, $row->price);
$rs->free();
}

list_products_fetch_array_assoc() usa fetch_array() per MYSQLI_ASSOC, ritorna un array di elementi, ognuno dei quali é indicizzato dal nome della colonna SQL selezionata dalla SELECT:

function list_products_fetch_array_assoc($mysqli)
{
$rs = select_products($mysqli);

echo "<br />List of available items through fetch_array(MYSQLI_ASSOC):<br />";
while(($row = $rs->fetch_array(MYSQLI_ASSOC)))
printf("(%s) %s: \$%s<br />", $row['sku'], $row['name'], $row['price']);
$rs->free();
}

list_products_fetch_array_num() usa anch'essa fetch_array() ma per MYSQLI_NUM, e quindi l'array risultante viene indicizzato dal numero (0, 1, ...) di riferimento della colonna selezionata:

function list_products_fetch_array_num($mysqli)
{
$rs = select_products($mysqli);

echo "<br />List of available items through fetch_array(MYSQLI_NUM):<br />";
while(($row = $rs->fetch_array(MYSQLI_NUM)))
printf("(%s) %s: \$%s<br />", $row[0], $row[1], $row[2]);
$rs->free();
}

Poco cambia nel resto del codice php, mi connetto a MySql, faccio le mie SELECT e mi disconnetto.

$mysqli = @new mysqli("host", "user", "password");
if(mysqli_connect_error())
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
echo "Connection to MySql succeeded<br />";

echo "Selecting the current database schema: select_db() ... ";
$mysqli->select_db("test") or die($mysqli->error());
echo "OK<br />";

// dump the product table
list_products_fetch_row($mysqli);
list_products_fetch_object($mysqli);
list_products_fetch_array_assoc($mysqli);
list_products_fetch_array_num($mysqli);

echo "<br />Closing the connection to MySql ... ";
if($mysqli->close())
echo "OK<br />";
else
echo "failed!<br />";

Quanto sopra può essere visto qui in azione.

Select, insert, delete

Un esempio un poco più funzionale, questa volta. Scriveremo del codice PHP per eliminare e inserire una riga nella nostra tabella, oltre che a leggere i dati con una select.

Prima cosa, trasformiamo le righe relative alla chiamata alla query per la select in una funzione a sé stante. Ci torna utile fare così perchè intendiamo vedere com'é la nostra tabella inizialmente, poi dopo una delete e infine dopo una insert.

La nostra funzione list_product() ha come parametro in input la connessione improved a mysql, e non da niente in output.

Esegue la select sulla tabella, la mostra all'utente, e ritorna. Piccola ma significativa aggiunta, quando finiamo di utilizzare il recordset ritornato dalla query, chiamamo il suo metodo free(), per ripulire la memoria. E' un passo che andrebbe sempre fatto, per evitare memory leak.
function list_products($mysqli)
{
// Create the query
$query = "SELECT sku, name, price FROM products ORDER BY name";
echo "<br />Performing a select: query(\"$query\") ... ";
if($rs = $mysqli->query($query))
echo "OK<br />";
else
die('Invalid query: ' . $mysqli->error());

echo "<br />List of available items:<br />";

// Iterate through the result set
while(list($sku, $name, $price) = $rs->fetch_row())
printf("(%s) %s: \$%s<br />", $sku, $name, $price);

// after a SELECT we should always cleanup the resultset, when done
$rs->free();
}
Il corpo del nostro script PHP comincia connettendoci a MySql, non cambia niente da quanto abbiamo già visto, ma riporto lo stesso il codice, per completezza.
// connect to the database server - notice the '@' to suppress warning
$mysqli = @new mysqli("host", "user", "password");
if(mysqli_connect_error())
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
echo "Connection to MySql succeeded<br />";

echo "Selecting the current database schema: select_db() ... ";
$mysqli->select_db("test") or die($mysqli->error());
echo "OK<br />";
Abbiamo la nostra connessione, adesso per prima cosa visualizziamo il contenuto della nostra tabella (chiamando la funzione che abbiamo definito sopra), poi eliminiamo una riga, ovvero facciamo una query con DELETE, e quindi richiamamo la nostra funzione per vedere l'effetto sulla tabella.

Da notare l'accesso ad affected_rows, membro di mysqli, per vedere quante righe sono state effettivamente eliminate.

Da notare inoltre che non si deve chiamare free sull'output di query(), dato che non abbiamo eseguito una SELECT, e quindi non c'è nulla da ripulire.
// dump the product table
list_products($mysqli);

// deleting a row
echo "<br />Deleting an item ... ";
$mysqli->query("DELETE FROM products WHERE sku = 'TY232278'");
printf("%d row has been deleted.<br />", $mysqli->affected_rows);

// dump the product table after the delete
list_products($mysqli);
Ultimo passo: inseriamo una nuova riga, eseguendo una query con INSERT INTO, visualizziamo per l'ultima volta cosa c'é nella nostra tabella, e finalmente chiudiamo la nostra connessione a MySql.
// inserting a row
echo "<br />Inserting an item ... ";
$query = "INSERT INTO products (sku, name, price) VALUES ('TY232278', 'AquaSmooth Toothpaste', 2.26);";
$mysqli->query($query);
printf("%d row has been inserted.<br />", $mysqli->affected_rows);

// dump the product table after the insert
list_products($mysqli);

echo "<br />Closing the connection to MySql ... ";
if($mysqli->close())
echo "OK<br />";
else
echo "failed!<br />";
La pagina che esegue il codice sopra descritto é questa.

Una select con mysqli

Rifacciamo ora la stessa cosa che abbiamo fatto nel post precedente (una semplice select) ma questa volta usando mysqli, la versione improved della connettività ad un database MySql via PHP.

Il codice é qualcosa di simile a questo:

// connect to the database server - notice the '@' to suppress warning
$mysqli = @new mysqli("host", "user", "password");

if(mysqli_connect_error())
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
echo "Connection to MySql succeeded<br />";

echo "Selecting the current database schema: select_db() ... ";
$mysqli->select_db("mezzabar_test") or die($mysqli->error());
echo "OK<br />";

// Create the query
$query = "SELECT sku, name, price FROM products ORDER BY name";
echo "Performing a select: query(\"$query\") ... ";
if($rs = $mysqli->query($query))
echo "OK<br />";
else
die('Invalid query: ' . $mysqli->error());

echo "<br />List of available items:<br />";

// Iterate through the result set
while(list($sku, $name, $price) = $rs->fetch_row())
printf("(%s) %s: \$%s<br />", $sku, $name, $price);

// close the connection
if($mysqli->close())
echo "<br />Connection to MySql successfully closed<br />";
else
echo "<br />Error closing MySql connection<br />";

E può essere visto in azione qui.

Connessione via mysql

In questo post faccio un passo indietro. Perché se é vero che la versione improved della connettività a mysql é molto più cool della versione base, può essere necessario talvolta ripiegare sulla versione base.

Ad esempio perché mysqli potrebbe non essere disponibile nel nostro progetto, per un qualche motivo aldilà delle nostre possibilità decisionali.

Poco male, però. Anche se meno cool, mysql ci fornisce tutte le funzionalità che ci servono. Vediamo quindi qui un esempio di codice PHP per la connessione e l'esecuzione di una query via mysql. Approfitto dell'occasione per utilizzare il pattern perl "or die()" che termina l'esecuzione dello script nel caso una funzione fallisca:

echo "Connecting to MySql: mysql_connect() ... ";
$link = @mysql_connect("host", "user", "password") or die(mysql_error());
echo "OK<br />";

echo "Selecting the current database schema: mysql_select_db() ... ";
@mysql_select_db("test", $link) or die(mysql_error());
echo "OK<br />";

// Create the query
$query = "SELECT sku, name, price FROM products ORDER BY name";
echo "Performing a select: mysql_query(\"$query\") ... ";
$rs = mysql_query($query) or die('Invalid query: ' . mysql_error());
echo "OK<br />";

if(mysql_num_rows($rs) == 0)
echo "No item available<br />";
else
{
echo "<br />List of available items:<br />";
while($data = mysql_fetch_array($rs))
printf("(%s) %s: \$%s<br />", $data['sku'], $data['name'], $data['price']);
}

echo "<br />Closing the MySql link: mysql_close() ... ";
mysql_close($link);
echo "OK<br />";
Si può vedere il risultato di questo codice qui.

Connettersi a MySql da PHP /2

Riprendo il post precedente per riprendere lo stesso esempio di connessione/disconnessione a MySql via PHP via netsons, un sito che offre hosting gratuito con uso di PHP e MySql (per l'appunto).

Ho scritto qui una paginetta PHP che verifica la connessione a MySql e la sua terminazione, nel loro ambiente. Riporto qui il codice sorgente PHP che ottiene lo scopo:

// connect to the database server - notice the '@' to suppress warning
$mysqli = @new mysqli("server_name", "user", "password");

if (mysqli_connect_error()) {
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
}

echo "Connection to MySql succeeded<br />";

// close the connection
if($mysqli->close())
{
echo "Connection to MySql successfully closed<br />";
}
else
{
echo "Error closing MySql connection<br />";
}

Connettersi a MySql da PHP

Prima cosa, occorre avere una tabella su MySql. Sono al capitolo 30, Using PHP with MySql. A pagina 770, si crea una tabella, via client MySql.

Creo, esplicitamente per lo schema test, la tabella products e ci metto dentro un po' di dati:
CREATE TABLE test.products (
id INT NOT NULL AUTO_INCREMENT,
sku VARCHAR(8) NOT NULL,
name VARCHAR(25) NOT NULL,
price DECIMAL(5,2) NOT NULL,
PRIMARY KEY(id)
)

INSERT INTO test.products (sku, name, price)
VALUES ('TY232278', 'AquaSmooth Toothpaste', 2.25);
INSERT INTO test.products (sku, name, price)
VALUES('PO988932', 'HeadsFree Shampoo', 3.99);
INSERT INTO test.products (sku, name, price)
VALUES('ZP457321', 'Painless Aftershave', 4.50);
INSERT INTO test.products (sku, name, price)
VALUES('KL334899', 'WhiskerWrecker Razors', 4.17);
E adesso mi creo una paginetta PHP che usa questi dati.

Primo passo, apro e chiudo una connessione al database, senza fare altro:
 // connect to the database server - notice the '@' to suppress warning
$mysqli = @new mysqli("localhost", "root", "password");

if(mysqli_connect_error())
{
die('Connect Error (' . mysqli_connect_errno() . ') ' .
mysqli_connect_error());
}

echo "Connection to MySql succeeded<br />";

// select the database schema
if($mysqli->select_db("test") == false)
{
echo "Can't select the required database schema<br />";
}
else
{
echo "The required database schema has been selected<br />";
}

// close the connection
$mysqli->close();
Ho chiamato il costruttore di mysqli passandogli l'host (in questo caso localhost), il nome utente (root), e la password (che dovrebbe essere qualcosa di più sensato che "password").
Nota l'uso di "@" prima di new per sopprimere i fastidiosi warning.

In teoria si potrebbe controllare che la connessione sia ok, testando $mysqli->connect_error, se non fosse che ... beh, non funziona. Quindi si usa la funzione vecchio stile mysqli_connect_error().

Se la connessione é su, cerco di accedere il database schema che mi interessa. E dopodiché
chiudo, soddisfatto, la mia sessione.

Configurare MySql e PHP per Apache

Sono a pagina 661 del capitolo 26, Installing and Configuring MySql, paragrafo Configuring PHP to Work with MySQL di Beginning PHP and MySQL, citato meglio nel precedente post, perché voglio far funzionare insieme PHP e MySql sulla mia macchina.

Dato che sono sotto windows, devo cambiare php.ini, decommentando (ovvero rimuovendo il ";" all'inizio riga) la seguente linea:

extension=php_mysqli.dll

Se stessi usando un MySql di versione pari o precedente alla 4.1, avrei dovuto usare php_mysql.dll (una sola "i" di differenza tra i due nomi).

A questo punto basta rifar partire Apache per poter usare PHP e MySql insieme.

Sfoglio rapidamente i capitoli fino al 29, perché quello che mi interessa adesso é usare PHP con MySql, e raggiungo il capitolo 30, che sembra prometta bene in questo senso.

Un libro su php e mySql

Dato che il progetto per il quale devo usare MySql richiede che lo usi in combinazione con PHP, mi sono preso un libro che mi pareva avesse un titolo adeguato (Beginning PHP and MySQL - From Novice to Professional, di W. Jason Gilmore, terza edizione, APress, edito nel 2008, ISBN: 978-1-59059-862-7) e lo leggo mentre faccio pratica.

Il capitolo 25 fornisce una introduzione su MySql, ma salto al 26 (dove di parla di installazione e configurazione), dove a pagina 645/646 l'autore mette in guardia l'installatore dal rischio di lasciare la password dell'utente root (che ha i privilegi di amministratore) non settata. Questo avviene se si installa MySql utilizzando un modo diverso dal Windows MySQL Configuration Wizard (e infatti io non sono incorso nel problema, dato che ho proprio usato quel metodo).

Nel caso, si sottolinea l'importanza di dare una password (sicura) a root.

Il modo più semplice consiste nel connettersi usando il client mysql e usare il comando SET PASSWORD:

mysql -u root mysql
SET PASSWORD FOR root@localhost=PASSWORD('secret');

ovviamente utilizzando una password un po' più forte di quanto possa essere secret.

Seguono numerose pagine sulla configurazione di MySql che io però ho solo sfogliato rapidamente, dato che il ruolo di DBA toccherà ad altri e che quindi posso evitare di acculturarmi troppo nel campo.

Devo però saperne abbastanza per permettere l'interazione tra PHP e MySql, argomento di cui si parla poco più avanti nel libro, e nel prossimo post di questo blog.

mysqladm

Per cominciare ad usare mySql si può fare riferimento al tutorial della sezione Getting Started. Un agile documento di una trentina di pagine che offre una buona introduzione alla materia, senza perdersi in troppi dettagli.

In seguito si può affrontare il resto della documentazione, che é ottima e abbondante, si veda qui per i dettagli.

Un programma non citato nel tutorial ma che può risultare utile é mysqladmin, che può essere usato per funzionalità amministrative, come illustro a seguire.

Per buttar giù il database mysqladmin si fa così:

mysqladmin shutdown -u user -ppassword

Lo si può usare per creare e distruggere un database, senza ricorrere a SQL:

mysqladmin create dbname -u user -ppassword
mysqladmin drop dbname -u user -ppassword [-f]

l'opzione -f, se specificata, porta al drop del database senza chiedere conferma.

E si può anche vedere se il server é in esecuzione:

mysqladmin ping -u user -ppassword

Client

Ho installato il MySql server sulla mia macchina, e ho quindi a disposizione anche il client standard, che é abbastanza simile a sqlplus (per chi abbia lavorato con oracle), si chiama mysql e si trova nella directory bin del server.

Come da manuale, lo si esegue in questo modo:

mysql -u user -ppassword -h host -P port

Ma dato che mi trovo in locale (e l'host di default é per l'appunto localhost), e ho lasciato come porta il default per mySql (3306), posso invocarlo usando la forma ridotta:

mysql -u user -ppassword

Il client mysql é ok, ma preferirei utilizzare un programma dotato di una interfaccia un poco più amichevole (mi sembra una richiesta del tutto ragionevole).

Già che ci sono esagero e ne uso due di client, che non si sa mai.

SQL Workbench/J un simpatico tool grafico free scritto e mantenuto da Thomas Kellerer
Oracle SQL Developer un altro tool free, questo scritto e mantenuto da Oracle

Entrambi i client che ho deciso di utilizzare sono scritti in java e usano jdbc per connettersi al server. Ora, il driver non é incluso nel server, bisogna scaricarsi un altro robo, il cosiddetto Connector/J. Si scarica lo zip, da cui si estrae il jar (mysql-connector-java-5.1.7-bin.jar nel mio caso) che verrà utilizzato dai client per accedere al server.

Per SQL Workbench/J, occorre specificare il nome del jar nel campo "Library" nella finestra "Manage drivers" per il driver MySQL.

Per SQL Developer, il jar va specificato in Strumenti|Preferenze come "Third party JDBC Driver Path".

Fatto ciò, una volta che il servizio mysql é attivo dovrebbe essere possibile accedere al database attraverso entrambi i client (almeno, a me funziona senza problemi).

Partenza

Per un progetto devo usare MySql, database che non ho ancora avuto la ventura di utilizzare.

Il primo passo che faccio é installarmelo in locale, per prenderci confidenza. La macchina é un Compaq con AMD Turion 64x2, un paio di giga di RAM, con Windows Vista.

La versione che installo é la 5.1, che vado a prendermi qui, sul sito ufficiale di mysql.

Installo il server come servizio, e infatti tra i servizi ora ho MySQL, che modifico da automatico a manuale, perchè preferisco decidere io quando farlo partire e quando buttarlo giù.

Con Vista c'é la seccatura che alla shell normalmente non vengono assegnati i privilegi di amministratore e quindi, se la si fa partire in modo standard (cmd) il tentativo di invocare "net start" risulta in un fastidioso errore "System error 5 /Errore di sistema 5" con l'unica spiegazione "Access denied / Accesso negato".

Occore far partire cmd come administrator, il modo più semplice é fare un click di destra sull'icona del prompt dei comandi, e qui specificare che vogliamo, per l'appunto, avere i privilegi di amministratore per questa istanza della shell.

Fatto questo possiamo accedere ai servizi senza più problemi:
net start mysql
net stop mysql