SQL: Использование оператора CASE для одновременного обновления 1000 строк - PullRequest
0 голосов
/ 05 мая 2010

Хорошо, я хотел бы использовать CASE STATEMENT для этого, но я потерян с этим. По сути, мне нужно обновить тонну строк, но только по столбцу "position". Мне нужно UPDATE все значения позиции, которые больше, чем значение позиции, которое было удалено до position - 1 на основе id_layout и id_layout_position.

ОК, вот картинка того, как выглядит таблица: альтернативный текст http://acs.graphicsmayhem.com/images/dp_positions_table.png

Теперь допустим, что я удалил обведенную строку, это удалит position = 2 и даст мне: 0, 1, 3, 5, 6, 7 и 4. Он должен изменить положение позиций, которые больше 2, поэтому что это выглядит так: 0, 1, 2, 4, 5, 6 и 3.

ОК, вот что я получил в рамках УДАЛЕНИЯ строки, вот как это выглядит (ПРИМЕЧАНИЕ: $ module_info ['clones'] = массив клонов для удаления, в приведенной выше таблице будет нет, поскольку все значения id_clone равны 0, а $ module_info ['id'] - это значение id_module, которое мы удаляем):

// Get rid of id_clone = 0, because we can't use them.
$module_info['clones'] = array_values(array_filter($module_info['clones']));

// Selecting the positions.
if (isset($module_info['clones'][0]))
    $query = 'id_module = {int:id_module} || id_clone IN ({array_int:id_clones})';
else
    $query = 'id_module = {int:id_module}';

$request = $smcFunc['db_query']('', '
    SELECT
        id_position, id_layout_position, id_layout, position
    FROM {db_prefix}dp_module_positions
    WHERE ' . $query,
    array(
        'zero' => 0,
        'id_module' => $module_info['id'],
        'id_clones' => $module_info['clones'],          
    )
);

while ($row = $smcFunc['db_fetch_assoc']($request))
{
    $module_info['position'][$row['id_layout']]['pos' . $row['id_position'] . $row['position'] . '_' . $row['id_layout_position']] = $row['position'];
    $module_info['id_positions'][] = $row['id_position'];
}

$smcFunc['db_free_result']($request);

// Remove all module and clone positions from the layout!
$smcFunc['db_query']('', '
    DELETE FROM {db_prefix}dp_module_positions
    WHERE id_position IN ({array_int:id_positions})',
    array(
        'id_positions' => $module_info['id_positions'],
    )
);

foreach($module_info['position'] as $id_layout => $id_layout_pos)
{
    foreach($id_layout_pos as $key => $position_val)
    {
        $lPos = explode('_', $key);
        $lPosId = (int) $lPos[1];

        $smcFunc['db_query']('', '
            UPDATE {db_prefix}dp_module_positions
            SET
                position = position - 1
            WHERE position > {int:position} AND id_layout = {int:id_layout} AND id_layout_position = {int:id_layout_position}',
            array(
                'id_layout' => (int) $id_layout,
                'position' => (int) $position_val,
                'id_layout_position' => $lPosId,
            )
        );
    }
}

NEW QUESTION: ТОЛЬКО должен быть какой-то способ использовать ЗАЯВЛЕНИЕ О СЛУЧАЕ в этом UPDATE сейчас. Например, мне теперь нужно обновить все позиции до позиции - 1, для каждого id_layout и для каждого id_layout_position. НО только там, где позиция больше текущей позиции для этого значения id_layout и id_layout_position.

Есть ли способ сделать это без использования FOREACH LOOP?

Ответы [ 2 ]

2 голосов
/ 05 мая 2010

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

Псевдокод:

function deleteRow($id){
  DB::startTransaction()
  try{
    $infos = DB::getData('SELECT position FROM db_positions WHERE id_position = :ID', array(':ID' => $id));
    if(empty($infos)){
      throw("useless ID");
    }
    DB::query('DELETE FROM db_positions WHERE id_position = :ID', array(':ID' => $id));
    DB::query('UPDATE db_positions SET position = position - 1 WHERE position > :position', array(':position' => $infos['position']);
    DB::commit();
  }
  catch(Exception $e){
    DB::rollBack();
  }
}
1 голос
/ 05 мая 2010

Для обновления с непрерывной последовательностью вы можете использовать следующее:

SET @reset := 0; 
UPDATE dp_positions
SET
  positions = 
  CASE          
    WHEN id_layout_position > @reset THEN 
      IF(@pos:=0,NULL, @reset:=id_layout_position)        
    ELSE @pos := @pos + 1    
  END - 1  
ORDER BY id_layout_position, position;

Примечания: для @reset должно быть установлено минимальное значение id_layout_position, при условии, что вы перезапускаете счетчик при изменении id_layout_position, если это более сложное условие WHEN, которое необходимо изменить, и вам может потребоваться больше переменных для хранения значений предыдущая строка.
Кроме того, если IF является хаком, он никогда не будет NULL, пока вы устанавливаете @pos равным 0, а не каким-либо другим начальным значениям, я просто не знал, как заставить mysql вычислять два выражения (кто-нибудь, есть что-то элегантное есть?)

Выше проверено, но с разными именами столбцов / таблиц (возможны ошибки перепечатывания).

РЕДАКТИРОВАТЬ: Мое тестирование было не очень хорошим, вышеупомянутое имеет ошибку, и также кажется, что OP должен обойти защиту структуры, так что вот исправление и версия хранимой процедуры

Используя phpmyadmin или командную строку mysql, выполните

delimiter //
CREATE PROCEDURE `renumerate`()
BEGIN
SET @pos := -1;
SET @reset := (SELECT MIN(id_layout_position) FROM dp_position);
UPDATE
  dp_positions
SET
  position = 
    CASE          
      WHEN b > @reset THEN 
        IF(@reset:=id_layout_position, @pos:=0, @pos:=0)        
      ELSE @pos := @pos + 1    
    END
ORDER BY id_layout_position, position;
END //
delimiter ;

(в случае, если вы используете phpMyAdmin, не используйте операторы разделителя, а непосредственно определяйте разделитель в интерфейсе как //). При вызове процедуры выполните обычный SQL

$smcFunc['db_query']('', 'CALL renumerate()');

Надеюсь, в этот раз мои тесты были лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...