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

I slept on this problem and woke up without a solution, so I'm finally turning to my fellow monks.

I have a photo album system. When you submit a URL where part of the query contains img_id=# - the img_id is used to do a lookup in the database and pull out the information on that record, including its disk location. Then this image is displayed on a page by itself.

On the page with the photo, I want to display a preview thumbnail of the photo just before this one and after so they can be clicked to progress through the system.

The problem is, I can't figure out how to pull the target record, plus the one before and after it in a single SQL query. I suppose that can't be done. So I decided as a workaround, I would do a selectall_arrayref() against the album_id and build an index out of it. Then I could find what index number the target record is in, in that array. TO get the previous and next images, I could subtract and add one from the index and grab the values out of them.

For example, I want to get img_id #43 from the database, so:

I do a fetchrow_array() to get the album_id it belongs to.

Then I do a select_all_arrayref() to get every img_id from the database that belongs to album_id.

Then I want to stuff all the returned img_id's into an array and use index @array, $img_id to find it's location in the index. To get the image that comes before and after it, I then -- and ++ the results.

The problem, of course, is that selectall_arrayref is an arrayref and to do this, I'll need just a regular array. So what would be the quickest/shortest way to turn the $album_idx = $dbh->selectall_arrayref() into just an array?

Or... could anyone with more experience offer another solution to my whole confounded "building an index of img_id's from the database" to keep track of the pevious/next images?

If the next and previous are just -- and ++ the id on the current image, you can do (in psudo sql) select images from the database where the image ID is greater than or equal to ($id - 1) and image ID is less than or equal to ($id +1) ordered by id . then you just need to special case the times where the database returns < 3 results (top and bottom of the stack).

I had considered that, but the problem is that the img_id's are sequential in the database -- not the album. In other words, if I wanted img_id #43, I could not just say "give me img_id #42, #43 and #44" - because #42 and #44 could possibly belong to another album_id for the same user - or another album_id for a completely different user.

Sounds rather complicated to me. If it's not too much of a hassle, I would suggest that you redesign your database to use img_id as the primary key (assuming that it is unique). You could then easily calculate the before and after img_id values and select them from the database in 1 SQL query. (It would also be very quick since primary keys are indexed)

The problem is that this application will serve infinite users with infinite albums, so while img_id is unique and sequential, it doesn't gaurantee that any two images belong to the same person or album.

You could easily keep the current album seq_id value in another table and reference it when adding new images. As I said, it may be a hassle modifying your database, but it may be worthwhile in the long run as it more easily does what you want than the other options.

Note: This may be a good "teachable moment" as educators like to call them.

Perhaps the most important thing in designing an application that uses a database is to know thoroughly what you want from the application before you even begin designing the database. Too often, people design a database based on a less than thorough examination of the application requirements and end up having to redo it (the database.) :)

I can't figure out how to pull the target record, plus the one before and after it in a single SQL query. I suppose that can't be done.

It can be done, though it's not particularly efficient, and the exact syntax of the query will depend on which database you're using. The strategy is to outer join the table to itself twice (once for the prev, once for the next). Something like (in Oracle):

This is the solution I put together. I'm not sure how speedy it is in relation to the other suggestions in this thread, but as long as no album has more than a few thousand images, I suspect this method should be the fastest solution.

If anyone has further ideas to tighten this up or yet other alternative approaches, I'm eager for you to share them with me.

# Elsewhere in the code, we get an img_id, do a SELECT to find what
# album_id it belongs to, then use the SELECT below to get all of
# the img_id's that belong to that album. We use flattenArray() to
# turn the arrayref from DBI into a plain old array.
my @image_idx = flattenArray( @{$dbh->selectall_arrayref("SELECT img_i+d FROM images WHERE album_id = $image_id")});
# Find what place our target img_id is in the array.
my $idx_loc = indexArray($img_id, @image_idx);
# Get img_id's from array that come before and after the target.
my $prev_img = $image_idx[$idx_loc - 1];
my $next_img = $image_idx[$idx_loc + 1];
# Thanks to merlyn, tilly and particle
# http://perlmonks.org/index.pl?node_id=151120
sub flattenArray {
my @flattened; # Will be in reverse order
while (@_) {
my $last = pop;
if (UNIVERSAL::isa($last, "ARRAY")) { push @_, @$last; }
else { push @flattened, $last; }
}
return reverse @flattened;
}
# My apologies, but I took this from a golf thread on PM. I've lost
# the node number and apologize to the author. Please let me know
# so I can credit you for this.
sub indexArray(@) {
my $s=shift;
$_ eq $s && return @_ while $_=pop;
-1;
}

Your solution is quite general, but I don't like the idea of retrieving all the records every time I need to build next and previous links in a page; why don't you use the LIMIT/OFFSET clause available under PostgreSQL and MySQL?

When putting a smiley right before a closing parenthesis, do you:

Use two parentheses: (Like this: :) )
Use one parenthesis: (Like this: :)
Reverse direction of the smiley: (Like this: (: )
Use angle/square brackets instead of parentheses
Use C-style commenting to set the smiley off from the closing parenthesis
Make the smiley a dunce: (:>
I disapprove of emoticons
Other