views:

618

answers:

1

I have a basic MySQL table, terms, comprised of an id and term field.

I want to create an alphabetically sorted dictionary index (in the literal sense), that would list ten 10 terms above the selected term, and 20 below it. An example of this could be found here http://www.urbandictionary.com/define.php?term=GD2&defid=3561357 where on the left column you see the current term highlighted, and a number of terms above it, and some below, all sorted alphabetically.

As we all know, MySQL doesn't support a ROW_NUMBER() or a similar function so we end up resorting to user variables and sub-selects. I also cannot create a View with user defined variables because MySQL doesn't allow that. Here's what I managed to come up with (and it works):

   SET @row_num := 0;

  SELECT
    @term_index := ordered.row_number
  FROM 
  (
   SELECT
     @row_num := @row_num + 1 AS row_number, terms.*
   FROM
    terms
   ORDER BY
    term ASC
  ) AS ordered 
  WHERE
   ordered.term = 'example term';

  SET @row_num := 0;

  SELECT *
  FROM 
  (
   SELECT
    @row_num := @row_num + 1 AS row_number, terms.*
   FROM
    terms
   ORDER BY
    term ASC
  ) AS ordered
  WHERE
    row_number BETWEEN @term_index - 10 AND @term_index + 20

The first SELECT simply finds out the row number of our target term across the entire alphabetically sorted terms table. The second SELECT uses that information to get 10 terms above it and 20 terms below it.

I wonder if there's a way to avoid running the sub-select in the second SELECT query and instead just reference the first one aliased ordered. Is there a more efficient way of accomplishing this without having to resort to manually create a temporary table? What am I doing wrong here?

+1  A: 

Update:

See this article in my blog for performance details:


If your term is indexed, you can just run:

SELECT  *
FROM    (
        SELECT  *
        FROM    terms
        WHERE   term <= @myterm
        ORDER BY
                term DESC
        LIMIT 10
        ) q
UNION ALL
SELECT  *
FROM    (
        SELECT  *
        FROM    terms
        WHERE   term > @myterm
        ORDER BY
                term
        LIMIT 20
        ) q
ORDER BY
        term

, which will be more efficient.

Quassnoi
This indeed does work and is simpler and faster for my purposes. However, both of the sub-queries need to be aliased as per MySQL and the first sub-query needs to be sorted in ASC order for this to work. Thanks a lot.
Salaryman
@Salaryman: right, fixing.
Quassnoi
@Salaryman: but first subquery needs to be `DESC`, doesn't it? It should select *last* `10` values that are less of equal to a given one.
Quassnoi
In both instances the same rows are selected, they're just ordered differently. Since I want everything to be sorted alphabetically in ascending ordere, everything is ordered ASC. This also eliminates the need for the final ORDER BY on the result of the union, and we can just do a UNION ALL on the sub-queries instead of on their parent SELECT statements.
Salaryman
I take that back, you're right. It needs to be in DESC order, I forgot about the LIMIT changing which rows are returned.
Salaryman