postgresql | qualche tips, qualche tricks

Posto alcuni comandi pgsql che ho avuto modo di usare di recente dovendo maneggiare i formati date di Postgresql. Li metto cosi’ in ordine sparso e premetto che fanno riferimento ad un database usato per la gestione dei consumi energetici di impianti di illuminazione; pertanto alcuni commenti (personali) fanno riferimento a questo tipo di lavoro. Tuttavia vanno benissimo per qualsiasi altro db.

Sia data una tabella “tbl01” che contiene un campo “anno” come stringa, “data01”, “data02”, “data03”, “data04” campi in formato date, “consumo_anno” come integer :

# comando SELECT per recuperare il valore del campo “anno” sul quale faremo le nostre operazioni:

mydb=# SELECT anno FROM tbl01 WHERE id=4;
 anno
——
 2010
(1 row)

Come si puo’ vedere si tratta di un campo varchar di 4 caratteri.

# comando SELECT che converte in numero (attualmente in formato testo) il campo “anno” della tabella “tbl01”:

mydb=# SELECT to_number(anno, ‘9999’) FROM tbl01 WHERE id=4;
 to_number
———–
      2010
(1 row)

# comando SELECT per convertire in numero il campo “anno” e togliere 1:

mydb=# SELECT to_number(anno, ‘9999’)-1 FROM tbl01;
 ?column?
———-
     2009
     2009
     2008
     2009
(4 rows)

# comando per convertire in data nel formato YYYYMMDD il valore del campo “anno”:

mydb=# SELECT to_date(anno,’YYYYMMDD’) FROM tbl01 WHERE id=4;
  to_date   
————
 2010-01-01
(1 row)

# calcolo del 31 dicembre dell’anno precedente a quello in oggetto (con riferimento al campo “anno”):

mydb=# SELECT to_date(anno,’YYYYMMDD’)-1 FROM tbl01;
  ?column?  
————
 2009-12-31
 2009-12-31
 2008-12-31
 2009-12-31
(4 rows)

# comando per calcolare la differenza tra una data chiamata “data1” (inizio anno) e il 31 dicembre dell’anno precedente (giorni in eccesso inizio anno): siano data0=2008-12-29, data1=2009-01-12, data2=2009-12-31, data3=2009-12-31.

mydb=# SELECT data1-(to_date(anno,’YYYYMMDD’)-1) FROM tbl01 WHERE id=4;
 ?column?
———-
       12
(1 row)

# comando per calcolare la differenza tra il 31 dicembre dell’anno precedente e la prima data “data0” (giorni in difetto inizio anno):

mydb=# SELECT (to_date(anno,’YYYYMMDD’)-1)-data0 FROM tbl01 WHERE id=4;
 ?column?
———-
        2
(1 row)

# comando per calcolare la differenza tra “data3” e il 31 dicembre dell’anno in corso (giorni in eccesso fine anno):

mydb=# SELECT data3-(to_date(anno,’YYYYMMDD’)+364) FROM tbl01 WHERE id=4;
 ?column?
———-
        0
(1 row)

# comando per calcolare la differenza tra il 31 dicembre dell’anno in corso e la “data2” (giorni in difetto fine anno):

mydb=# SELECT (to_date(anno,’YYYYMMDD’)+364)-data2 FROM tbl01 WHERE id=4;
 ?column?
———-
        0
(1 row)

===========================================================================

Supponiamo di avere due tabelle in relazione 1 a molti (tabella “a” e tabella “b”) con campo di collegamento (“id” della tabella “a” si collega a a_id della tabella “b”). Se vogliamo recuperare valori da entrambe le tabelle:

mydb=# SELECT a.campo1, b.campo2 FROM a,b WHERE a.id=b.a_id AND campo3=valore ORDER BY id;

===========================================================================

Quella che segue e’ una query su una tabella cosi’ strutturata:

