The story so far, with comments:

Michael DePhillips wrote:
> Hi,
>
> Does anyone have a clever way of returning; a requested value with  one
> value less than that value, and one value greater than that value with
> one query.
>
> For example T1 contains
>
> ID
> 1234
> 1235
> 1236
> 1238
>
> select ID from T1 where ID = 1235 and ID<1235 and ID >1235 LIMIT 3
> (obviously this doesn't work)  I would want to return....
>
> 1234
> 1235
> 1236
>
> or;
> select ID from T1 where ID = 1237 and ID<1237 and ID >1237 LIMIT 3 I
> would want
>
> 1236
> 1238
>
> I would be surprised if there was no way of doing this.....but then
> again, I'm often surprised....

Michael DePhillips also wrote:
<snip>
> ...the id may not always
> be one(1) value away. So the number one needs, somehow, to be replaced
> with a way to get the "next largest value " and the "previous less than"
> value.
>
> Sorry for the lack of precision in my prior post.

nigel wood wrote:
> What you want to do isn't possible in the form you want without
> subqueries. Which would you prefer:

Yes, I believe it is.  See below.

> a) A single query returning one row of three columns containing nulls
> b) A single query using sub queries (MySQL 4.1+ only) returning upto
> three rows with one column
> c) multiple queries
>
> Nigel

Michael DePhillips wrote:
> Hi Nigel,
>
> A and B...please.

Douglas Sims wrote:
>
> I think this will do it, although it takes three queries.
>
> I'm assuming the id values are unique, even if there can be gaps (that's
> what you might get with an AUTO_INCREMENT field).  If the values are not
> guaranteed to be unique then this may not give what you want (if there
> are multiple rows with the same value you're looking for, e.g.
> 3,4,5,5,5,6,9, and you ask for 5, then this would give you 3,5,6, not
> three fives.)
>
> SELECT @id:=5;
> SELECT * FROM t1 WHERE id<(SELECT MAX(id) FROM t1 WHERE id<@id) ORDER BY
> id DESC LIMIT 1;
> SELECT * FROM t1 WHERE [EMAIL PROTECTED] LIMIT 1;
> SELECT * FROM t WHERE id>(SELECT MIN(id) FROM t1 WHERE id>@id) ORDER BY
> id ASC LIMIT 1;

I think you're on the right track, but this is unnecessarily complicated, and, unfortunately, wrong. Given rows with the ids 3,4,5,6,9, it selects 3, 5, and 9 rather than 4, 5, and 6.

Look at the first query:

  SELECT * FROM t1
  WHERE id < (SELECT MAX(id) FROM t1 WHERE id<@id)
  ORDER BY id DESC LIMIT 1;

The subquery finds the previous id (isn't that what we want?), then the outer query selects all the ids that are *less than* the id we want and limits the output to the largest one. In other words, we get the row prior to the row that is prior to the row with the chosen id. You need an "=" in there somewhere.

To get the previous id:

  SELECT MAX(id) FROM t1 WHERE id < @id;

To get the entire previous row:

  SELECT * FROM t1 WHERE id < @id ORDER BY id DESC LIMIT 1;

or

  SELECT * FROM t1 WHERE id = (SELECT MAX(id) FROM t1 WHERE id < @id);

Your last query is similar -- it selects the 2nd row after the middle one rather than the next one.

> But as to putting that in one statement... it might be better just to do
> it as three.

There's always UNION.  See below.

[EMAIL PROTECTED] wrote:
> Here's a single query version of Douglas's solution:
>
> select @id:=6;
>
> select distinct t.testid,
>  (select max(testid) from t  where testid < @id) as previousId,
>  (select min(testid) from t  where testid > @id) as nextId
> from t
> where [EMAIL PROTECTED]
>
> Donna

Well, this requires subqueries (mysql 4.1+), and only returns the ids (not the other columns in those rows), but probably neither is a big deal. There is one big problem, however: It returns nothing if the chosen id doesn't exist. Michael specified that he wanted the rows before and after the chosen id, even if no row has the chosen id.

Try this:

  SET @id = 13;

To get just the ids, even when no row has id = @id:

    (SELECT MAX(id) FROM t1 WHERE id < @id)
  UNION DISTINCT
    (SELECT id FROM t1 WHERE id = @id)
  UNION DISTINCT
    (SELECT MIN(id) FROM t1 WHERE id > @id)
  ORDER BY 1;

To get the row contents, as well:

    (SELECT * FROM t1 WHERE id < @id ORDER BY id DESC LIMIT 1)
  UNION DISTINCT
    (SELECT * FROM t1 WHERE id = @id)
  UNION DISTINCT
    (SELECT * FROM t1 WHERE id > @id ORDER BY id ASC LIMIT 1)
  ORDER BY id;

(Whether those are each one query or three depends on how you feel about UNION, I suppose.)

Now, that last query isn't very efficient. The first and last parts may not use the index on id very well. You could improve on this significantly if you knew the size of the largest gap in the sequence of ids (or at least an upper bound). For example, if you were sure no 2 ids were farther apart than 12, you could take that into account, like so:

  SET @id = 2345;
  SET @maxgap = 12;

    (SELECT * FROM inits
      WHERE id < @id AND id > @id - @maxgap
      ORDER BY id DESC LIMIT 1)
  UNION DISTINCT
    (SELECT * FROM inits WHERE id = @id)
  UNION DISTINCT
    (SELECT * FROM inits
      WHERE id > @id AND id < @id + @maxgap
      ORDER BY id ASC LIMIT 1)
  ORDER BY id;

As long as the largest gap is small relative to the total number of rows, the index on id will almost certainly be used to speed up this version. It's a bit of a hack, but it would work.

That said, it may well be faster to fetch just the ids with the first query, then get the matching rows with a second query. In fact, that's almost certainly the case if you can't or won't use the max gap hack.

Michael

--
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]

Reply via email to