Question

Background:

I have the idea of a group which is a collection of items. Each item is defined by two properties: the group they belong to and their position with the group.

Table Structure

CREATE TABLE group (
id INT AUTO_INCREMENT,
--other fields
PRIMARY KEY(id)
) ENGINE = MyISAM;

CREATE TABLE items (
group_refid INT NOT NULL, --points to id in group table
group_pos SMALLINT AUTO_INCREMENT,
--other fields
PRIMARY KEY(group_refid, group_pos)
) ENGINE = MyISAM;

So right now inserting items works great. All I need to do is:

INSERT INTO items (group_refid) VALUES (1);

and the item will be appended to the end of the group. My issue comes up when I want to move one of the items. Moving an item should work as follows:

Example:

I have three items in positions 1,2,3, 4 but I want to move the one in position 3 to position 1.

Desired Result: 3,1,2,4 but their group_pos in the table should be 1,2,3,4 or 1,2,3,5

I want to do this so that the order of the elements (other than the one being moved) are preserved. Is there a good way to do this in one SQL statement? I would prefer one statement because I cannot use transactions so a one off statement is the only way to make it atomic other than using table locks.

Was it helpful?

Solution

try something like this, make your group_pos a float instead of smallint. Try this sqlFiddle in the fiddle i set @position to 1 to take 2nd position, if you want it to be first in the list you can try setting it to 0.

SET @position = 1;
UPDATE items SET group_pos = 
(SELECT SUM(group_pos)/2 as newGroupPos FROM
   (SELECT 0 as group_pos,0 as rank
    UNION
    SELECT group_pos,@rank:=@rank+1 as rank FROM items,(SELECT @rank:=0)var
    WHERE group_refid=1
    ORDER BY group_pos
   )T1 WHERE rank IN (@position,@position+1)
)
WHERE group_refid=1 AND group_pos=3;

OTHER TIPS

You could do it in 2 statements. Say you want to rearrange the items with group_refid = 37 and specifically move item with group_pos = 3 to the top (make it =1) and push all the others down.

First pull it into zero position:

UPDATE items
WHERE group_refid = 37
  AND group_pos = 3
SET group_pos = 0 ;             -- this is a temporary value 

Then push all of them down:

UPDATE items
WHERE group_refid = 37
SET group_pos = group_pos + 1
ORDER BY group_refid, group_pos DESC ;      -- the order by is required 

I think chaining IF statements should work:

UPDATE items 
SET group_pos = 
    IF(group_pos = 3, 1, # sets 3 to 1
    # otherwise, increment if pos is less than 3
    IF(group_pos < 3, group_pos + 1, group_pos))

Obviously the condition group_pos < 3 has to change if you're not moving it to the first position, e.g. to BETWEEN 2 and 3 if you move it to second place.

The performance of this query will probably be heinous, though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top