MySQL Help: Оптимизация запроса на обновление, который устанавливает ранг в соответствии с порядком другого столбца - PullRequest
2 голосов
/ 02 июля 2011

Hello StackOverflow community:

Ситуация

Я создаю запрос на обновление, который устанавливает ранги для всех записей в таблице в соответствии с тем, как каждая запись сравнивается друг с другом.Пример таблицы:

id | budget | cost | rank | rank_score  
 1 |   500  |  20  |   ?  |     ?  
 2 |   400  |  40  |   ?  |     ?  
 3 |   300  |  40  |   ?  |     ?

Таким образом, в этой таблице наибольшее значение при определении ранга имеют cost, за которым следует budget.Таким образом, запись № 2 будет выше, а запись № 3 будет второй, а № 1 будет последней.Как видите, если две записи имеют одинаковый cost, то budget разрывает связь.

Теперь, чтобы легко отследить такой «вес», я создаю rank_score столбец, который будет содержать объединение cost и budget.Таким образом, rank_score для приведенной выше таблицы будет выглядеть следующим образом:

id | budget | cost | rank | rank_score  
 1 |   500  |  20  |   ?  |   20500   
 2 |   400  |  40  |   ?  |   40400  
 3 |   300  |  40  |   ?  |   40300  

Это rank_score можно заполнить следующим образом:

UPDATE table_name
SET rank_score = CONCAT(cost, budget);

Проблема

Все в порядке, поэтомудалеко.Но теперь приходит проблема.Мне нужен только целочисленный столбец rank для нескольких вещей, таких как сортировка, но прежде всего для , показывающего пользователю ранг его записи.Конечно, этот столбец rank будет равен убыванию порядка rank_scores.Но я не могу найти способ вычислить этот столбец rank в одном запросе на обновление без необходимости выполнять подзапросы, циклы в php и т. Д.

Что я пробовал

ИтакСначала я пытался получить вычисление rank_score, например:

SELECT id,
       CONCAT(cost, budget) AS rank_score
FROM table_name ;

, а затем зациклить в php все эти rank_scores, только чтобы создать запрос, который выглядит следующим образом:

UPDATE table_name
SET rank_score = CASE id WHEN 1 THEN 20500 END,
    rank = CASE id WHEN 1 THEN 3 END
WHERE id IN (1) ;

... Конечно, этот пример запроса на обновление не завершен, поскольку в нем содержится больше предложений WHEN THEN END для каждой записи в таблице.Излишне говорить, что это уродливо, особенно если вы ожидаете иметь тысячи и тысячи записей.

Итак, в заключение, у меня уже есть способ вычисления rank_score, но я также хочу рассчитать rank (= в порядке убывания рейтинга) в том же запросе или, по крайней мере, без выполнения этого сумасшедшего цикла php и предложений CASE WHEN THEN END.

Спасибо за чтение и размышления об этом;)

Разъяснения

Разъяснение того, что сказал @ SJuan76: я не могу назначить ранг через php, поскольку есть случаи, когда пользователю будет показано фиксированное количество записей за раз (например, его страница пользователя: SELECT * WHERE user_id = 333, который может получить 1, 3 или 8 записей), и он должен знать, каков ранг для каждой записи.Присвоение ранга через php в этом случае не работает, потому что такой ранг будет относиться к извлеченным записям, а не ко всем в таблице.

Ответы [ 3 ]

2 голосов
/ 02 июля 2011

Во-первых, я бы изменил budget, cost и rank_score на целочисленный или другой числовой тип данных вместо

UPDATE table_name
SET rank_score = CONCAT(cost, budget) ;

Тогда вы бы использовали:

UPDATE table_name
SET rank_score = cost * 1000 + budget * 1  ;

Это проще, так как вам не придется иметь дело со строковыми функциями и иметь что-то вроде:

SELECT * 
FROM table_name
WHERE (conditions...)
ORDER BY rank_score DESC

(Скобка: наличие одного параметра (1000), установленного выше другого (1), эквивалентно порядку cost, budget. Попробуйте проверить:

SELECT * 
FROM table_name
ORDER BY cost DESC
       , budget DESC

Таким образом, вы можете полностью отказаться от rank_score, если, конечно, вы не планируете проводить эксперименты с различными значениями параметров.

Как отмечали другие, не рекомендуется использовать поле, в котором хранятся не данные, а вычисления. Это ненормализация. Instaed, вы сохраняете таблицу нормализованной и позволяете базе данных выполнять вычисления каждый раз, когда вам это нужно:

SELECT id, budget, cost, 
       cost*1000 + budget*1 AS rank_score_calculated
FROM table_name
ORDER BY rank_score_calculated DESC

rank_score_calculated не сохраняется в приведенном выше примере. Таким образом, вам не придется обновлять вычисляемое поле каждый раз, когда изменяется бюджет или стоимость или добавляется новая строка в таблицу.

Есть только один недостаток. Если таблица действительно большая и вам нужно, чтобы этот запрос (и расчет) выполнялся многими пользователями и очень часто, а таблица обновлялась довольно часто, то это может замедлить работу вашей базы данных. В этом случае стоит задуматься о добавлении такого поля.

Другой случай, когда нужно абсолютное rank во всех строках таблицы, как вам нужно. Поскольку в MySQL нет «оконных» функций, очень сложно написать такой запрос на чистом SQL.)


Ранг можно рассчитать с помощью переменных MySQL

SELECT *
     , @rownum:=@rownum+1 AS rank_calculated
FROM table_name
   , (SELECT @rownum:=0) AS st
ORDER BY rank_score DESC

И если вы хотите поместить эти значения в rank, используйте:

UPDATE table_name
         JOIN
         ( SELECT id
                , @rownum:=@rownum+1 AS rank_calculated
           FROM table_name
              , (SELECT @rownum:=0) AS st
           ORDER BY rank_score DESC
         ) AS r
         ON r.id = table_name.id
SET table_name.rank = r.rank_calculated ;

Вышеуказанные два запроса не являются чистым SQL. Вы можете изучить возможность перехода на другую систему daabase, которая поддерживает оконные функции, такие как Postgres, SQL-Server или Oracle.

1 голос
/ 02 июля 2011

Вы пытались разделить его на два запроса? Или с помощью подзапроса?

mysql> select p.*, (select count(0)+1 from table_name as s where s.cost >= p.cost and s.budget < p.budget) as rank from table_name as p where p.id in (1,2,3);
+----+------+--------+------+
| id | cost | budget | rank |
+----+------+--------+------+
|  1 |   20 |    500 |    3 |
|  2 |   40 |    400 |    2 |
|  3 |   40 |    300 |    1 |
+----+------+--------+------+
3 rows in set (0.00 sec)
0 голосов
/ 02 июля 2011

SQL уже может упорядочивать записи по вашему желанию, без необходимости дополнительного табличного пространства и (что гораздо важнее) без нарушения нормальных форм.

И ранг можно получить непосредственно из упорядочения, просто спросите упорядочениеВы хотите, и когда вы получаете данные, добавьте индекс из его порядка, в коде программы.

Почему вы хотите сделать это в базе данных?

...