[mysql] Creare tabella pivot dinamica in MySQL (per esordienti totali)

Con il MySQL è possibile generare tabelle pivot dinamiche, ovvero che contengano nomi di colonne automatiche, in base ad una tabella di riferimento.

Quello che vogliamo ottenere sarà un risultato come il seguente:

Prima di cominciare creiamo un database con un po’ di dati in modo da poterci lavorare.

1. Creazione dati di partenza

Creiamo 3 tabelle: prodotti, agenti e vendite. Di seguito riporto le query per creare le singole tabelle ed inserire i dati di esempio.

PRODOTTI

Per creare la tabella prodotti eseguiamo la seguente query.

Inseriamo i dati nel modo seguente:

AGENTI

Andiamo adesso a creare la tabella degli agenti, in modo analogo:

Ed inseriamo i dati:

VENDITE

Per le vendite andremo a creare la tabella in questo modo:

A differenza delle precedenti due, qui voglio inserire dei dati casuali. Per inserire una riga di dati casuali, pescati dalle precedenti due tabelle, potremmo scrivere:

Faccio notare che con l’istruzione ROUND(1+RAND()*4,0) inseriamo un valore intero casuale tra 1 e 5. Se volessimo generalizzare il procedimento potremmo scrivere ROUND(n+RAND()*(m-n),0) dove n è il minimo, mentre m è il massimo.

Con n = 3 e m = 8 avremmo infatti le seguenti operazioni:

  1. RAND() genera un valore casuale decimale da 0 a 1
  2. Moltiplicando per (8-3) il valore casuale sarà tra 0 e 5.
  3. Sommano il risultato a 3 il valore casuale sarà un decimale tra 3 e 8. Potrebbe essere per esempio 6,4.
  4. Usando ROUND() arrotondiamo a 0 cifre decimali ottenendo quindi un intero.

Se volessimo inserire più di una riga in una singola operazione, possiamo farlo scrivendo una procedura.

Dichiariamo quindi una procedura nel modo seguente, che inserisca 1000 valore casuali per volta.

Chiamiamo la procedura digitando:

2. Creazione tabella pivot

Per creare una tabella pivot manualmente utilizziamo CASE WHEN ... END. A mano potremmo scrivere una query come quella di seguito:

Ogni colonna della pivot sarà generata da:

In questo caso noi abbiamo una tabella dei prodotti, dove sappiamo esserci all’id = 1 il prodotto chiamato mele.

Con SUM() sommiamo i valori (avremmo potuto usare COUNT(), AVG(), MAX(), MIN() ecc.) e con ROUND() arrotondiamo. Quest’ultima funzione in particolare non sarebbe necessaria, ma la utilizzo solo perché, visto che si parla di importi in euro, non ha senso calcolare un risultato al di sotto dei centesimi.

Quello che vogliamo fare ora è poter scrivere, quel pezzo di query, in modo iterativo, ripetendola per CIASCUN valore della tabella prodotti.

In particolare potremmo eseguire una query come la seguente:

Il risultato di questa query sarebbe un singolo campo contenente i seguenti valori:

Faccio notare che grazie a GROUP_CONCAT() vengono inserite le virgole tra le singole righe.

Andiamo a creare quindi una procedura come la seguente:

Eseguiamo, come prima, la procedura:

Otterremo il risultato cercato.

Dobbiamo usare una procedura per evitare di incorrere in una serie di incongruenze legate all’interrogazione del database.

DIfatti, usando il PHPMyAdmin e lanciando solamente l’istruzione:

Si incorrere nel seguente errore:

Fatal error: Uncaught Error: Call to a member function getClauses() on null in D:\xampp\phpMyAdmin\vendor\phpmyadmin\sql-parser\src\Utils\Query.php:564 Stack trace: #0 D:\xampp\phpMyAdmin\vendor\phpmyadmin\sql-parser\src\Utils\Query.php(681): PhpMyAdmin\SqlParser\Utils\Query::getClause(NULL, NULL, 'ORDER BY', -1, false) #1 D:\xampp\phpMyAdmin\libraries\DisplayResults.php(1385): PhpMyAdmin\SqlParser\Utils\Query::replaceClause(NULL, NULL, 'ORDER BY', '') #2 D:\xampp\phpMyAdmin\libraries\DisplayResults.php(4376): PMA\libraries\DisplayResults->_getUnsortedSqlAndSortByKeyDropDown(Array, '') #3 D:\xampp\phpMyAdmin\libraries\sql.lib.php(1689): PMA\libraries\DisplayResults->getTable(Object(mysqli_result), Array, Array, false) #4 D:\xampp\phpMyAdmin\libraries\sql.lib.php(1980): PMA_getHtmlForSqlQueryResultsTable(Object(PMA\libraries\DisplayResults), './themes/pmahom...', NULL, Array, false, 6, 6, NULL, Object(mysqli_result), Array) #5 D:\xampp\phpMyAdmin\libraries\sql.lib.php(2199): PMA_getQueryResponseForResultsReturned(Object(my in D:\xampp\phpMyAdmin\vendor\phpmyadmin\sql-parser\src\Utils\Query.php on line 564

3. Utilizzo della tabella nel PHP

Leggiamo la tabella tramite il PHP. Per farlo utilizzerò la classe MySQLdb come in questo esercizio sul MySQL Cluster.

Creiamo nella root del nostro sito un file MySQLdb.php contenente il seguente codice:

Creiamo adesso un file index.php, nella stessa posizione, contenente il seguente codice:

Vediamo in particolare come nel vettore $prodotti abbiamo l’elenco di tutti i prodotti e quindi delle colonne della pivot, che si trovano in $dati. Il risultato che otterremo sarà simile a questo:

 

Vedi articolo

Errori durante il file upload con PHP, Apache & nginx

Riallacciandomi al precedente articolo ([php] Configurare php.ini per l’upload dei file) voglio approfondire il problema del caricamento di file di grosse dimensioni su server che utilizzano PHP, Apache e nginx (nel mio caso specifico anche Plesk).

Gli errori che possono sorgere sono difatti innumerevoli e spesso apparentemente senza senso.

Cominciamo riepilogando il necessario per quello che riguarda il PHP. Come spiegato nel precedente articolo, assicuriamoci di avere una configurazione simile alla seguente nel file php.ini

In questo caso sto supponendo che caricheremo file fino a 512MB.

Per quanto riguarda il PHP ci dobbiamo assicurare che la quantità di memorie allocabile sia compatibile con la dimensione dei file da caricare e che lo sia anche il tempo di esecuzione.

Con un tempo di 1200 secondi si suppone che l’upload avverrà ad un minimo di 0,43MB/s. Questo significa che parte del caricamento dipende anche dalla velocità di upload del client, che potrebbe non essere sufficientemente alta da permettere al server di terminare l’operazione nei tempi consentiti.

Per inciso ricordiamoci che le classiche connessioni ADSL 20 Mega hanno upload che si aggirano attorno a 1 Mbit, ovvero 0,12 MB/s. Questo significa che il tempo di esecuzione dovrebbe essere per lo meno di 4.300 secondi (approssimando per eccesso).

Detto tutto questo si potrebbe incorrere in altri problemi, come ad esempio, lato client: Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR

Se si sta tentando di gestire l’upload tramite javascript e si incorre in questo errore, esso nulla ha a che fare con il protocollo HTTP/2 (e tanto meno è utile tornare al HTTP/1.1 o simili), ma è legato al fatto che la pagina non invia una risposta corretta. La risposta non viene inviata correttamente perché ad interrompere l’upload possono essere Apache oppure nginx.

Controllando il log del server si potrebbe trovare infatti un errore simile al seguente: 19855#0: *532 client intended to send too large body: 180584796 bytes

In questo caso sto cercando di caricare circa 172MB di file ed nginix blocca l’operazione.

