Dovevo trasferire rapidamente i dati da una singola tabella in un db MySQL a quattro distinte – in modo da normalizzare un po’ i dati.
Con perl e DBI la cosa viene facile.
use strict;
use DBI;
[…]
my $dsn = "dbi:mysql:database=$db_name";
my $dbh = DBI->connect( $dsn, $username, $password ) or die $DBI::errstr;
my $sth = $dbh->prepare(
‘SELECT name, surname, birth, address, cap, city, country, phone, email, notes FROM rubrica’
) or die $dbh->errstr;
$sth->execute();
my $ins0 = $dbh->prepare(
‘INSERT INTO persone (nome,cognome) VALUES (?,?)’
) or die $dbh->errstr;
my $ins1 = $dbh->prepare(
‘INSERT INTO indirizzi (id,via,cap,citta,paese) VALUES (?,?,?,?,?)’
) or die $dbh->errstr;
[… ecc, seguono altri statement insert …]
while ( my $row = $sth->fetchrow_hashref() ) {
$ins0->execute( $row->{name}, $row->{surname} );
my $rv = $dbh->last_insert_id(undef, undef, ‘persone’, ‘id’);
$ins1->execute( $rv, $row->{address}, $row->{cap}, $row->{city}, $row->{country} );
[…]
}
La cosa interessante sta in quel last_insert_id che raccatta l’ultimo valore auto incrementato e lo mette in uno scalare, riutilizzabile poi in altri insert per avere le foreign key allineate.
Come già accennato, le relazioni fra gli schemi (che rappresentano tabelle) avvengono grazie alla configurazione di relazioni.
Le due righe che seguono fanno esattamente la stessa cosa, e creano una relazione fra lo schema Folks e Addresses.
__PACKAGE__->has_many( ‘addresses’, ‘Tr0n3::Schema::Agenda::Result::Addresses’, ‘id’ );
Dunque se vorremo fare una join, potremo chiamare join sul metodo che determina la relazione (addresses, guarda caso l’ho chiamato come la tabella da joinare, ma si noti bene che quello è il nome della relazione segnato come primo parametro in has_many) proprio come segue:
che lancerà la seguente query:
Come si vede dall’SQL risultante, i dati della tabella joinata NON sono fetchati dal database (e nemmeno i metodi corrispettivi), comunque è possibile filtrare i dati su entrambe le tabelle sfruttando nella clausola WHERE anche i campi della seconda tabella.
Ad esempio:
Cerca i dati in Folks dove il nome del folk è ‘Simone’ e dove la città (in addresses) finisce per ‘a’.
Con prefetch al posto di join vengono fetchate tutte le colonne:
Come spiegato qua, per accedere ai dati si può usare un metodo già definito oppure get_column().
La selezione delle colonne avviene attraverso i metodi select e as
oppure con columns
che mi è parso una combinazione dei due.
Si può mettere un ‘+’ davanti al nome per aggiungere le colonne selezionate al resultset.
Ad esempio, dopo una join in cui vengono richiamati solo i campi della tabella principale, potremmo rendere disponibili le colonne di un altra tabella con ‘+columns’:
Inoltre grazie alle relazioni potremo anche raccattare dati da uno schema diverso dal resultset di partenza, come in questo caso:
che sarà rappresentato da queste due query consecutive:
SELECT me.id, me.address, me.zip, me.city, me.country FROM addresses me WHERE ( me.id = ? ): ’27′
Questi sono gli oggetti Addresses (cioè gli indirizzi) appartenenti al Folk num. 27:
my @addresses = $folk->addresses;
Lo sapevo che prima o poi mi avrebbe punito.
Ho imparato l’SQL e i db stando su MySQL, poi sono passato a Pg per varie ragioni (di lavoro ed altro).
Mi sono trovato assolutamente bene con Postgre – pur lottando ogni tanto con la sua macchinosità, e alla fine mi sono ritrovato a non usare mai MySQL… fino ad oggi. E ovviamente non mi ricordo (quasi) più nulla.
Poco male, si ricomincia. E due DB is meil che one (almeno finchè ci sono gli ORM come DBIC che capiscon tutto loro).
Mentre pistolavo a casaccio, ho visto che MySQL ha un tot di script da shell che mostrano un po’ di robba, e comunque aiutano se li si impara ad usare.
$ mysqlshow +--------------------+ | Databases | +--------------------+ | information_schema | | agenda | | cmsfdt | | evento_odg | | libri | | mail | | mysql | | studiog7 | | system_backup | | test | | test_blog | | wines | | wp281 | +--------------------+ $ mysqlshow libri Database: libri +---------+ | Tables | +---------+ | book | | details | +---------+ $ mysqlshow libri book Database: libri Table: book +---------+-----------+-------------------+------+-- | Field | Type | Collation | Null | .. +---------+-----------+-------------------+------+-- | id | int(11) | | NO | .. | titolo | char(100) | latin1_swedish_ci | NO | .. | autore | char(100) | latin1_swedish_ci | NO | .. | editore | char(50) | latin1_swedish_ci | NO | .. +---------+-----------+-------------------+------+---
A me è parso caruccio.
Edit postumo: mysqlshow ha un po’ di opzioni. Una cosa che ho trovato utile è stato usare l’opzione –count che dice quante righe ha la tabella richiesta (ho aggiunto in ~/.bashrc alias mysqlshow=’mysqlshow –count’)
Dopo aver bestemmiato come un satanista, forse ne sono uscito. Finalmente.
Gli schemi che si mangia DBIx::Class sono di questo genere (ho tagliato la definizione delle colonne, perché irrilevante):
[…]
use base ‘DBIx::Class’;
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
__PACKAGE__->table("thoughts");
__PACKAGE__->add_columns(
"id", { […] },
"title", { […] },
"body", { […] },
"create_time", { […] },
"last_modif", { […] },
"author_id", { […] },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("thought_pkey", ["id"]);
__PACKAGE__->belongs_to(
"author_id", "Tr0n3::Schema::Main::Result::Authors", { id => "author_id" },
);
1;
e
[…]
use base ‘DBIx::Class’;
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
__PACKAGE__->table("authors");
__PACKAGE__->add_columns(
"id", { […] },
"nick", { […] },
"password", { […] },
"mail", { […] },
"create_time", { […] },
"last_login", { […] },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("authors_pkey", ["id"]);
__PACKAGE__->has_many(
"links", "Tr0n3::Schema::Main::Result::Links", { "foreign.author_id" => "self.id" },
);
__PACKAGE__->has_many( "thoughts", "Tr0n3::Schema::Main::Result::Thoughts", { "foreign.author_id" => "self.id" },
);
1;
Per fare una join con DBIC ci si appoggia alle relazioni:
Thoughts->belongs_to( "author_id", "Tr0n3::Schema::Main::Result::Authors", { id => "author_id" }, );
has_many crea una relazione “uno a molti”, e ha come argomenti: il nome dell’accessor, il nome della classe che rappresenta la tabella, un hashref con la reference (a cosa punta la foreign key).
In pratica lo schema Authors è in connessione uno a molti con Thoughs (per un Author ci possono essere molti Thoughts) , la relazione si chiama thoughts e vi si accede attraverso l’accessor thoughts, che punta allo schema Main::Result::Thoughts, secondo la relazione author_id (nella tabella thoughts) – id (nella tabella author).
In maniera analoga, lo schema Thoughts ha una relazione belongs_to con nome author_id, che punta allo schema Authors e che lega l’id in Authors con l’author_id di Thoughts.
Grazie a queste relazioni esplicitate nello schema, è possibile creare delle join in DBIx::Class:
Questa join parte dal ResultSet di Thoughts e senza filtri fa un JOIN con author attraverso l’accessor author_id.
L’SQL risultante è:
(Il prefisso “me” – inserito da DBIC – indica la tabella a sinistra del join.)
A forza di spaccarsi la testa sulle cose, ogni tanto se ne esce.
Avendo un database su PostgreSQL, e volendo manipolarlo con strumenti ORM in perl si può usare DBIx::Class (DBIC per gli amici).
DBIC si appoggia a “schemi” di tabelle nel database che gli permettono di interagire col db-server in maniera corretta (e ottimizzata).
Gli schemi sono un po’ pallosi da estrarre, ma a tutto c’è soluzione:
Dopo questo comando nella directory Sim ci sarà un pacchetto Prova.pm (lo schema) che caricherà le classi che rappresentano le tabelle del database XXX (nella dir Sim/Prova/tabella_*.pm).
Le rappresentazioni delle tabelle sono interessanti da guardare.
Da questo momento sarà possibile scrivere script che usino lo schema (Sim::Prova), si connettano attraverso di esso (metodo connect) ed agiscano sui dati del database XXX.
use Sim::Prova;
my $schema = Sim::Prova->connect(‘dbi:Pg:dbname=XXX’, ‘YYY_user’, ‘ZZZ_passwd’, {});
my @all_authors = $schema->resultset(‘Authors’)->all;
foreach (@all_authors) {
print $_->name ."\n";
}
L’oggetto $schema porta la connessione, tramite ->resultset(‘table’) capisce com’è organizzata la tabella nel db e poi accetta i metodi utili per fare qualcosa (CRUD: Create, Retrieve, Update, Delete).
$schema->resultset(‘tabella’)->create( {id => 1, name => ‘Simone’} );
# R
my @rows = $schema->resultset(‘tabella’)->all;
foreach $row (@rows) {
print $_->id ." | ". $_->name ."\n";
}
# U
my $author = $schema->resultset(‘tabella’)->find(1);
# dentro $author c’è il nostro record.
$author->name(‘Mario Rossi’);
# rimpiazza l’attributo "name" di $author con qualcosa di diverso.
# A questo punto il database ha ancora il record non cambiato, ma
# $author contiene un record differente.
$author->update();
# così aggiorno i dati sul db.
# D
# Come update, si identifica il record giusto e…
$author->delete();
Un po’ di basic usage e di manualistica.
E’ un periodo in cui sto lavorando molto con perl e database differenti.
Ho già parlato del modulo DBI, e di quanto esso mi gusti.
Se per caso servisse sapere quali driver DBI mette a disposizione, ecco l’elenco dei database a cui è possibile collegarsi.
AnyData
CSV
DBM
ExampleP
File
Gofer
Pg
Proxy
SQLite
Sponge
mysql
Nel delirio perlico che ogni tanto mi prende, mi sono messo a guardare un po’ di codice che richiedeva un differente approccio per collegarsi ai db.
Devo dire che sono rimasto schifosamente affascinato da DBIx::Class e il suo “Schema”, ma non avendoci ancora capito molto, aspetto a scriverci su qualcosa.
Di sicuro invece c’è che DBIx::Simple funziona. Non ho ben capito perchè dovrebbe essere più semplice di DBI, però… funziona.
DBI, come ho già scritto in passato, funge così: si carica l’handler, si prepara la query, la si esegue e si fetcha il risultato nella maniera che si ritiene più opportuna. (*)
use DBI;
my $dsn = "dbi:mysql:database=test";
my $dbh = DBI->connect( $dsn, $username, $password )
or die $DBI::errstr;
my $sth = $dbh->prepare("SELECT * FROM testing")
or die $dbh->errstr;
$sth->execute();
while ( my $row = $sth->fetchrow_hashref() )
{ ... }
Il vantaggio con DBIx::Simple è che prepare() ed execute() sono compresi in un’unica chiamata. Il fetching dei risultati dovrebbe essere un po’ semplificato, ma a me non ha cambiato la vita (i metodi sono al paragrafo DBIx::Simple::Result methods).
use DBIx::Simple;
my $dbh = DBIx::Simple->connect('dbi:Pg:dbname=test',
'username', 'password' ) or die DBIx::Simple->error;
my $res = $dbh->query('SELECT * FROM pluto');
while ( $res->into(my($id, $ti, $ra)) ) {
say "$id $ti $ra";
}
Nota: L’idea del metodo html per ottenere una tabella HTML dal risultato l’ho trovata simpatica.
(*) Attualmente il modo più comodo che ho trovato per fare le chiamate è passando attraverso una subroutine che si mangia l’handler e la query come argomenti:
sub sql_query {
my ($dbh,$query) = @_;
my $result = $dbh->prepare($query)
or print("Failed to set up query: $query\n");
$result->execute()
or print("Failed to exec query: $query\n");
return $result;
}
...
my $dbh = DBI->connect(...)
$risultato = sql_query($dbh, "SELECT * FROM pippo");
while ( my @riga = $risultato->fetchrow_array ) { ... }