Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

'automating' SQL queries, use Class::DBI?

by geektron (Curate)
on Jan 27, 2004 at 06:34 UTC ( [id://324365]=perlquestion: print w/replies, xml ) Need Help??

geektron has asked for the wisdom of the Perl Monks concerning the following question:

i started this writeup earlier when i was at work ... but couldn't get it finished ( the lull in the day came to an abrupt end ).

i've been looking into easier, faster ways of generating some of my SQL queries. ( faster on the coding time, at least ). i've poked around a bit in the Class::DBI perldoc, but it looks like using it for more complicated joins ( like LEFT JOINs when they're needed ) would be almost more of a hassle than a help ...

the first place i've been looking to replace some of my SQL is actually in my INSERT/UPDATE queries. i tend to do this a lot:

sub storeData { my $self = shift; my @fields = qw/ foo bar baz quid quux /; my $insStmt = "INSERT INTO table VALUES ( " ; $insStmt .= "? " x ( scalar( @fields ) +1 ); $insStmt .= " ) "; my $updStmt = "UPDATE table SET " . join( "$_ = ?, ", @fields; $updStmt .= "WHERE id = ? "; $useSql = ( $id ? $updStmt : $insStmt );
then run through all the usual prepare, execute, bind_params, etc. as needed ( working off the @fields array.

it's fairly maintainable. adding an element to @fields gets the column added to the ins/upd statements, but I'm starting to wonder how much easier it could get.

would Class::DBI be an appropriate solution?

Replies are listed 'Best First'.
Re: 'automating' SQL queries, use Class::DBI?
by blokhead (Monsignor) on Jan 27, 2004 at 07:26 UTC
    Class::DBI isn't really for abstracting the SQL-generation process. Of course, it must generate SQL, but everything's under the hood -- you don't use Class::DBI to get back SQL that you execute yourself. Use it (or similar modules*) when you want to have an abstract, object-oriented view of your data and not have to think about SQL at all. The catch for this abstraction is that it's harder to do complex queries all at once. But don't think you can't get the same results, for instance:
    for my $cd (@cds) { print $cd->name, $/; printf " - %d: %s\n", $_->num, $_->song->name for sort { $a->num <=> $b->num } $cd->tracks; }
    This code simulates a join among the "cds", "tracks", and "songs" tables using an object-persistence interface. You get the same results. Of course, it uses many queries on the database instead of just one join query. You need to weigh the gains of this abstraction against the performance of your complex queries when you simulate them like this. But also keep in mind that Class::DBI and friends execute most select queries using the primary key as a constraint, making all those small individual queries very fast.

    OK, now assuming you really do want to abstract the SQL-generation process yourself, to do the whole generate-SQL/prepare/execute/fetch process, then here are some good starting points for common queries. Personally, I prefer using sprintf for SQL generation:

    my $select = sprintf "select %s from $table where %s", join("," => @cols), join(" and " => map { "$_=?" } keys %constraints); my $sth = $dbh->prepare($select); $sth->execute( values %constraints ); ######### my $insert = sprintf "insert into $table (%s) values (%s)", join("," => keys %data), join("," => ('?') x scalar keys %data); my $sth = $dbh->prepare($insert); $sth->execute( values %data ); ########## my $update = sprintf "update $table set %s where %s", join("," => map { "$_=?" } keys %data), join(" and " => map { "$_=?" } keys %constraints); my $sth = $dbh->prepare($update); $sth->execute( values %data, values %constraints );
    But if you do need to do complex stuff like joins, you'd probably be better off with a CPAN solution. You're own your own there, I've never tried any of them myself. Try searching for SQL or in the DBIx namespace.

    *: similar modules = Class::Tables. I'm quite biased, but I can't pass up any opportunity for a plug ;)

    blokhead

      i'm not a huge fan of sprintf, mostly because i always forget the formatting codes.

      and so far most of the code has been tied to a CGI object ( not the best solution, but very few projects have needed to be decoupled from it ), so i can get away with:

      my $cnt = 1; foreach my $field ( @fields ) { $sth->bind_param( $cnt, $q->param( $field ); $cnt++; }
      i'll probably end up poking around more, but since i'm the only developer there, i don't want to blow too much time looking for solutions that don't really result in a gain.
Re: 'automating' SQL queries, use Class::DBI?
by edoc (Chaplain) on Jan 27, 2004 at 08:14 UTC

    Class::DBI takes care of all the basics for you while at the same time helping to keep all your sql stuff in dedicated modules. Once you have your module set up for a table inserts & updates are easy as pie and custom queries aren't much harder.

    package My::Table; use base 'My::Class::DBI'; __PACKAGE__->table('blah'); __PACKAGE__->columns( All => qw/ foo bar baz quid quux / ); __PACKAGE__->columns( Essential => qw/ foo bar / ); __PACKAGE__->columns( Primary => 'foo' ); # assuming we have another table called 'other'.. # this creates a method called sql_method_name __PACKAGE__->set_sql( method_name => qq( select b.foo, b.bar, o.summat +, o.summat_else from blah b left join other o using(foo) where b.quid +=?)); sub public_method_name { my ($class,$quid) = @_; my $sth = $class->sql_method_name(); $sth->execute($quid); return @{$sth->fetchall_arrayref({})}; }

    and using the module...

    # fetch a My::Table object my $record = My::Table->create({ foo => 1, bar => 'bar val', baz => 'baz val', quid => 'quid val', quux => 'quux val' }); # update a value $record->quid('new quid val'); $record->update; # fetch a list of records via our join method my @list = My::Table->public_method_name('new quid val');

    I've also recently started using TEMP columns to store values from other tables in the retrieved object via custom join methods so I can still use the oo interface to read the values (can't update in this case) but it's beer o'clock and I don't have an example of that handy.. 8)

    cheers,

    J

      Class::DBI takes care of all the basics for you while at the same time helping to keep all your sql stuff in dedicated modules. Once you have your module set up for a table inserts & updates are easy as pie and custom queries aren't much harder.

      i already do what i can to pull all of my SQL out of main 'functional' modules and put them into SQL.pm-type classes/packages ( i think the current one is named ArticleQureies.pm and all it does is fetch/store and return results ).

      i'll have to spend some time playing with it, but it doesn't seem like much of a win to write the query, install it as a method, and then call the method. ( well, OK, it does, but it doesn't require the 'overhead' of importing another module. )

Re: 'automating' SQL queries, use Class::DBI?
by perrin (Chancellor) on Jan 27, 2004 at 17:07 UTC
    There is nothing that will do a good job of generating SQL for complicated outer joins and the like. Class::DBI does a good job of mapping tables to objects and letting you address your data in an OO way. It can generate SQL for simple things like what you show above, and it can take arbitrary SQL and handle to busy-work of fetching the data and turning it into objects that you can work with.
      i don't expect there to be a good way of generating everything should of a lot of concatenation ... :-(

      and i'm not particularly fond of table --> object mapping ( if it's a one-to-one relationship ). i've already had to deal with a 'home-grown' solution with a previous job that just became unmaintainable, unweildy, slow, and excessively complicated.

        Class::DBI is better than most homegrown versions of this sort of application, which is why people use it.
Re: 'automating' SQL queries, use Class::DBI?
by Anonymous Monk on Jan 27, 2004 at 07:54 UTC
    Yes
    perldoc Class::DBI
    
    ...
    INTRODUCTION
        Class::DBI provides a convenient abstraction layer to a database.
    
        It not only provides a simple database to object mapping layer, but can
        be used to implement several higher order database functions (triggers,
        referential integrity, cascading delete etc.), at the application level,
        rather than at the database.
    

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://324365]
Approved by jcpunk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2024-04-25 10:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found