Select a row and rows around it

后端 未结 7 2129
庸人自扰
庸人自扰 2020-12-19 15:05

Ok, let\'s say I have a table with photos.

What I want to do is on a page display the photo based on the id in the URI. Bellow the photo I want to have 10 thumbnails

相关标签:
7条回答
  • 2020-12-19 15:29

    Only one ORDER BY clause can be defined for a UNION'd query. It doesn't matter if you use UNION or UNION ALL. MySQL does support the LIMIT clause on portions of a UNION'd query, but it's relatively useless without the ability to define the order.

    MySQL also lacks ranking functions, which you need to deal with gaps in the data (missing due to entries being deleted). The only alternative is to use an incrementing variable in the SELECT statement:

    SELECT t.id, 
           @rownum := @rownum+1 as rownum 
      FROM MEDIA t, (SELECT @rownum := 0) r
    

    Now we can get a consecutively numbered list of the rows, so we can use:

    WHERE rownum BETWEEN @midpoint - ROUND(@midpoint/2) 
                     AND @midpoint - ROUND(@midpoint/2) +@upperlimit
    

    Using 7 as the value for @midpoint, @midpoint - ROUND(@midpoint/2) returns a value of 4. To get 10 rows in total, set the @upperlimit value to 10. Here's the full query:

    SELECT x.* 
      FROM (SELECT t.id, 
                   @rownum := @rownum+1 as rownum 
              FROM MEDIA t, 
                   (SELECT @rownum := 0) r) x
     WHERE x.rownum BETWEEN @midpoint - ROUND(@midpoint/2) AND @midpoint - ROUND(@midpoint/2) + @upperlimit
    

    But if you still want to use LIMIT, you can use:

      SELECT x.* 
        FROM (SELECT t.id, 
                     @rownum := @rownum+1 as rownum 
                FROM MEDIA t, 
                     (SELECT @rownum := 0) r) x
       WHERE x.rownum >= @midpoint - ROUND(@midpoint/2)
    ORDER BY x.id ASC
       LIMIT 10
    
    0 讨论(0)
  • 2020-12-19 15:29

    I'm agree with the answer suggested by malonso(+1), but if you try it with id= 1, you will get only 5 thumbnails. I don't know if you want this behaviour. If you want always 10 thumbs, you can try:

    select top 10 * from media where id > 7 - 4
    

    The problem is that select top is database dependent (in this case is a sql server clause). Other database has similar clauses:

    Oracle:

    SELECT *  media
    FROM media
    WHERE ROWNUM < 10
    AND id > 7 - 4
    

    MySQL:

    SELECT * 
    FROM media
    WHERE id > 7 - 4
    LIMIT 10
    

    So maybe you can use the last one.

    If we do it, we will have the same problem if you want the last 10 thumbs. By example, If we have 90 thumbs and we give an id=88 ... You can solve it adding an OR condition. In MySQL will be something like:

    SELECT * 
        FROM media
        WHERE id > 7 - 4
        OR (Id+5) > (select COUNT(1) from media)
        LIMIT 10
    
    0 讨论(0)
  • 2020-12-19 15:34

    If you're happy to use temp tables, your original query could be broken down to use them.

    SELECT
        *
    FROM media
    WHERE id < 7
    ORDER BY id DESC
    LIMIT 0, 4
    INTO TEMP t1;
    
    INSERT INTO t1
    SELECT
       *
    FROM media
    WHERE id >= 7
    ORDER BY id ASC
    LIMIT 0, 6;
    
    select * from t1 order by id;
    drop table t1;
    
    0 讨论(0)
  • 2020-12-19 15:40

    Try union all instead. Union requires the server to ensure that the results are unique and this conflicts with your ordering.

    0 讨论(0)
  • 2020-12-19 15:41

    I don't believe that you can have an "order by" in different sections of a UNION. Could you just do something like this:

    SELECT * FROM media where id >= 7 - 4 and id <= 7 + 4 ORDER BY id
    
    0 讨论(0)
  • 2020-12-19 15:41

    I had to solve a similar problem, but needed to account situations where we always got the same number of rows, even if the desired row was near the top or bottom of the result set (i.e. not exactly in the middle).

    This solution is a tweak from OMG Ponies' response, but where the rownum maxes out at the desired row:

    set @id = 7;
    SELECT natSorted.id 
      FROM (
           SELECT gravitySorted.* FROM (
               SELECT Media.id, IF(id <= @id, @gravity := @gravity + 1, @gravity := @gravity - 1) AS gravity 
                 FROM Media, (SELECT @gravity := 0) g
           ) AS gravitySorted ORDER BY gravity DESC LIMIT 10
      ) natSorted ORDER BY id;
    

    Here's a break down of what's happening:

    NOTE: In the example below I made a table with 20 rows and removed ids 6 and 9 to ensure a gap in ids do not affect the results

    First we assign every row a gravity value that's centered around the particular row you're looking for (in this case where id is 7). The closer the row is to the desired row, the higher the value will be:

    SET @id = 7;
    SELECT Media.id, IF(id <= @id, @gravity := @gravity + 1, @gravity := @gravity - 1) AS gravity 
      FROM Media, (SELECT @gravity := 0) g
    

    returns:

    +----+---------+
    | id | gravity |
    +----+---------+
    |  1 |       1 |
    |  2 |       2 |
    |  3 |       3 |
    |  4 |       4 |
    |  5 |       5 |
    |  7 |       6 |
    |  8 |       5 |
    | 10 |       4 |
    | 11 |       3 |
    | 12 |       2 |
    | 13 |       1 |
    | 14 |       0 |
    | 15 |      -1 |
    | 16 |      -2 |
    | 17 |      -3 |
    | 18 |      -4 |
    | 19 |      -5 |
    | 20 |      -6 |
    | 21 |      -7 |
    +----+---------+
    

    Next we order all the results by the gravity value and limit on the desired number of rows:

    SET @id = 7;
    SELECT gravitySorted.* FROM (
        SELECT Media.id, IF(id <= @id, @gravity := @gravity + 1, @gravity := @gravity - 1) AS gravity 
          FROM Media, (SELECT @gravity := 0) g
    ) AS gravitySorted ORDER BY gravity DESC LIMIT 10
    

    returns:

        +----+---------+
        | id | gravity |
        +----+---------+
        |  7 |       6 |
        |  5 |       5 |
        |  8 |       5 |
        |  4 |       4 |
        | 10 |       4 |
        |  3 |       3 |
        | 11 |       3 |
        |  2 |       2 |
        | 12 |       2 |
        |  1 |       1 |
        +----+---------+
    

    At this point we have all the desired ids, we just need to sort them back to their original order:

    set @id = 7;
    SELECT natSorted.id 
      FROM (
           SELECT gravitySorted.* FROM (
               SELECT Media.id, IF(id <= @id, @gravity := @gravity + 1, @gravity := @gravity - 1) AS gravity 
                 FROM Media, (SELECT @gravity := 0) g
           ) AS gravitySorted ORDER BY gravity DESC LIMIT 10
      ) natSorted ORDER BY id;
    

    returns:

    +----+
    | id |
    +----+
    |  1 |
    |  2 |
    |  3 |
    |  4 |
    |  5 |
    |  7 |
    |  8 |
    | 10 |
    | 11 |
    | 12 |
    +----+
    
    0 讨论(0)
提交回复
热议问题