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

I'm writing a cgi script that will basicly display a database.. but I want to be able to provide the user with an option to say.. show me the next 10 results, show me the previous 10 results.. show me the 10 results that are on page 3.. blah blah blah, stuff like that, and with my limited perl experience, I haven't even come close to doing what i need to do. or is it even possible, using one script?

Replies are listed 'Best First'.
Re: last 10 ... next 10?
by alfie (Pilgrim) on Mar 28, 2001 at 11:13 UTC
    There are several approaches that you can use. If you are using an SQL database for the backend there is the LIMIT command where you can select exactly just the lines you want, with offset and length. Please take a look at the documentation of the database, then...
    This has also the advantage that it saves lots of memory and is quite possibly faster.

    If you want to do it in perl and/or don't know if your database supports the LIMIT selection, you should try with slices of the aray:

    @foo = ("1", "2", "3", "4", "5", "6", "7", "8"); print "@foo[3..6]\n";
    This gives you the wanted elements. Don't forget that you start counting with 0 for the first element (so this returns the 4th to the 6th element). Thanks to alakaboo and his great explenation in this note :-)

    Until you don't go further into detail about the layout of your database or how you proceed your data within your perl script I can't offer better answer (maybe other more enlighened monks can ;)
    --
    Alfie

(dkubb) Re: (2) last 10 ... next 10?
by dkubb (Deacon) on Mar 28, 2001 at 11:30 UTC

    I assume you are fetching the results from a database using DBI, in which case you should take alfie's advice, and look into the LIMIT command, if your database supports it. However, most have something similar in concept.

    As for displaying the HTML results, you can combine HTML::Template and HTML::Pager to get the results you want. HTML::Pager was designed for exactly the purpose you describe, and is quite slick. It abstracts alot of the details for maintaining state across each script execution.

Re: last 10 ... next 10?
by epoptai (Curate) on Mar 28, 2001 at 22:33 UTC
    You need to keep track of two things: where to start, and how many to show.

    First set $start to one unless we're getting a param.

    unless(param('start')){ $start = 1 }
    Then use two counters to track where to start and how many to process.
    for(@array){ $c++; # count all items if($c >= $start){ # if $c count is = or greater than where to star +t $d++; # count items that match first test unless($d > $show){ # unless $d count is greater than how many + to show print # or whatever } } }
    Then calculate the 'previous n' and 'next n' links.
    $previous = ($start-$show); unless($previous < 1){ # unless we're already at the beginning print qq~<a href="$url?start=$previous&show=$show"> Previous $show</a>~; } $next = ($start+$show); unless($next > $c){ # unless we're already at the end print qq~<a href="$url?start=$next&show=$show"> Next $show</a>~; }
Re: last 10 ... next 10?
by Beatnik (Parson) on Mar 28, 2001 at 14:20 UTC
    Try a supersearch on Paging. It will, among others, return a link to merlyn's WT Column 02

    Greetz
    Beatnik
    ... Quidquid perl dictum sit, altum viditur.
Re: last 10 ... next 10?
by knobunc (Pilgrim) on Mar 28, 2001 at 19:00 UTC

    If your database does not support LIMIT or an equivalent to allow you to retrieve the appropriate number of rows at an offset then you can do the following:

    # offset is how many records into the dataset to start, # 0 means start at the beginning # count is how many records to read # This returns an arrayref to an arrayref corresponding # to the values for each row. You will only get at most # count rows returned, but may get less if there is less # data than requested sub get_data_from_db { my ($offset, $count, $dbh) = @_; my $sth = $dbh->prepare( qq(SELECT whatever FROM TABLE_FOO )); $sth->execute; my @results; while ($row = $sth->fetchrow_arrayref and $count-- > 0) { if ($offset > 0) { $offset--; $next; } push @results, $row; } $sth->finish; return \@results; }

    Below this is a lot of Mason specific blabbering. If you feel like decoding my feverish screed about how I hook the above up to the HTML that the user sees then keep going, otherwise there is probably not much else.

    Since I am using Mason I wrote a component to handle tables that I pass the parameters for the number of rows to display and the offset to start at and then generate widgets in the html to allow the user to move forward or backward through the dataset generating the correct URL with the appropriate offset and count plugged in. The component is smart enough to know when it has hit the beginning and does not put up a widget to allow you to scroll further backwards, and similarly at the end. To detect the end, it actually cheats, I request one more row than I want and the table component uses that ti detect if there is more data. The table component does not display the additional marker row.

    In order to allow movement any page that wants to support this has to have 2 page arguments that match the ones that the table component knows how to generate so that it can run the correct subroutine that executes the DB query and pass in the appropriate offset and count parameters.

    I actually added a further wrinkle by allowing users to click on the column headings in the tables to sort the rows (or reverse sort if they have already sorted by that row). In order to do this I added a third parameter for sorting and the table component generates little up/down arrows to show which column is sorted. The table component then generates link tags for each column heading that tells the page what the appropriate sort is. This then gets passed into the query which validates the sort and turns it into a SQL order by clause. The following is the full blown beast.

    sub get_top_status { my %args = (start_point => 1, num_rows => 10, sort_by => ['result ascending'], @_); my $dbh = $args{dbh} or croak 'Missing DB handle'; # Define the valid sorts my %sorts = (id => 'T.id', name => 'T.name' ); # Build the SQL sort string my $sort = build_sort($args{sort_by}, \%sorts, ['name']); my $statement = "SELECT name, id, foo FROM FOO_TABLE ORDER BY $sort"; my $sth = $dbh->prepare($statement); $sth->execute(); my @results; while ($row = $sth->fetchrow_arrayref and $count-- > 0) { if ($offset > 0) { $offset--; $next; } push @results, $row; } $sth->finish; return \@results; } # Takes a sort order and a hash defining the legal sorts and produces # a string for embedding in SQL statements # - Order is an arrayref of all the sorts to use # - Sorts is a hashref keyed by the sort names mapping sorts to colum +ns # - Fallback is an optional arrayref of the columns to append to ensu +re # the ordering is sane (if we are sorting by severity we still want + to # sort by name where the severity is the same) sub build_sort { my ($order, $sorts, $fallback) = @_; $fallback = [] unless defined $fallback; my %done = (); my $sort = ''; foreach my $s (@$order, @$fallback) { # Skip ones we have already seen next if exists $done{$s}; $done{$s} = 1; # Build the sort string $sort .= ', ' unless $sort eq ''; my ($col, $order) = (split(/ /, $s), 'asc'); croak "Bad sort request '$s' (parsed '$col' '$order')." unless exists $sorts->{$col}; $sort .= "$sorts->{$col} $order"; } return $sort; }
Re: last 10 ... next 10?
by busunsl (Vicar) on Mar 28, 2001 at 11:06 UTC
    Sure is it possible. Just give a pointer to the next set to your script.
    The script has to provide the links for last/next, which will contain the pointer, which again is processed by your script.