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

I am thinking about moving my database abstraction layer to Class::DBI. The generic methods of this class seem to be able to save a lot of coding for me.

I have only one problem. Most of my queries need a LIMIT clause in the SQL (like SELECT * FROM link LIMIT 100 OFFSET 100). I am reading the docs and it seems to be possible with the retrieve_from_sql method, but this way I would need to code all my queries just like it was before - so there wan't be any gain. What I need is to be able to add the LIMIT clause to the generic searching methods. Using the example from the docs I need something like:

Music::Artist->has_many(cds => 'Music::CD'); my @cds = $artist->cds(year => 1980, limit => 100, offset => 100);
What would be your approach?

Replies are listed 'Best First'.
Re: LIMIT clause in Class::DBI
by jeffa (Bishop) on Feb 17, 2004 at 16:34 UTC

    I think you hit Class::DBI's major weakness. :(

    Personally, i love Class::DBI, but when it comes to doing this stuff "professionally", i just stick with plain old DBI and selectall_arrayref or selectcol_arrayref and specify the optional Slice parameter. Observe:

    my $sth = $dbh->selectall_arrayref(' select id,title,year from movie limit 2 ',{Slice => {}}); print Dumper $sth; __END__ $VAR1 = [ { 'title' => 'Alien', 'year' => '1979', 'id' => '78748' }, { 'title' => 'Aliens', 'year' => '1986', 'id' => '90605' } ];
    Now you send $sth straight to an HTML::Template or TT template.

    If you are wanting to further separate your SQL from your Perl, look into Class::Phrasebook::SQL

    UPDATE: yup, i shoulda named it $movies

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Just a nitpick, but $sth is practically a reserved word when writing DBI apps. I'd call it something else, like $results.
Re: LIMIT clause in Class::DBI
by freddo411 (Chaplain) on Feb 17, 2004 at 18:11 UTC
    I found this to be a problem for me as well. In retrospect, I think I could have been well served to stick with DBI as suggested above. However, I wrote my own pager for Class::DBI and Oracle using Data::Page. If you are using MySQL you can find similar code on CPAN already.

    The invoking code looks like this:

    use Data::Page; my $total_rows = $table->get_count_sql($sql_where_clause); # my $pager = Data::Page->new($total_rows, $G::CF{rows_per_page}, $current_page_num); # Now get only the needed chunk from the DB my @results = $table->get_range_sql($sql_where_clause, $pager->first, $pager->last);
    The supporting functions added to Class::DBI are on my scratchpad

    The interesting bit was getting the SQL that would work with Oracle to provide a "limit" or an "offset" like MySQL. This ends up being ridiculously complicated:

    Select * From ( SELECT %s ,rownum R FROM (SELECT * FROM %s %s ) ) WHERE R BETWEEN %s AND %s
    Update:
    Note that you need to be very careful about what you pass into the where clause. This code will allow bad things to happen if a tainted where clause is passed in.

    -------------------------------------
    Nothing is too wonderful to be true
    -- Michael Faraday

Re: LIMIT clause in Class::DBI
by larsen (Parson) on Feb 17, 2004 at 16:37 UTC
    I've found Class::DBI::Extension on CPAN that should have what you're looking for. By the way, beware the Description:
    THIS RELEASE IS FOR TEMPORARY DEVELOPMENT. Hope this will eventually be merged into Class::DBI and/or Class::DBI::mysql.
    FWIW, I think it should be included in Class::DBI::mysql, or in Class::DBI provided some general implementation of the LIMIT idiom.

      Regrettably, the code notes that it the SQL it implements is specific to MySQL. In Oracle, for example, you use a "where rownum < ?" to limit results. Thus the code is not terribly portable (yet). It would be nice to see that added.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: LIMIT clause in Class::DBI
by cees (Curate) on Feb 17, 2004 at 18:01 UTC

    It might be worthwhile having a look at Class::DBI::Pager. It handles this sort of thing for you, although I have a feeling that it doesn't use LIMITs on the select statements. So if your queries really are massive, it won't solve the problem you are facing. It does however simplify getting ranges of data when using Class::DBI.

    - Cees

Re: LIMIT clause in Class::DBI
by perrin (Chancellor) on Feb 17, 2004 at 20:26 UTC
    I don't consider SQL generation to be the biggest benefit of Class::DBI. Mostly I just like having all of the drudgery of moving stuff between objects and the database handled for me. However, if you want this and you don't want to code the SQL yourself, it's pretty easy to subclass it and add this. Just take a look at the code. Also, search the Class::DBI mailing list because others have done this before.
      I don't quite understand what you mean by "moving stuff between objects and the database" - I thought this requires generating an "UPDATE" SQL sentence.
        That is what I'm talking about -- selecting things from the database, doing all the DBI busywork, moving it into objects, providing accessors on those objects, tracking if I change anything, saving it back to the database if I have, doing lazy-loading of groups of data, iterating over lists of primary keys and inflating them to objects if I try to use them, having a centralized place to do validation before saves, having a centralized place to do caching, etc. All of these things are done for me by Class::DBI. If I had to write every line of SQL, it would still be valuable to me.
Re: LIMIT clause in Class::DBI
by jdtoronto (Prior) on Feb 17, 2004 at 19:36 UTC
    There is another alternative, you can use the underlying relationship between Class::DBI and Ima::DBI. As the docs for Class::DBI say:
    For more complex queries you need to fall back on the underlying Ima::DBI query mechanism.
    There is an example given of a query, using the MySQL 'LIMIT' function:
    Music::Artist->set_sql(most_cds => qq{ SELECT artist.id, COUNT(cd.id) AS cds FROM artist, cd WHERE artist.id = cd.artist GROUP BY artist.id ORDER BY cds DESC LIMIT 10 }); my @artists = Music::Artist->search_most_cds();

    jdtoronto