http://qs321.pair.com?node_id=377548

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?

    Edit: I'm using PostgreSQL.
  • Replies are listed 'Best First'.
    Re: Building an index for next/last in a photo album.
    by waswas-fng (Curate) on Jul 26, 2004 at 20:15 UTC
      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).


      -Waswas
        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.

        Here is an example of the table:

        img_id | album_id | uid | name | ext -------+----------+-----+--------------+------ 38 | 14 | 89 | mypetrat | jpg 39 | 14 | 89 | mypetlemur | jpg 40 | 2 | 12 | vacation_01 | jpg 41 | 14 | 89 | mypetvulture | jpg 42 | 2 | 12 | vacation_02 | jpg 43 | 2 | 12 | vacation_03 | jpg 44 | 14 | 89 | mypetcow | jpg
          What database? if Mysql you should be able to use a more generic query that limits to the user and album and then use it's INDEX statement to say only the Item N -> N+2.


          -Waswas
    Re: Building an index for next/last in a photo album.
    by Roy Johnson (Monsignor) on Jul 26, 2004 at 20:44 UTC
      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):
      select max(P.img_id), T.img_id, min(N.img_id) from images T, images P, images N where P.img_id(+) < T.img_id and P.uid(+) = T.uid and P.album_id(+) = + T.album_id and N.img_id(+) > T.img_id and N.uid(+) = T.uid and N.album_id(+) = + T.album_id and T.img_id = ? and T.uid = ? and T.album_id = ? group by T.img_id
      That said, grabbing the array is a better way to do it.
      Update: added other outer-joined columns.

      We're not really tightening our belts, it just feels that way because we're getting fatter.
    Re: Building an index for next/last in a photo album.
    by davidj (Priest) on Jul 26, 2004 at 20:34 UTC
      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)
      my ($img_id) = $str =~ m/img_id=(\d+)/; my $prev_id = $img_id - 1; my $next_id = $img_id + 1;
      Then the SQL query (something like):
      "select WHATEVER from TABLE where img_id in ($prev_id, $img_id, $next_ +id)"
      Hope this helps,
      davidj
        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.
          Ok, this may or may not be a good solution, depending you whether or not you want to mess with your database design, but it will work.

          Somewhere in the table put another column for a sequence number. So, instead of
          img_id | album_id | uid | name | ext -------+----------+-----+--------------+------ 38 | 14 | 89 | mypetrat | jpg 39 | 14 | 89 | mypetlemur | jpg 40 | 2 | 12 | vacation_01 | jpg 41 | 14 | 89 | mypetvulture | jpg 42 | 2 | 12 | vacation_02 | jpg 43 | 2 | 12 | vacation_03 | jpg 44 | 14 | 89 | mypetcow | jpg
          you have something like
          img_id | album_id | seq_id | uid | name | ext -------+----------+--------+-----+--------------+------ 38 | 14 | 1 | 89 | mypetrat | jpg 39 | 14 | 2 | 89 | mypetlemur | jpg 40 | 2 | 1 | 12 | vacation_01 | jpg 41 | 14 | 3 | 89 | mypetvulture | jpg 42 | 2 | 2 | 12 | vacation_02 | jpg 43 | 2 | 3 | 12 | vacation_03 | jpg 44 | 14 | 4 | 89 | mypetcow | jpg
          You could then grab the seq_id to the corresponding img_id
          select seq_id from TABLE where img_id = $img_id
          Then 1) calculate prev and next seq_id and 2) grab the img_ids you want be referencing the seq_id
          my $p_seq_id = $seq_id - 1; my $n_seq_id = $seq_id + 1; "select img_id from TABLE where seq_id in ($p_seq_id, $seq_id, $n_seq_ +id)"
          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.) :)

          davidj
    Solution - Re: Building an index for next/last in a photo album.
    by Seumas (Curate) on Jul 27, 2004 at 07:14 UTC
      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?

        Ciao, Valerio

    Re: Building an index for next/last in a photo album.
    by ccn (Vicar) on Jul 26, 2004 at 20:24 UTC
      what would be the quickest/shortest way to turn the $album_idx = $dbh->selectall_arrayref() into just an array?

      @album_idx = @{$dbh->selectall_arrayref()}

      Update: also, you may put album_id in the url, and eliminate the first SQL query