id integer
utente_id integer
data_lettura0 date
energia_attiva_kwh_fase0_l0 numeric(10,2)
energia_attiva_kwh_fase1_l0 numeric(10,2)
energia_attiva_kwh_fase2_l0 numeric(10,2)
energia_attiva_kwh_fase3_l0 numeric(10,2)
totale_energia_attiva_kwh_l0 numeric(12,2)
data_lettura1 date
energia_attiva_kwh_fase0_l1 numeric(10,2)
energia_attiva_kwh_fase1_l1 numeric(10,2)
energia_attiva_kwh_fase2_l1 numeric(10,2)
energia_attiva_kwh_fase3_l1 numeric(10,2)
totale_energia_attiva_kwh_l1 numeric(12,2)
data_lettura2 date
energia_attiva_kwh_fase0_l2 numeric(10,2)
energia_attiva_kwh_fase1_l2 numeric(10,2)
energia_attiva_kwh_fase2_l2 numeric(10,2)
energia_attiva_kwh_fase3_l2 numeric(10,2)
totale_energia_attiva_kwh_l2 numeric(12,2)
data_lettura3 date
energia_attiva_kwh_fase0_l3 numeric(10,2)
energia_attiva_kwh_fase1_l3 numeric(10,2)
energia_attiva_kwh_fase2_l3 numeric(10,2)
energia_attiva_kwh_fase3_l3 numeric(10,2)
totale_energia_attiva_kwh_l3 numeric(12,2)
anno character varying(4)
inizio_anno date
fine_anno date
consumo_anno integer

# query per aggiornare la tabella delle letture (in un db che contiene le letture dei consumi di contatori elettrici) calcolando il consumo_anno nel caso in cui data_lettura0!=data_lettura1 per l’anno=’2009′:

energia_django=> UPDATE manager_letturailluminazione SET consumo_anno=(totale_energia_attiva_kwh_l3-totale_energia_attiva_kwh_l1)+((totale_energia_attiva_kwh_l1-totale_energia_attiva_kwh_l0)/(data_lettura1-data_lettura0)*(data_lettura1-(to_date(anno,’YYYYMMDD’)-1)))-((totale_energia_attiva_kwh_l1-totale_energia_attiva_kwh_l0)/(data_lettura1-data_lettura0)*(data_lettura3-(to_date(anno,’YYYYMMDD’)+364)))  WHERE anno=’2009′ AND data_lettura0!=data_lettura1;

id integer

array_to_string in Postgresql ovvero il problema della “battaglia navale”

Premetto come per ogni post su questo blog che quanto scrivo è frutto di tentativi e ricerche per risolvere un problema pratico che di volta in volta mi si presenta. Non è detto quindi che sia il modo migliore ed il più elegante; ogni feedback e/o consiglio è pertanto graditissimo.

Parto dall’inizio: dispongo di un grafo stradale (stradario comunale) archiviato in db Postgresql con estensione spaziale Postgis. La tabella contenente gli archi stradali si chiama “grafo_new”; in essa ogni arco di strada è spezzato all’intersezione con altri tratti. Questo comporta che una strada denomimata “Via Roma” sia composta da tanti archi. Inoltre il db contiene una griglia regolare di passo quadrato che identifica i quadranti del territorio comunale. Il problema da risolvere è: “Come faccio ad ottenere un elenco delle strade (in ordine alfabetico) che indichi anche il quadrante o i quadrantiall’interno dei quali la strada ricade”. Mi ricorda moltissimo il funzionamento della battaglia navale quando per colpire una nave avversaria di indicavano le coordinate: A7: acqua; B5: colpito; B6: colpito e affondato.

