MySQL查询在图片中重新分配标签并避免重复

I have the following structure in MySQL, table 'images_tags':

id | image_id | tag_id
----------------------
1  |  243     | 52
2  |  94      | 52
3  |  56      | 52
4  |  56      | 53
5  |  56      | 54

Table 'tags':

id  | tag     
---------------
52  |  fashion 
53  |  cars  
54  |  sports  
55  |  bikes  

I'm building a function in my CMS to delete a tag, for that I need to reassign all the pictures containing that tag to a another tag. The problem is the picture can already have assigned the new tag and I want to avoid possible duplicated records.

I couldnt find the right way to do it straight in SQL so I tried in PHP as follows:

$result=mysql_query("select image_id from images_tags where tag_id='".$oldtag."'");
while($row=mysql_fetch_assoc($result)){

    $result2=mysql_query("select id from images_tags
    where image_id='".$row['image_id']."' and tag_id='".$newtag."'");

    if(mysql_num_rows($result2)==0){

        mysql_query("update images_tags set tag_id='".$newtag."'
        where image_id='".$row['image_id']."' and tag_id='".$newtag."'");

    }

}

As you can see, my code is very bad and non-efficient as I'm running queries inside iterations. Do you know a better way to do this? Preferably in just one SQL query. Thanks.

When I think of this problem, I think of it more easily as "insert the new image tags, if appropriate, then delete the old ones".

The following code takes this approach:

create unique index image_tags_unique on image_tags(image_id, tag_id);

insert into image_tags
    select image_id, <newtagid>
    from image_tags
    where tag_id = <oldtagid>
    on duplicate key ignore;

delete from image_tags
    where tag_id = <oldtagid>;

The first step creates a unique index on image_tags, so duplicates are not allowed in the table.

The second inserts the new records, ignoring any errors generated by duplicates.

The third deletes the old records.

To be honest, you can also do this with the ignore keyword on update instead of insert step. However, ignore is very general, so -- in theory -- there could be another error being ignored incorrectly. The on duplicate key ignore is much more specific about what is allowed.

I think this will add new rows to images_tags that meet your criteria.

insert into images_tags (image_id, tag_id)
select image_id, tag_id
from (select i.image_id image_id, t.id tag_id
      from images_tags i
      join tags t
      where i.tag_id = $oldtag and t.id != $oldtag) crossp
left join images_tags existing
using (image_id, tag_id)
where existing.id is null
group by image_id

The crossp subquery creates a full cross-product between all the image_ids that currently have the old tag, and all the tags other than the old tag. Then we do a left join with the existing images_tags, and use the null check to filter out all the pairs that already exist. This generates a list of image_id and tag_id pairs that do not match anything in the database. Finally we group by image_id so we just add one new row per image.

After you do this, you can delete the rows with tag_id = $oldtag.

SQLFIDDLE

The only problem with this is that it changes the IDs of the images_tags rows. There may be a way to do it all in one step with an UPDATE query, which wouldn't have that problem, but I'm not sure how to turn my query into that.