views:

256

answers:

2

Ok, I would like to use a CASE STATEMENT for this, but I am lost with this. Basically, I need to update a ton of rows, but just on the "position" column. I need to UPDATE all position values that are great than the position value that was removed to position - 1 on a per id_layout and id_layout_position basis.

OK, here is a pic of what the table looks like: alt text

Now let's say I delete the circled row, this will remove position = 2 and give me: 0, 1, 3, 5, 6, 7, and 4. It should reposition the position values that are greater than 2, so that it looks like this: 0, 1, 2, 4, 5, 6, and 3.

OK, Here's what I got so far within the DELETING of the ROW, so here's how it looks for this (NOTE: $module_info['clones'] = an array of clones to delete, in the table above there would be none, because all id_clone values are 0, and $module_info['id'] is the id_module value that we are removing):

// 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: THERE's Just gotta be some sort of way to use a CASE STATEMENT in this UPDATE now. For Example, I now need to update all positions to position - 1, on a per id_layout, and per id_layout_position basis. BUT only where the position is greater than the current position for that id_layout and id_layout_position value.

Is there anyway to do this without using a FOREACH LOOP?

+1  A: 

To update with uninterrupted sequence you can use the following:

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;

Notes: @reset should be set to minimum value of id_layout_position, assuming that you are restarting the counter on id_layout_position change, if it is more complicated the WHEN condition will need to change and you might need more variables to hold the values from previous row.
Furthermore the IF is a hack, it will never be NULL as long you are setting @pos to 0 and not to some other starting value, I just didn't know how to force mysql to evaluate two expressions (anyone, is there something elegant there?)

The above is tested, but with different column/table names (retyping errors possible).

EDIT: My testing was not so good, the above has an error, and also seems OP needs to bypass security of a framework, so here's a correction and stored procedure version

Using phpmyadmin or mysql command line execute

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 ;

(in case you use phpMyAdmin do not use delimiter statements but directly specify delimiter in the interface as //). When calling the procedure execute normal SQL

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

Hope my testing was better this time.

Unreason
Hello, I'm not sure that SMF will allow me to do `:=` inside of a query, and I can't access the database without using the $smcFunc class, can you please take a look at how SMF does it in the 2nd code block within my question, and maybe you could incorporate the same style as that into your answer?? I'd really really really appreciate it. Thanks :)
SoLoGHoST
Also, I'm not seeing, in your query, where you are setting the position column values? Is `dp_positions` supposed to be the `position` column? But what about the table name, where does that go? I see you have `two` in there instead of the table name...
SoLoGHoST
Hm, not really itching to check the depths of SMF. Try running the above SQL as 2 statements, it might be more allergic to ; then to :=
Unreason
Re `dp_positions` retyping error... fixing
Unreason
Also alternatively make the above a stored procedure in mysql (maybe with some parameters and additional WHERE) that should make it callable with the SMF query security on.
Unreason
Sorry, I don't know how to do that. Thanks anyways :(
SoLoGHoST
Thanks for your help, greatly appreciated, though I have changed it now so that it will update all positions after deleting a row. Just seems a bit more practical to do it that way, but I am STILL STUCK in a foreach loop, anyway out of this foreach loop?
SoLoGHoST
If you are doing a single delete (or a delete of known number of consecutive records) then Arkh solution is enough. Otherwise your solution is not bad. If you really want a single query you will have to take approach similar to my solution. I see that checking 'id_layout_position' is not enough and that you also need to check 'id_layout'. If you decide to go stored procedure way I will update it to work with two levels.
Unreason
Sure, I'll give it a shot with the stored procedure way than. Thank You, I'll wait for your update to this than.
SoLoGHoST
Can you just check if stored procedures will work for you (are you able to create and use them), don't want to get into troubleshooting SMF.
Unreason
Ok, how do I use it? Just place it in the function??
SoLoGHoST
Ok, forget it, I'm still getting the "Hacking Attempt..." error message when I use this. Thanks anyways...
SoLoGHoST
+2  A: 

Before you delete a row, you could get it's position and decrement the position of all rows which have a higher position.

Pseudo code :

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();
  }
}
Arkh
Now that's a thought... hmmm, thanks :)
SoLoGHoST
Ok, there is a problem with this code, you are deleting the id position, and the UPDATE is grabbing all info from the position column instead of info from the id_position value which doesn't exist anymore... hmmm
SoLoGHoST
And than if you try to update it first you run into another issue.... OMG, how to do this...
SoLoGHoST
The thing is I need to update all positions on multiple id_layouts and multiple id_layout_positions, not just 1. You're code would work if it were just 1 though.
SoLoGHoST
Wait, don't you want to have the "position" always ordered ? If yes, you just get the position value of the row you want to delete (the first query I do), then you delete the row, and at the end you change the position value of all rows which were at a higher position (the Where position > :position) so they lose one position (set position = position -1).
Arkh
Ok, I have decided to go with this concept, however, it's still within a foreach loop. I'll post up how I am doing it, if there's anything further you can help me with.Thanks :)
SoLoGHoST