Un po’ come fa l’ottimo servizio maposmatic (http://www.maposmatic.org)

Ecco i passaggi che ho seguito:

1. creazione di una view in postgres che aggrega tutti i tratti di via con lo stesso nome:

#= CREATE VIEW view_grafo_union AS SELECT nome, ele_desc, ele_tipo, gid, ST_Multi(ST_Union(f.the_geom)) AS the_geom FROM grafo_new AS f GROUP BY nome ORDER BY nome;

2.creazione di una seconda view di intersezione spaziale tra il grafo e la griglia di quadranti:

#= CREATE VIEW view_grafo_union_quadrante AS SELECT ST_Intersection(r.the-geom, m.the_geom) AS intersection_geom, m.codice, r.nome FROM view_grafo_union AS r, quadrante_stradario AS m WHERE ST_Intersects(r.the_geom, m.the_geom);

3. Creazione di una query “array_to_string” che  fornisca l’elenco di tutti i quadranti di instersezione per ogni tratto di strada:

#= SELECT DISTINCT a.nome, array_to_string(array(SELECT codice FROM view_grafo_union_quadrante AS b WHERE b.nome = a.nome),’,’) FROM view_grafo_union_quadrante AS a ORDER BY nome;

Per salvare il risultato dell’ultima query su un file basta impartire prima della query il seguente comando:

#= \o /percorso/al/file/di/output.csv

Il CSV potrà poi essere aperto e gestito con Calc  o altro editor di testo.

To be continued con snapshot e altri dettagli….

 

openlite | migrazione tra database in pochi click

Il papà di SpatiaLite, Alessandro Furieri, ha lanciato un altro ottimo tool: OpenLite. Si tratta di uno strumento leggerissimo e semplice per migrare database (interi o in parte) tra SpatiaLite, PostGIS e MySQL.

E’ disponibile in forma sorgente oppure binaria per Windows ed è accompagnato da una semplice ma esaustiva guida che si trova alla stessa pagina del progetto.

Ho provato ad installarlo dai sorgenti su Ubuntu 10.10. Ecco una sisntesi dei passaggi e delle prove fatte:

– scaricare i sorgenti da qui;

– scompattare tutto in una directory di lavoro; nel mio caso in “/home/sit/src/openlite-0.0.1

– ci sono dipendenze da soddisfare: libspatialite e wxWidgets. Le installiamo via sudo apt-get install nome_pacchetti o via synaptic

– da riga di comando si accede alla directory interessata:

cd /home/sit/src/openlite-0.0.1

– lanciare in sequenza i tre canonici comandi:

  ./configure

  make

  sudo make install-strip

– a questo punto il programma è installato. Lo lanciamo da riga di comando con:

sit@dell1530:~$ openlite

– si presenta così:

OpenLite - come si presenta

la semplicità dello strumento si nota subito


– è necessario stabilire prima di tutto una connessione ad un db SQLite. Se ne esiste già uno colleghiamoci cliccando sul pulsante in alto a sx “Connecting an existing SQLite DB” e cerchiamo il DB interessato. In alternativa possiamo creare un DB nuovo cliccando sul secondo pulsante da sx “Creating a New (empty) SQLite DB”.

– in seconda battuta connettiamoci ad un atro DB. Nel mio caso ho provato con un DB PostGIS (PostgreSQL). Cliccare sull’icona con freccia azzurra e contenitore magenta “Connecting to PostgreSQL/PostGIS DBMS”. La prima volta che si tenta di connettersi ad un DB PostgreSQL viene chiedo di individuare la libreria necessaria. Nel nostro caso si tratta di “/usr/lib/libpq.so.5.2“. Questa impostazione viene salvata permanentemente ed ogni connessione successiva a DB PostgreSQ/PostGIS cercherà questa libreria. Nel caso si indicasse la libreria sbagliata viene restituito il messaggio “No PostgreSQL Client Library available … sorry
Connection impossible”. Per “sbloccare” la situazione ed impostare il percorso corretto alla libreria ci sono due alternative:

– alternativa 1 – brutale ma efficade:

– andare nella home directory
– lanciare il comando: ls -la
– dovremmo trovare un file nascosto chiamatto “.OpenLite” (è il file in cui vengono salvate le impostazioni permanenti)
– cancellare il file e ripartire.

– alternativa 2 – piu’ soft (ma non testata):

– andare nella home directory e aprire il file nascosto .OpenLite con: $ nano .OpenLite

– modificare la riga “PostgisLibraryName=libpq.so.5.2” indicando la libreria corretta.

– rilanciare openlite e ripartire.

Una volta individuata la libreria compare un popup per l’inserimento del percorso al DB e delle credenziali di accesso.

connessione a PostgreSQL

Connessione a DB PostgreSQL/PostGIS

Nella parte dx del pannello vengono elencate tutte le tabelle presenti del DB PostgreSQL. Selezionare con click sx la tabella che si vuole trasferire al DB SQLite e poi click dx. Compare un menù contenstuale: scegliere la voce “Select for data transfer”. Poi click dx sullo schema che contiene la tabella in questione (nel nostro caso “public“) altrimenti non è possibile avviare il tarsferimento. Fatto questo verrà abilitato un nuovo pulsante “Start data transfer” che si trova a sx del pulsante “About…”.

– Click sul pulsante indicato.

– Fatto: in questo modo avviene il trasferimento della tabella in questione al DB SQLite che comparirà dopo qualche secondo (a seconda delle dimensioni) nella parte sx del pannello.

La tabella "areacirc" è stata trasferita con successo

da PostGIS a Spatialite

Avevo necessità di esportare un DB PostGIS (PostgreSQL con estensione spaziale) in spatialite.

Cercando in rete ho trovato questa pagina: le librerie GDAL/OGR sono in grado di fare la conversione richiesta. In particolare il terzo esempio riportato alla pagina indicata fa proprio al caso nostro.

$ ogr2ogr –config PG_LIST_ALL_TABLES YES –config PG_SKIP_VIEWS NO -f “SQLite” nome_db.sqlite -progress PG:”dbname=’nome_db_postgis’ active_schema=public schemas=public host=’nome_host’ port=’5432′ user=’nome_utente’ password=’pwd_segreta’ ” -lco LAUNDER=yes -dsco SPATIALITE=yes -lco SPATIAL_INDEX=no

Ma provando a caricare il DB in QGIS si ottiene un errore:

“nome del layer” (GEOMETRY non è un layer valido e non può essere caricato)

Ho chiesto il lista gfoss.it e il papà di spatialite, Alessandro Furieri, mi ha gentilmente controllato l’output di ogr2ogr. La sua illuminante risposta e conclusione è riportata qui. Ma provo a riassumere i passaggi che, con le sue indicazioni, ho seguito per ripulire le sporcizie che erano rimaste.

Il database che viene creato presenta alcune incongruenze (alcune dovute ad errori preesistenti del DB Postgis come il settaggio dello SRID che non deve essere uguale a -1 ma deve essere esplicitamente dichiarato). Poi errori di interpretazione delle colonne geometriche nel passaggio da postgis a spatialite.

In sostanza, una volta creato il DB spatialite ho seguito questi passaggi (indicati da Sandro):

$ spatialite nome_db.sqlite

– si presenta il prompt di spatialite: a questo punto impostare alcune semplici configurazioni per l’output dei comandi:

spatialite> .nullvalue NULL

spatialite> .headers on

spatialite> .mode column

In questo modo otteniamo che vengano visualizzati come NULL in valori senza valore (bel gioco di parole :-)), che si vedano gli headers delle tabelle quando si lanciano query di select e che i valori vengano visualizzati secondo una impostazione tabellare.

