Выберите ряд и ряды вокруг него - PullRequest
4 голосов
/ 28 ноября 2009

Хорошо, допустим, у меня есть таблица с фотографиями.

То, что я хочу сделать, - это отобразить фотографию на основе идентификатора в URI. Скопируйте фотографию. Я хочу иметь 10 миниатюр ближайших фотографий, а текущая фотография должна быть в середине миниатюр.

Вот мой запрос (это только пример, я использовал 7 в качестве идентификатора):

SELECT
    A.*
FROM
  (SELECT
       *
   FROM media
   WHERE id < 7
   ORDER BY id DESC
   LIMIT 0, 4
   UNION
   SELECT
       *
   FROM media
   WHERE id >= 7
   ORDER BY id ASC
   LIMIT 0, 6
  ) as A
ORDER BY A.id

Но я получаю эту ошибку:

#1221 - Incorrect usage of UNION and ORDER BY

Ответы [ 7 ]

7 голосов
/ 29 ноября 2009

Для запроса UNION 'может быть определено только одно предложение ORDER BY. Не имеет значения, используете ли вы UNION или UNION ALL. MySQL поддерживает предложение LIMIT для частей запроса UNION 'd, но это относительно бесполезно без возможности определения порядка.

В MySQL также отсутствуют функции ранжирования, которые вам нужны для устранения пробелов в данных (пропущены из-за удаления записей). Единственная альтернатива - использовать инкрементную переменную в операторе SELECT:

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

Теперь мы можем получить последовательно пронумерованный список строк, поэтому мы можем использовать:

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

Используя 7 в качестве значения для @midpoint, @midpoint - ROUND(@midpoint/2) возвращает значение 4. Чтобы получить всего 10 строк, установите значение @upperlimit равным 10. Вот полный запрос:

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

Но если вы все еще хотите использовать LIMIT, вы можете использовать:

  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
4 голосов
/ 11 января 2012

Я решаю эту проблему, используя следующий код:

SELECT  A.* FROM  (
   (
      SELECT  *  FROM gossips
      WHERE id < 7
      ORDER BY id DESC
      LIMIT 2
   )
  UNION
   (
      SELECT * FROM gossips
      WHERE id > 7
      ORDER BY id ASC
      LIMIT 2
   )

 ) as A
ORDER BY A.id
2 голосов
/ 28 ноября 2009

Я не верю, что вы можете иметь «заказ» в разных разделах UNION. Не могли бы вы просто сделать что-то вроде этого:

SELECT * FROM media where id >= 7 - 4 and id <= 7 + 4 ORDER BY id
1 голос
/ 29 ноября 2009

Попробуйте вместо объединить все . Union требует, чтобы сервер обеспечил уникальность результатов, что противоречит вашему заказу.

1 голос
/ 29 ноября 2009

Если вы счастливы использовать временные таблицы, ваш исходный запрос может быть разбит на их использование.

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;
1 голос
/ 29 ноября 2009

Я согласен с ответом, предложенным Малонсо (+1), но если вы попробуете его с id = 1, вы получите только 5 миниатюр. Я не знаю, хочешь ли ты такого поведения. Если вы хотите всегда 10 пальцев, вы можете попробовать:

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

Проблема в том, что select top зависит от базы данных (в данном случае это предложение сервера sql). Другая база данных имеет аналогичные пункты:

Oracle:

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

MySQL:

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

Так что, может быть, вы можете использовать последний.

Если мы сделаем это, у нас возникнет та же проблема, если вы захотите последние 10 пальцев. Например, если у нас 90 пальцев и мы даем id = 88 ... Вы можете решить это, добавив условие ИЛИ. В MySQL будет что-то вроде:

SELECT * 
    FROM media
    WHERE id > 7 - 4
    OR (Id+5) > (select COUNT(1) from media)
    LIMIT 10
0 голосов
/ 14 ноября 2014

Мне пришлось решить аналогичную проблему, но мне нужно было учитывать ситуации, когда мы всегда получали одинаковое количество строк, даже если требуемая строка была в верхней или нижней части набора результатов (то есть не совсем в середине). * +1001 *

Это решение - твик из ответа OMG Ponies, но в котором максимальное значение rownum в желаемой строке:

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;

Вот пример того, что происходит:

ПРИМЕЧАНИЕ. В приведенном ниже примере я создал таблицу с 20 строками и удалил идентификаторы 6 и 9, чтобы гарантировать, что разрыв в идентификаторах не влияет на результаты

Сначала мы присваиваем каждой строке значение силы тяжести, центрированное вокруг конкретной строки, которую вы ищете (в данном случае, когда id равно 7). Чем ближе строка к нужной строке, тем выше будет значение:

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

возвращается:

+----+---------+
| 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 |
+----+---------+

Далее мы упорядочиваем все результаты по значению силы тяжести и ограничиваем желаемое количество строк:

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

возвращается:

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

На данный момент у нас есть все желаемые идентификаторы, нам просто нужно отсортировать их в исходном порядке:

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;

возвращается:

+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  7 |
|  8 |
| 10 |
| 11 |
| 12 |
+----+
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...