Nel mio caso specifico posso verificare la cosa, confrontando su Windows la dimensione dei file che sto tentando di inviare al server con un unico upload.

Si può notare come la dimensione bloccata sia leggermente più grande dei file in upload, perché, come già discusso nel precedente articolo, il corpo che viene inviato al server contiene anche informazioni aggiuntive che vanno al di là dei singoli file che si stanno caricando.

A questo punto dobbiamo intervenire su nginx aggiungendo l’istruzione:

La configurazione predefinite di nginx sarebbe di 1m, mentre sotto Plesk è di 128m.

Questo parametro può essere modificato all’interno del file /etc/nginx/nginx.conf

Per farlo su Plesk procediamo nel modo seguente (è anche spiegato nella vademecum ufficiale, anche se ci sono delle piccole incongruenze):

  1. Colleghiamoci via SSH al Server con Plesk
  2. Creiamo un file di configurazione aggiuntivo a cui aggiungeremo l’istruzione precedente: echo 'client_max_body_size 128m;' > /etc/nginx/conf.d/aa_client_max_body.conf
  3. Verifichiamo se esiste il file /usr/local/psa/admin/conf/panel.ini
  4. Se non dovesse esistere creiamolo copiandolo dal file predefinito: cp /usr/local/psa/admin/conf/panel.ini.sample /usr/local/psa/admin/conf/panel.ini
  5. Aggiungiamo l’impostazione per la massima dimensione del corpo: echo -e "[webserver]\n nginxClientMaxBodySize = 512m\n" >> /usr/local/psa/admin/conf/panel.ini
  6. Modifichiamo i permessi: chmod 644 /usr/local/psa/admin/conf/panel.ini
  7. Riconfiguriamo il tutto: plesk sbin httpdmng --reconfigure-all
  8. Riavviamo nginx: service nginx restart
  9. Assicuriamoci che su tutti i webserver sia configurato il parametro giusto: nginx -T | grep client_max_body_size
  10. Nel caso non lo fosse possiamo usare l’istruzione, per riconfigurare il tutto: plesk repair web -y -v

In generale dovremmo assicurarci che, rispetto ai parametri suddetti, nelle configurazioni di nginx, sotto la voce server, compaiano i seguenti due valori:

Questi valori dovrebbero essere in linea (o superiori) con quelli scelti per il PHP.

Infine dobbiamo verificare che anche Apache consenta l’esecuzione dell’upload. Nel caso specifico potrebbero esserci due parametri ad influenzarlo: FcgidMaxRequestLen  e LimitRequestBody

Impostiamoli nel modo seguente:

Se abbiamo configurato tutto correttamente dovremmo essere in grado di caricare i file come definito all’inizio.

Vedi articolo

Inviare comando POST a script in PHP mediante BASH

Per un po’ di svago pomeridiano propongo oggi un veloce esercizio che unisca bash e PHP. Quello che vogliamo fare è creare uno script che da console ci chieda quali dati inviare mediante POST ad una pagina in PHP, che poi li utilizzerà per conto proprio.

Anzitutto costruiamo una pagina PHP opportunamente posizionata nel nostro webserver come la seguente:

In questo modo la pagina riceverà dei dati dal form e li scriverà nel file dati.txt, poi leggerà il file dati.txt e lo stamperà a schermo prima del form. Inutile dire che si tratta di una pagina a scopo di prova e sarebbe assolutamente inopportuno utilizzare un simile meccanismo online.

Adesso vediamo come creare il nostro script in bash:

Ricordo che per creare il file bash, nella posizione che desideriamo sarà sufficiente digitare qualcosa come:

Una volta creato ed inseriti tutti i dati ricordiamoci di dargli i permessi necessari perché possa essere seguito:

Faccio notare che il nostro file in PHP si chiama index.php e si trova su un percorso raggiungibile da http://localhost/test/index.php

Eseguendo il file in bash dovremmo vedere qualcosa di simile sulla nostra console:

Vedi articolo