A questo punto bisogna forzare lo SRID (nel nostro caso si tratta del codice EPSG=3003, Gauss Boaga Fuso Ovest) nella tabella geometry_columns:

spatialite> update geometry_columns SET srid = 3003;

Poi sistemiamo il type geometrico aggiornando alcuni valori sghembi che erano rimasti; in particolare si tratta di alcune tabelle con dati poligonali in cui il tipo geometrico era rimasto con valore GEOMETRY.

spatialite> update geometry_columns SET type=’POLYGON’ WHERE type = ‘GEOMETRY’;

– Infine aggiorniamo correttamente lo SRID su tutte le geometrie creando i trigger necessari al buon funzionamento di spatialite.

spatialite> UPDATE acqua SET GEOMETRY = SetSrid(GEOMETRY, 3003);

Questo va fatto tabella per tabella (nel caso riportato la tabella “acqua”).

A questo punto il caricamento con QGIS va liscio!

PostreSQL | trigger

Un trigger definisce una funzione che agisce prima o dopo un’altra azione su una tabella.

In sostanza un trigger richiama una determinata funzione (che deve essere creata a priori) che esegue operazioni sui valori di una tabella.

Nel caso particolare di questo esempio disponiamo di una tabella chiamata “valori” che contiene 5 colonne:

valore1, valore2, valore3, valore4 e somma (tutti di tipo integer).

I valori delle prime 4 colonne vengono inserite dall’utente; vogliamo fare in modo che la quinta colonna (somma) venga popolata dalla funzione richiamata dal trigger una volta che i primi 4 valori sono inseriti. Procediamo:

– definiamo la funzione:

nome_db=> CREATE OR REPLACE FUNCTION somma_valori() RETURNS trigger AS $somma_valori$ DECLARE new_somma integer;
BEGIN IF (TG_OP=’INSERT’) THEN UPDATE valori SET somma=valore1+valore2+valore3+valore4; END IF; RETURN NEW; END; $somma_valori$ language plpgsql;

– definiamo ora il trigger che richiama la funzione ad ogni INSERT che avviene nella tabella;

nome_db=> CREATE TRIGGER tr_somma_valori AFTER INSERT ON valori FOR EACH ROW EXECUTE PROCEDURE somma_valori();

In questo modo ogni volta che vengono inseriti i primi 4 valori vedremo popolarsi automagicamente anche il quinto.

Postgresql | replace

Ho importato un file CSV in Postgresql in cui i testi erano racchiusi tra doppio apice (“).

Il risultato si traduce in campi (quelli testuali) tutti belli incapsulati in doppie apici che, a dirla tutta, non sono il massimo…soprattuto se si devono fare SELECT o UPDATE via psql (client a riga di comando comodissimo e potente). Ho tentato allora di approfondire cercando una soluzione (in Postgresql) che permettesse di fare un bel FIND AND REPLACE (come siamo soliti fare con openoffice/libreoffice e/o qualsiasi altro editor di testo).

Alla fine ho scovato la funzione REPLACE di Postgresql che ha la seguente sintassi:

replace(string text, from text,to text)

che riportata in concreto diventa:

nome_db =# UPDATE nome_tabella SET nome_campo=replace(nome_campo, ‘valore_vecchio’,’valore_nuovo’);

Vediamo nel caso pratico del mio db. Si tratta di una tabella contente dati relativi a ditte. Facendo una SELECT sul campo “denominazione” prima della modifica con REPLACE si otteneva:

nome_db =# SELECT denominazione FROM ditte WHERE numero=2;

denominazione
—————–
“NOME DITTA A CASO s.n.c.”
(1 riga)

Aggiorniamo il campo denominazione togliendo i doppi apici:

nome_db =# UPDATE ditte SET denominazione=replace(denominazione,'”‘,”);

e alla fine la query di SELECT precedente torna il valore “pulito”:

nome_db =# SELECT denominazione FROM ditte WHERE numero=2;

denominazione
—————–
NOME DITTA A CASO s.n.c.
(1 riga)

Altro esempio: supponiamo di avere un campo che memorizza il percorso di salvataggio di una immagine. Il campo si chiama “foto” e dobiamo aggiornare l’indirizzo IP del server sul quale risiedono le immagini: si passa da server con IP “100.0.4.231” a IP”192.168.4.50″.
Se interroghiamo il DB prima dell’aggiornamento otteniamo:

