tags:

views:

147

answers:

5

I have a database with ID's that are non-integers like this:

b01
b02
b03
d01
d02
d03
d04
s01
s02
s03
s04
s05

etc. The letters represent the type of product, the numbers the next one in that group.

I'd like to be able to select an ID, say d01, and get b05, d01, d02 back. How do I do this in MYSQL?

+1  A: 

Find your target row,

SELECT p.id FROM product WHERE id = 'd01'

and the row above it with no other row between the two.

LEFT JOIN product AS p1 ON p1.id > p.id    -- gets the rows above it

LEFT JOIN       -- gets the rows between the two which needs to not exist
        product AS p1a ON p1a.id > p.id AND p1a.id < p1.id

and similarly for the row below it. (Left as an exercise for the reader.)

In my experience this is also quite efficient.

    SELECT
        p.id, p1.id, p2.id
    FROM
        product AS p
    LEFT JOIN
        product AS p1 ON p1.id > p.id
    LEFT JOIN 
        product AS p1a ON p1a.id > p.id AND p1a.id < p1.id
    LEFT JOIN
        product AS p2 ON p2.id < p.id
    LEFT JOIN 
        product AS p2a ON p2a.id < p.id AND p2a.id > p2.id
    WHERE
        p.id = 'd01'
        AND p1a.id IS NULL
        AND p2a.ID IS NULL
le dorfier
Crikey! Is that really the way to do this? What are the t1 and t2? This is all from one table...
Rich Bradshaw
Yup, I named the table "table". I'll change it to "product" to make it clearer.
le dorfier
Replacing all the 't's with 'product' (the name of my table) gives me an error at the first LEFT JOIN... Thanks for your help so far, but I must admit, I don't really know how to use this...
Rich Bradshaw
Try again. I left out some stoff. I'll add more explanation.
le dorfier
OK, but I get an error near "p1a.id IS NULL AND p2a.ID IS NULL".
Rich Bradshaw
...need one more 'and' in the 'where' clause. Btw, this technique is known as a self-exclusion join, and le dorfier is a master at it.
Adam Bernier
Thx @adam, I added it.
le dorfier
Good work - I salute your SQL genius!
Rich Bradshaw
A: 

Although not a direct answer to your question I personally wouldn't rely on the natural order, since it may change duo to import/exports and produce side effects not easily understandable by fellow programmers. What about creating an alternate INTEGER index and fire up another query? "WHERE id > ...yourdesiredid ... LIMIT 1"?

merkuro
A: 
mysql> describe test;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | varchar(50) | YES  |     | NULL    |       | 
+-------+-------------+------+-----+---------+-------+

mysql> select * from test;
+------+
| id   |
+------+
| b01  | 
| b02  | 
| b03  | 
| b04  | 
+------+

mysql>  select * from test where id >= 'b02' LIMIT 3;
+------+
| id   |
+------+
| b02  | 
| b03  | 
| b04  | 
+------+
Macarse
It's good, but I want the row before, the row itself, and the row afterwards, rather than just the row and 2 after.
Rich Bradshaw
in your code you generate the id.if you want b02 you turn it into b01, that's not hard.I would preferred not having complicated sql queries.
Macarse
+1  A: 

Here is another way to do it using UNIONs. I think this is a little easier to understand and more flexible than the accepted answer. Note that the example assumes the id field is unique, which appears to be the case based on your question.

The SQL query below assumes your table is called demo and has a single unique id field, and the table has been populated with the values you listed in your question.

( SELECT id FROM demo WHERE STRCMP ( 'd01', id ) > 0 ORDER BY id DESC LIMIT 1 )
UNION ( SELECT id FROM demo WHERE id = 'd01' ORDER BY id ) UNION 
( SELECT id FROM demo WHERE STRCMP ( 'd01', id ) < 0 ORDER BY id ASC LIMIT 1 ) 
ORDER BY id

It produces the following result: b03, d01, d02.

This solution is flexible because you can change each of the LIMIT 1 statements to LIMIT N where N is any number. That way you can get the previous 3 rows and the following 6 rows, for example.

William Brendel
But typically not as efficient (UNION is effectively 3 queries). Using UNION and TOP is generally considered less elegant and (for TOP) less portable (which isn't worth much in this case); but that's personal preference and YMMV.
le dorfier
Point taken WRT efficiency, but it really depends on how much efficiency matters in this case. The author didn't really say one way or the other. Sometimes really efficient code that is more difficult to understand is less valuable than less efficient code that is easier to understand. To each his own...
William Brendel
A: 

What about using a cursor? This would let you traverse the returned set one row at a time. using it with two variables (like "current" and "last"), you could inchworm along the result until you hit your target. Then return the value of "last" (for n-1), your entered target (n), and then traverse / iterate one more time and return the "current" (n+1).