tags:

views:

366

answers:

4

I need to find the next record in a database with (say) a given Surname. So if the user is looking at A Smith, the query would return either the Next A Smith, or B Smith

the solution is probably similar to that discussed here : http://stackoverflow.com/questions/638065/selecting-subsequent-records-arbitrarily-with-limit but using an ODBC 2.0 compliant database rather than mysql.

At the moment I have a procedural solution, but it is very slow. Is there a more elegant approach which uses only basic SQL ?

Sample Data in "appropriate" order

recnum, surname, firstnames 
1000,smith,andrew 
1320,smith,andrew 
1340,smith,andrew 
14,smith,anton 
17,smith,anton 
2000,smith,brian 
2030,smith,brian 
1017,smith,brianally 

I would have the recnum of the current record and need to find the next in the above (which is ordered on surname, forenames, recnum) In this example the case for 1320 is easy, but 1340 makes it harder!

A: 

If by 'next' you mean alphabetical on first name, then try:

  Select * From Table T
  Where lastName = @GivenSurname
     And firstName = 
      (Select Min(FirstName)
       From Table 
       Where LastName = T.LastName
          And FirstName > @GivenFirstName)

If there might be more than one record with this same first name and last Name, and if you have a surrogate Primary Key, then you can arbitrarilly pick one of them by using this:

  Select * From Table T1
  Where lastName = @GivenSurname
     And PK = 
      (Select Min(PK) From Table T2
       Where LastName = T1.LastName
         And FirstName =
          (Select Min(FirstName)
           From Table
           Where LastName = T1.LastName
                And FirstName > @GivenFirstName))
Charles Bretana
Sorry but no. The db could have hundreds of "A Smith" entries - also I think your code would just give the second entry ? Or am I reading it wrong somehow ?
Andiih
Sorry, I was editing to deal with dupes when you commented... Also, assume you don't care which B Smith you return ?
Charles Bretana
Each time the user clicks next they need to walk through all of them. See the sample data I (just) added to the question. Thanks for your input so far!
Andiih
+1  A: 

SQL is intended to work with and return set of data. In fact, one of the points Coad (or one of the gurus of relationalism anyway) uses to describe a relational database is that there is no "next record" -- the concept shouldn't even exist.

As such, from the viewpoint of SQL, what you should do is create a set that contains the data you want in the order you want:

select * from YourTable
    where LastName = @whatever
    order by FirstName

Then, if you want to work with one record at a time, you probably want a cursor into that data set. Using that, you'll just look at a record, and when you advance the cursor to the next record, it'll be the one that should be next.

Jerry Coffin
Think trad ASP and ODBC. No cursors, no real state preservation between calls, no stored procedures. My current solution uses a recordset, and FindFirst then FindNext - which is more or less what you describe but I was hoping for something at the SQL level. Also worth noting that the user should be able to click "next" and move from Smith to Smyth without reentering search conditions. We simply need to find the next record in the DB , by Surname, Forenames after the one we know the recnum of.
Andiih
+1  A: 

Given your sample data and assuming that at any point in the process you have the "recnum" value available for the current record, it would be possible to query for the next value by using a series of up to three queries. If you think of the three pieces of data (surname, firstname, recnum) as a composite index, then you want to find the record that comes next in index order. So assuming you have the three pieces of data from the current record, run this:

select top 1 surname, firstname, recnum from Table
    where surname = @CurrentLast and 
    firstname = @CurrentFirst and 
    Recnum > @CurrRec
    order by surname, firstname, recnum

If that is empty, then run this:

select top 1 surname, firstname, recnum from Table
    where surname = @CurrentLast and 
    firstname > @CurrentFirst
    order by surname, firstname, recnum

And finally, if that is empty:

select top 1 surname, firstname, recnum from Table
    where surname > @CurrentLast
    order by surname, firstname, recnum

If the three fields are indexed, it should be reasonably efficient. It would also be possible to use a single query such as the following (syntax probably wrong), but it would require a composite index on all three fields in order to be efficient.

select top 1 surname, firstname, recnum from Table
    where surname+firstname+convert(recnum,char) > @CombinedValue
    order by surname, firstname, recnum

If recnum refers to a physical record number in the underlying data and if the SQL engine uses the record number as the tie breaker when other conditions match in sorting, then I believe that recnum could be left out of the order by clauses.

Mark Wilkins
I think the 3-query technique will work and be more efficient than what I have now. Not sure whether the rubbish odbc driver I am stuck with will cope with the second solution : But I will try it! Thanks Mark
Andiih
Andiih
Yes they would. Sorry about that. They would all need order by clauses. I had originally run this on a table of my own and tried to quickly translate the names to match your data. Completely left off that part. I'll edit it.
Mark Wilkins
FYI I finally got to test this and it works perfectly. The reverse, to find the previous record, is suffering performance issues - I guess because of SQL optimization.
Andiih
It's good to know it worked, but that is unfortunate about the reverse operation. You are probably correct that the SQL optimization isn't working so well. It is possible that descending indexes on each of the fields would help that case (ouch - lots of indexes). If the database supports ROWID, it might be worth trying to replace "recnum" with "rowid" in each query. I think it would be logically equivalent and might optimize differently (hard to say if differently would mean better, though).
Mark Wilkins
You are spot on with the requirement for descending indexes. Dataflex ODBC driver - no support for < without a descending index. Which is impractical due to sheer volume of indexes - too much of a performance hit on data write. Lesson. Avoid Dataflex ODBC. Sigh.
Andiih
A: 

Does "no state preservation" mean that you can't store any data at all, even pre-caching? In other words, query

SELECT recnum FROM my_table ORDER BY surname, firstname

Use getrows to store that in a local array. Then iterate through that array, each time executing the prepared statement

SELECT surname, firstname FROM my_table WHERE recnum = ?

That will almost certainly be slower than querying it all at once, but it will satisfy your goal.

Dan
each time the user went to the next record, the process would have to be repeated: so this is going to be inefficient to say the least. One of the problems is that the table is several million records
Andiih