MySQL update an AUTO_INCREMENT pk to preserve row ordering - mysql

I have a table with three columns: id, foreign_id, and tag. Queries on this table are ordered first by foreign_id, then by tag, but we want to deprecate the tag column in favor of the more reliable and auto-generated id. In doing so, we also need to preserve the ordering data stored in the tag column without keeping tag around. This ordering only makes sense within the scope of the foreign_id column.
To solve this problem, we've decided to update the ids within the scope of each foreign_id such that the order of the ids preserves the tag order information.
How does one update an AUTO_INCREMENT primary key column such that it gets assigned the next value in the counter without changing the rest of the row?
Alternatively, how would one copy an entire row (minus the pk) into a new row and delete the old row?

SELECT MAX(id) INTO #maxID FROM the_table;
UPDATE the_table SET id = id + #maxID;
SET #i := 0;
UPDATE the_table SET id = (#i := #i + 1) ORDER BY foreign_id, tag;
I am not 100% positive this will work; I don't usually do this kind of things with UPDATE statements. Alternatively, you could replace that last UPDATE with:
INSERT INTO the_table(id, foreign_id, tag)
SELECT (#i := #i + 1) AS `new_id`, foreign_id, tag
FROM the_table
ORDER BY foreign_id, tag
;
DELETE FROM the_table
WHERE id >= #maxID
;
In either case, this assumes current id values are always >= 0.

Related

A column that auto increments in MYSQL

I need to create a column that auto increments from 1- (however number of rows there are). However, I need the column to reorder itself depending on the Order of my probability column. Is is possible?
I'd generally recommend against implementing that kind of ordering calculation as an explicit table field. Keeping such information up to date would create more and more overhead as the table grows. Instead, you could just ORDER BY your probability column; or if you really need the "rank" in the query result, there are a number of ways to do that, something like this should work:
SELECT #seq := seq + 1, d.*
FROM theRealData AS d, (SELECT #seq := 0) AS init
ORDER BY theRealData.probability
;
Pseudo code (i'm not looking up exact syntax as I write this, so it there might be some things I overlook) for the stored procedure I mention in the comments below (may need adjustments if I have the ordering reversed.)
CREATE PROCEDURE theProc (newID INT)
BEGIN
DECLARE newProb INT; //Not sure if it is int, but for the sake of example
DECLARE seqAt INT;
SET newProb = SELECT probability FROM theTable WHERE ID = newID;
SET seqAt = SELECT IFNULL(min(seq), 1) FROM theTable WHERE probability > newProb;
UPDATE theTable SET seq = seq + 1 WHERE seq >= seqAt;
UPDATE theTable SET seq = seqAt WHERE ID = newID;
END
If you pass all the fields inserted, instead of just the new row's id after it is inserted, then the procedure can do the insert itself and use last_insert_id() to do the rest of the work.
Modifying the primary key values can become very expensive, specially if you have related tables that point to it.
If you need to keep an order by probability, I would suggest adding an extra column with the probability_order. You can update this column after every insert or every minute, hour or day.
Alternatively, as #Uueerdo says you can just use ORDER BY when querying the table rows.

MySQL: Update every second row string

I need to be able to update every even (2, 4, 6, etc) row string within the image column ... the name will end with an extension .png and I want to insert some text before the .jpg
So from MyImage.png to MyImage-Small.png
Then I need to be able to do the same for every odd (1, 3, 5, etc) row
Assuming the table has a numeric primary key column id, you can do this for even rows:
update mytable set picture = replace(picture, '.png', '-Small.png') where id % 2 = 0;
Similarly just change the predicate to id % 2 = 1 for odd rows
If the table has no numeric key which is a continuous sequence then we can still achieve selective updates for odd and even rows by using following update.
UPDATE mytable o
INNER JOIN
(SELECT id, #row :=#row +1,mod(#row,2) AS num
FROM mytable, (SELECT #row := 0) r)AS t
ON o.id = t.id -- any indexed field which is unique for the table
SET o.image =
CASE num
WHEN 0 THEN 'even_row'
WHEN 1 THEN 'odd_row'
END;
All this query is doing is generating the sequence for the table then joining it with the original table so we update odd and even rows separately.

How to delete entries that share similar pattern in MySQL

I have a column that may contain entries like this:
abc.yahoo.com
efg.yshoo.com
hij.yahoo.com
I need to delete all the duplicates and LEAVE ONE ONLY as I don't need the others. Such command can be easily done if I know the second part (ex: yahoo.com) but my problem is that the part (yahoo.com) is not fixed. I may have entries such as:
abc.msn.com
efg.msn.com
hij.msn.com
And I want to treat all these cases at once. Is this possible?
To delete the duplicates you can use
DELETE FROM your_table t1
LEFT JOIN
(
SELECT MIN(id) AS id
FROM your_table
GROUP BY SUBSTRING_INDEX(REVERSE(col), '.', 2)
) t2 ON t2.id = t1.id
WHERE b.id IS NULL
If you need to create an UNIQUE constraint for that you can do the following
1.Add another field to hold the domain value
ALTER TABLE your_table ADD COLUMN `domain` VARCHAR(100) NOT NULL DEFAULT '';
2.Update it with the correct values
UPDATE your_table set domain = REVERSE(SUBSTRING_INDEX(REVERSE(col), '.', 2));
3.Add the unique constraint
ALTER IGNORE TABLE your_table ADD UNIQUE domain (domain);
4.Add before insert and before update trggers to set the domain column
DELIMITER $$
CREATE TRIGGER `your_trigger` BEFORE INSERT ON `your_table ` FOR EACH ROW
BEGIN
set new.domain = REVERSE(SUBSTRING_INDEX(REVERSE(new.col1), '.', 2));
END$$
CREATE TRIGGER `your_trigger` BEFORE UPDATE ON `your_table ` FOR EACH ROW
BEGIN
set new.domain = REVERSE(SUBSTRING_INDEX(REVERSE(new.col1), '.', 2));
END$$
DELIMITER ;
Note: this assumes the domain is the last 2 words when separated by '.', it will not work for a domain such as ebay.co.uk . For that you will probably need to make a stored function which returns the domain for a given host and use it instead of REVERSE(SUBSTRING_INDEX....
This is assuming that you just want to take out the letters before the first . then group on the column:
DELETE a FROM tbl a
LEFT JOIN
(
SELECT MIN(id) AS id
FROM tbl
GROUP BY SUBSTRING(column, LOCATE('.', column))
) b ON a.id = b.id
WHERE b.id IS NULL
Where id is your primary key column name, and column is the column that contains the values to group on.
This will also account for domains like xxx.co.uk where you have two parts at the end.
Make sure you have a backup of your current data or run this operation within a transaction (where you can ROLLBACK; if it didn't fit your needs).
EDIT: If after deleting the duplicates you want to replace the letters before the first . with *, you can simply use:
UPDATE tbl
SET column = CONCAT('*', SUBSTRING(column, LOCATE('.', column)))

mysql query mass increment

I have a large table of data with a new field added called uniq_id what I am looking for is a query I can run that will update and increment this field for each row, without having to write a script to do this.
Any ideas?
somethink like that (hard to make better without your structure)
SET #rank = 0;
UPDATE <your table> JOIN (SELECT #rank:= #rank+ 1 AS rank, <your pk> FROM <your table> ORDER BY rank DESC)
AS order USING(<your pk>) SET <your table>.uniq_id = order.rank;
or easier
SET #rank=0;
UPDATE <your table> SET uniq_id= #rank:= (#rank+1) ORDER BY <anything> DESC;
What other columns does the table have? If you define the new column with AUTO_INCREMENT MySQL will fill it with sequential values starting from 1. There's a catch though: the column has to be a key, or at least a part of the key (see the documentation for details). If you don't have a primary key in the table already, you can simply do this:
alter table MyTable add uniq_id int auto_increment, add primary key (uniq_id);
If you can't change the keys in the table and just want to fill the values of the new column as a one-off thing, you can use this update statement:
update MyTable set uniq_id = (#count := coalesce(#count+1, 1));
(coalesce returns its first non-null argument; it effectively establishes the value of the column for the first row.)

Is it really no solution to update multiple records in MySQL?

I want to do all these update in one statement.
update table set ts=ts_1 where id=1
update table set ts=ts_2 where id=2
...
update table set ts=ts_n where id=n
Is it?
Use this:
UPDATE `table` SET `ts`=CONCAT('ts_', `id`);
Yes you can but that would require a table (if only virtual/temporary), where you's store the id + ts value pairs, and then run an UPDATE with the FROM syntax.
Assuming tmpList is a table with an id and a ts_value column, filled with the pairs of id value, ts value you wish to apply.
UPDATE table, tmpList
SET table.ts = tmpList.ts_value
WHERE table.id = tmpList.id
-- AND table.id IN (1, 2, 3, .. n)
-- above "AND" is only needed if somehow you wish to limit it, i.e
-- if tmpTbl has more idsthan you wish to update
A possibly table-less (but similar) approach would involve a CASE statement, as in:
UPDATE table
SET ts = CASE id
WHEN 1 THEN 'ts_1'
WHEN 2 THEN 'ts_2'
-- ..
WHEN n THEN 'ts_n'
END
WHERE id in (1, 2, ... n) -- here this is necessary I believe
Well, without knowing what data, I'm not sure whether the answer is yes or no.
It certainly is possible to update multiple rows at once:
update table table1 set field1='value' where field2='bar'
This will update every row in table2 whose field2 value is 'bar'.
update table1 set field1='value' where field2 in (1, 2, 3, 4)
This will update every row in the table whose field2 value is 1, 2, 3 or 4.
update table1 set field1='value' where field2 > 5
This will update every row in the table whose field2 value is greater than 5.
update table1 set field1=concat('value', id)
This will update every row in the table, setting the field1 value to 'value' plus the value of that row's id field.
You could do it with a case statement, but it wouldn't be pretty:
UPDATE table
SET ts = CASE id WHEN 1 THEN ts_1 WHEN 2 THEN ts_2 ... WHEN n THEN ts_n END
I think that you should expand the context of the problem. Why do you want/need all the updates to be done in one statement? What benefit does that give you? Perhaps there's another way to get that benefit.
Presumably you are interacting with sql via some code, so certainly you can simply make sure that the three updates all happen atomically by creating a function that performs all three of the updates.
e.g. pseudocode:
function update_all_three(val){
// all the updates in one function
}
The difference between a single function update and some kind of update that performs multiple updates at once is probably not a very useful distinction.
generate the statements:
select concat('update table set ts = ts_', id, ' where id = ', id, '; ')
from table
or generate the case conditions, then connect it to your update statement:
select concat('when ', id, ' then ts_', id) from table
You can use INSERT ... ON DUPLICATE KEY UPDATE. See this quesion: Multiple Updates in MySQL
ts_1, ts_2, ts_3, etc. are different fields on the same table? There's no way to do that with a single statement.