nome_db =# SELECT foto FROM ditte WHERE numero=2;

foto
—————–
http://100.0.4.231/percorso_al_file_immagine/002.jpg
(1 riga)

Modifichiamo allora solo la parte di campo relativa all’indirizzo IP:

nome_db =# UPDATE ditte SET foto=replace(foto,’100.0.4.231,’192.168.4.50′) WHERE numero=;

UPDATE 1

ed il risultato della SELECT sarà:

nome_db =# SELECT foto FROM ditte WHERE numero=2;

foto
—————–
http://192.168.4.50/percorso_al_file_immagine/002.jpg
(1 riga)

postgresql | archiviazione di immagini

Per archiviare le immagini in un db postgreSQL ci sono 2 metodi: salvare l’immagine direttamente nel database (con formato dati BLOB) oppure memorizzare solamente il percorso al file immagine. Questo secondo metodo risulta più pulito: in fase di dump del database le immagini vengono mantenute all’esterno del db.
Si crea una tabella “foto”:
nome_database=# CREATE TABLE foto (id integer NOT NULL, nome varchar(50), image_path varchar(100); // inserimento del percorso al file
Si popola la tabella creata:
nome_database=# INSERT INTO foto VALUES (‘1′,’foto01′,’/percorso/al/file/immagine/image001.png’);
Si concedono i GRANT di selezione agli utenti desiderati.
Le immagini possono essere visualizzate via PHP su browser web. Il codice PHP per la navigazione vine mandato al altro post.

postgis | conteggio punti ricadenti all’interno di poligoni

Questo metodo consente di contare il numero di punti ricadenti all’interno di poligoni mediante instruzione SQL in postGIS.

Partiamo da un esempio: siamo in possesso di uno strato poligonale (i.e. una griglia a maglia quadrata di lato 100m) e di uno strato puntuale (i.e. i punti che individuano antenne dislocate sul territorio). Lo scopo è quello di contare quante antenne ricadono all’interno di ogni quadrato di 100m.

La procedura illustrata crea una nuova tabella con il conteggio dei punti cercati.

nome_datbase=# CREATE TABLE nome_tabella AS SELECT tabella_polygon.cat, COUNT(id) AS count FROM tabella_point,tabella_polygon WHERE tabella_point.the_geom && tabella_polygon.the_geom AND CONTAINS (tabella_polygon.the_geom, tabella_point.the_geom) GROUP BY tabella_polygon.cat;

dove:

tabella_polygon è la tabella che contiene le griglie;

tabella_point è la tabella che contiene le antenne;

tabella_polygon.cat rappresenta un campo id univoco (il cat proviene da una esportazione di dati da GRASS);

postgresql | elencare tutti i valori (non ripetuti) di un campo

La parola chiave DISTINCT esclude dai risultati le righe duplicate.

Es: vogliamo elencare tutti i valori che sono contenuti nel campo “nome_via” di una tabella contenente i nomi di tutte le strade. Può succedere che esistano strade “spezzate” in più tratti ed in un database geometrico (PostGIS) siano memorizzati più archi di strada con lo stesso nome. DISTINCT consente di elencare i valori non ripetuti:

nome_database=# SELECT DISTINCT nome_via FROM nome_tabella_strade;

Nel caso volessimo elencare i nomi delle strade così distinte indicando anche la lunghezza totale dei tratti che hanno lo stesso nome:

nome_database=#SELECT DISTINCT nome,sum(lunghezza) FROM nome_tabella_strade GROUP BY nome;

e se volessimo esportare l’output della query in un file CSV:

– nel prompt di psql indicare:

nome_database=# \o /percorso/alt/file/nome_file_output.csv

– poi lanciare la query:

nome_database=#SELECT DISTINCT nome,sum(lunghezza) FROM nome_tabella_strade GROUP BY nome;

– In questo modo otteniamo un file CSV con il risultato.