2009-05-13 8 views
5

J'ai une requête qui réside dans plusieurs méthodes dont chacune (requête) peut contenir plusieurs paramètres. J'essaye de réduire la taille de fichier et le nombre de lignes pour le rendre plus maintenable. Ci-dessous un tel événement:Comment rendre plusieurs requêtes de base de données plus efficaces en Perl?

$sql_update = qq { UPDATE database.table 
        SET column = 'UPDATE!' 
        WHERE id = ? 
       }; 

$sth_update = $dbh->prepare($sql_update); 

if ($dbh->err) { 
    my $error = "Could not prepare statement. Error: ". $dbh->errstr ." Exiting at line " . __LINE__; 
    print "$error\n"; 
    die; 
} 

$sth_rnupdate->execute($parameter); 

if ($dbh->err) { 
    my $error = "Could not execute statement. Error: ". $dbh->errstr ." Exiting at line " . __LINE__; 
    print "$error\n"; 
    die; 
} 

Ceci est juste un exemple, cependant, il existe plusieurs autres exemples choisis qui contiennent juste l'un des paramètres passés dans, mais il y a aussi quelques-uns avec deux ou plusieurs paramètres. Je suppose que je me demandais juste serait-il possible d'encapsuler tout cela dans une fonction/méthode, passer dans un tableau de paramètres, comment les paramètres seraient peuplés dans la fonction execute()?

Si cela était possible, je pourrais écrire une méthode que vous simplement passer dans la requête SQL et les paramètres et récupérer une référence aux enregistrements récupérés. Est-ce que cela semble sûr du tout?

Répondre

6

Si le code de ligne et le code maintenable sont votre seul but, votre meilleur pari serait d'utiliser l'un des nombreux frameworks/bibliothèques ORM disponibles. Class::DBI et DBIx::Class sont deux bons points de départ. Juste au cas où, vous êtes inquiet de passer plus de temps pour apprendre ces modules - ne: Il m'a fallu juste un après-midi pour commencer et productif. En utilisant Class :: DBI par exemple votre exemple est une seule ligne:

Table->retrieve(id => $parameter)->column('UPDATE!')->update; 

Le seul bas-côté (si cela) de ces cadres est que les instructions SQL très complexes nécessaires à écrire des méthodes personnalisées d'apprentissage qui peut vous prendre quelques supplémentaires temps (pas trop) pour se déplacer.

+0

Cela semble très bénéfique. Est-ce que le module Class :: DBI est livré avec le noyau perl? (pour unix) –

+0

Non. Vous devrez l'installer. Taper "install Class :: DBI" et répondre avec un 'y' à toutes les dépendances dans un shell CPAN l'installera pour vous. –

2

La fonction "execute" ne accepter un tableau contenant tous vos paramètres.

Vous avez juste besoin de trouver un moyen d'indiquer quelle déclaration vous gérez à exécuter et vous avez terminé ...

Il serait beaucoup mieux de garder votre relevé gère quelque part parce que si vous créez un nouveau chaque fois et préparez-le chaque fois que vous ne rippez pas vraiment les avantages de "préparer" ...

À propos du retour de toutes les lignes, vous pouvez le faire (quelque chose comme "tandis que fetchrow_hashref push") se méfier des grands ensembles de résultats qui coudl mange toute ta mémoire!

+0

Je vois ce que vous dites - je vais devoir explorer cela un peu. Toutes mes déclarations imitent le code que j'ai spécifié ci-dessus mais la seule chose qui est diverse sont les paramètres et la déclaration elle-même. –

2

est ici une approche simple en utilisant des fermetures/sous-marins anonymes stockés dans un hachage par nom de mot-clé (compiles, mais pas testé par ailleurs), modifié pour inclure l'utilisation de RaiseError:

# define cached SQL in hash, to access by keyword 
# 
sub genCachedSQL { 
    my $dbh = shift; 
    my $sqls = shift; # hashref for keyword => sql query 
    my %SQL_CACHE; 
    while (my($name,$sql) = each %$sqls) { 
    my $sth = $dbh->prepare($sql); 
    $SQL_CACHE{$name}->{sth} = $sth; 

    $SQL_CACHE{$name}->{exec} = sub { # closure for execute(s) 
     my @parameters = @_; 
     $SQL_CACHE{$name}->{sth}->execute(@parameters); 

     return sub { # closure for resultset iterator - check for undef 
      my $row; eval { $row = $SQL_CACHE{$name}->{sth}->fetchrow_arrayref(); }; 
      return $row; 
     } # end resultset closure 

    } # end exec closure 

    } # end while each %$sqls 

    return \%SQL_CACHE; 

} # end genCachedSQL 


my $dbh = DBI->connect('dbi:...', { RaiseError => 1 }); 

# initialize cached SQL statements 
# 
my $sqlrun = genCachedSQL($dbh, 
{'insert_table1' => qq{ INSERT INTO database.table1 (id, column) VALUES (?,?) }, 
    'update_table1' => qq{ UPDATE database.table1 SET column = 'UPDATE!' WHERE id = ? }, 
    'select_table1' => qq{ SELECT column FROM database.table1 WHERE id = ? }}); 

# use cached SQL 
# 
my $colid1 = 1; 
$sqlrun->{'insert_table1'}->{exec}->($colid1,"ORIGINAL"); 
$sqlrun->{'update_table1'}->{exec}->($colid1); 
my $result = $sqlrun->{'select_table1'}->{exec}->($colid1); 
print join("\t", @$_),"\n" while(&$result()); 

my $colid2 = 2; 
$sqlrun->{'insert_table1'}->{exec}->($colid2,"ORIGINAL"); 

# ... 
+0

C'est excellent. J'essaierai d'implémenter cette solution un peu plus tard et je vous ferai savoir comment je m'en sors. –

+1

Heureux de contribuer - assurez-vous certainement de tester comment l'eval se comporte mal pour exécuter les cas de test – bubaker

+0

Question rapide Bubaker, Cette requête a exécuté un fetchall_arrayref() qui entraîne l'extraction de tous les enregistrements, ce qui est problématique car ma requête retourne dans excès de 300 000 enregistrements avec une moyenne d'environ 9 colonnes. Existe-t-il un moyen de faire en sorte que cette requête utilise fetchrow_arrayref() ou fetchrow_hashref pour parcourir les enregistrements individuels? –

1

Je suis très impressionné par ce Boubakeur exemple d'utilisation d'une fermeture pour cela. Tout de même, si l'objectif initial était de rendre la base de code plus petite et plus facile à maintenir, je ne peux pas m'empêcher de penser qu'il y a beaucoup de bruit suppliant d'être retiré du code original, avant que quelqu'un ne se lance dans une conversion à CDBI ou DBIC etc (en dépit des grandes bibliothèques qu'ils sont tous les deux.)

Si le $dbh avait été instancié avec RaiseError ensemble dans les attributs, la plupart de ce code en va:

$sql_update = qq { UPDATE database.table 
        SET column = 'UPDATE!' 
        WHERE id = ? 
       }; 
$sth_update = $dbh->prepare($sql_update); 
$sth_update->execute($parameter); 

Je ne vois pas que l'erreur de manipulation dans le code original ajoute beaucoup que vous n'obtiendrait pas de la vanille die produite par RaiseError, mais si c'est important, jetez un oeil à l'attribut HandleError dans la page de manuel DBI. En outre, si ces instructions ne sont pas réutilisées (ce qui est souvent le but principal de leur préparation, de mettre en cache comment elles sont optimisées, l'autre raison est d'atténuer l'injection SQL en utilisant des espaces réservés), alors pourquoi ne pas utiliser do?

$dbh->do($sql_update, \%attrs, @parameters); 
4

Aucune raison de vérifier les erreurs après chaque appel de base de données. Comme c'est ennuyeux! Au lieu de cela, lorsque vous vous connectez à la base de données, définissez l'option RaiseError sur true. Ensuite, si une erreur de base de données se produit, une exception sera levée. Si vous ne l'attrapez pas (dans un bloc eval {}), votre programme mourra avec un message, similaire à ce que vous faisiez manuellement ci-dessus.

Questions connexes