Removing duplicate entries from a Mysql database - mysql

I've got a table with three columns, obj1, obj2 (both varchars) and the distance between the objects. Unfortunately the way the data was constructed, I've double the number of entries, for example,
obj1 obj2 distance
c1 c2 10.5
c2 c1 10.5
Want I want is to be able to delete one of the entries listed. I've thought and tried to use the Exists clause, but had no luck. I'm wondering if this requires a stored procedure?
Any help would be gratefully received!
Jim

mysql> create table doubles(a int,b int,c int);
Query OK, 0 rows affected (0.11 sec)
mysql> insert into doubles values (1,2,10),(2,1,10),(1,3,12),(3,1,12),(2,3,13);
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from doubles;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 2 | 10 |
| 2 | 1 | 10 |
| 1 | 3 | 12 |
| 3 | 1 | 12 |
| 2 | 3 | 13 |
+------+------+------+
5 rows in set (0.00 sec)
mysql> DELETE a FROM doubles a JOIN doubles b ON a.a = b.b AND a.b = b.a AND a.a > b.a;
Query OK, 2 rows affected (0.03 sec)
mysql> select * from doubles;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 2 | 10 |
| 1 | 3 | 12 |
| 2 | 3 | 13 |
+------+------+------+
3 rows in set (0.00 sec)
The last clause (a.a > b.a) could equally be a.a < b.a, we just have to decide which one of the doubles should go.

If you can guarantee that every row has a "duplicate" with obj1 and obj2 values reversed, then you can remove one such row for each duplicate by doing
DELETE FROM dist WHERE obj1 > obj2
where dist is the name of your table.
If your table has rows where obj1 equals obj2, then you could make a unique index on (obj1,obj2):
ALTER IGNORE TABLE dist ADD UNIQUE INDEX dist_index (obj1,obj2)
The above command will drop rows from the table whenever the unique index constraint is not satisfied. (The first row where obj1 equals obj2, the row will be kept because the unique index constraint is still satisfied. The second row where obj1 equals obj2 will be dropped, because the second row contradicts the uniqueness constraint.)
You can choose to keep the unique index, or, if you wish to drop it, the command would be:
ALTER TABLE dist DROP INDEX dist_index

Related

what syntax to use to update a SET column in mysql?

I created a column called oilcompany that has SET data (Hunt, Pioneer, Chevron, BP)
I can enter any one of those into the oilcompany column and change from one to another one but I can not figure out how to change from one oilcompany to multiple oilcompany (eg. Hunt and BP)... any suggestion?
In the MySQL documentation there are not examples for UPDATE statements, but I normally use two ways to update these kind of columns:
Using text values
Using numeric values
Creating the test environment
mysql> CREATE TABLE tmp_table(
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> oilcompany SET('Hunt', 'Pioneer', 'Chevron', 'BP')
-> );
Query OK, 0 rows affected (0.54 sec)
mysql> INSERT INTO tmp_table(oilcompany) VALUES ('Hunt'), ('Pioneer');
Query OK, 2 rows affected (0.11 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM tmp_table;
+----+------------+
| id | oilcompany |
+----+------------+
| 1 | Hunt |
| 2 | Pioneer |
+----+------------+
2 rows in set (0.00 sec)
Alternative#1: Using Text Values
As a SET is a collection of ENUM elements, and any ENUM element can be treated as a string, then we can do things like:
mysql> UPDATE tmp_table
-> SET oilcompany = 'Hunt,BP'
-> WHERE id = 1;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM tmp_table;
+----+------------+
| id | oilcompany |
+----+------------+
| 1 | Hunt,BP |
| 2 | Pioneer |
+----+------------+
2 rows in set (0.00 sec)
Alternative#2: Using Numeric Values
Any SET element is stored internally as a 64bit number containing the combination of the bits that represent each SET element.
In our table: 'Hunt'=1, 'Pioneer'=2, 'Chevron'=4, 'BP'=8.
Also, mysql allows to use these numbers instead of text values. If we need to see the numeric value in the select, we need to use the SET column inside a numeric expression (E.g. adding zero).
Let's see the current values:
mysql> SELECT id, oilcompany+0, oilcompany FROM tmp_table;
+----+--------------+------------+
| id | oilcompany+0 | oilcompany |
+----+--------------+------------+
| 1 | 9 | Hunt,BP |
| 2 | 2 | Pioneer |
+----+--------------+------------+
2 rows in set (0.00 sec)
Here 9 = 'Hunt' (1) + 'BP' (8) and 2 = 'Pioneer' (2).
Now, let's change the Pioneer to 'Hunt' (1) + 'Chevron' (4):
mysql> UPDATE tmp_table
-> SET oilcompany = 5
-> WHERE id = 2;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT id, oilcompany+0, oilcompany FROM tmp_table;
+----+--------------+--------------+
| id | oilcompany+0 | oilcompany |
+----+--------------+--------------+
| 1 | 9 | Hunt,BP |
| 2 | 5 | Hunt,Chevron |
+----+--------------+--------------+
2 rows in set (0.00 sec)

Use of UNION when creating tables

Database-1
create table sample (
id INT,
nm VARCHAR(10)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
UNION=(for tables from another databases);
So, when we do union what actually it meance?
Please explain, I am getting confusing for this type of UNION.
That looks close to the syntax for creating a merge table, but it has the engine type wrong. Your statement will ignore the union clause and simply create a new, empty table. In order to create merge table you need to specify ENGINE=MERGE.
14.3 The MERGE Storage Engine
The MERGE storage engine, also known as the MRG_MyISAM engine, is a
collection of identical MyISAM tables that can be used as one.
The tables you specify in the UNION clause there, must all be identical - ie, having the same index and column specification, and they must all be in the same order in each table.
After that, can you query your merge table and access the data from all of the tables that form it.
You can also insert into your merge table, which is something you cannot do with a view:
You can optionally specify an INSERT_METHOD option to control how
inserts into the MERGE table take place. Use a value of FIRST or LAST
to cause inserts to be made in the first or last underlying table,
respectively. If you specify no INSERT_METHOD option or if you specify
it with a value of NO, inserts into the MERGE table are not permitted
and attempts to do so result in an error.
Anyway, the doco has the rest of the information if you want to peruse more - I've never felt the need to use this type of table.
Example:
mysql>
mysql> create table t2 (
-> id integer primary key auto_increment,
-> val char(20)
-> ) engine=myisam;
Query OK, 0 rows affected (0.05 sec)
mysql>
mysql> insert into t1(val) values ('table1 a'), ('table1 b');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into t2(val) values ('table2 a'), ('table2 b');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql>
mysql>
mysql> create table mt (
-> id integer primary key auto_increment,
-> val char(20)
-> ) engine=merge union=(t1,t2) insert_method=last;
Query OK, 0 rows affected (0.04 sec)
mysql>
mysql> select * from mt;
+----+----------+
| id | val |
+----+----------+
| 1 | table1 a |
| 2 | table1 b |
| 1 | table2 a |
| 2 | table2 b |
+----+----------+
4 rows in set (0.00 sec)
mysql> insert into mt(val) values ('12345');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mt;
+----+----------+
| id | val |
+----+----------+
| 1 | table1 a |
| 2 | table1 b |
| 1 | table2 a |
| 2 | table2 b |
| 3 | 12345 |
+----+----------+
5 rows in set (0.01 sec)
mysql> select * from t2;
+----+----------+
| id | val |
+----+----------+
| 1 | table2 a |
| 2 | table2 b |
| 3 | 12345 |
+----+----------+
3 rows in set (0.00 sec)

How to Reset auto_increment value in MySQL

I am not able to reset the auto_increment value even after making changes after referring to other post
I tried :
ALTER TABLE tablename AUTO_INCREMENT = 101
ALTER TABLE users AUTO_INCREMENT=1001;
or if you haven't already added an id column, also add it
ALTER TABLE users ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT,
ADD INDEX (id);
But still not working
Check this :
mysql> ALTER TABLE table2 ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT,
-> ADD INDEX (id);
Query OK, 5 rows affected (0.17 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from table2;
+----------------+----+
| name | id |
+----------------+----+
| Abhilash Gupta | 1 |
| John | 2 |
| Peter | 3 |
| Clarke | 4 |
| Virat | 5 |
+----------------+----+
5 rows in set (0.00 sec)
mysql> ALTER TABLE table2 AUTO_INCREMENT=101;
Query OK, 5 rows affected (0.25 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from table2;
+----------------+----+
| name | id |
+----------------+----+
| Abhilash Gupta | 1 |
| John | 2 |
| Peter | 3 |
| Clarke | 4 |
| Virat | 5 |
+----------------+----+
5 rows in set (0.00 sec)
mysql>
I want the value of id to start from 101.
Thanks in advance
If you want to change the existing IDs to start from 101, use:
UPDATE table2
SET id = id + 100;
The auto_increment setting is used for the ID of the next row to be added, it has no effect on existing rows.
Follow this link for reference to AUTO INCREMENT
Now what you are doing is i think correct, but the changes are not reflected because you did not try to enter a new row to the database. Alter command changes the AUTO INCREMENTS value but that will only be reflected in the next insert to the database. It will not affect the data that is already present in the TABLE. Try entering a new row to the DB and check if the ID Value changes.
If not then post the output after entering that row.
This is how it should be written
cur.execute('''ALTER TABLE tablename AUTO_INCREMENT=0''')

Is it possible to make MySQL use an index for the ORDER by 1 DESC, 2 ASC?

I have a materialized path-driven bulletin board. It is using the following query to get the messages in order,
SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100
where root is an id of the root message for the thread, and path is a materialized path.
However, none of my efforts to make this query to use indexes were of any success.
mysql> explain extended select path from Board order by root desc, path asc limit 100;
+-------+---------------+----------+---------+------+-------+----------+----------------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra
+-------+---------------+----------+---------+------+-------+----------+-----------------------------
| index | NULL | rootpath | 261 | NULL | 21998 | 100.00 | Using index; Using filesort
At the moment it is showing the number of all the rows in the table under rows column. I am wondering, is there a way to reduce that number or optimize the query any other way?
CREATE TABLE `Board` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`path` varchar(255) NOT NULL DEFAULT '0',
`root` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `root` (`root`),
KEY `path` (`path`),
KEY `rootpath` (`root`,`path`)
)
The main problem with the query is pagination - I need to start the second page right from the message next to the last one on the previous page. That's why I want it the straight way - without sublelects and stuff.
The current setup is not quite nice though, as it starts the second page from the middle of the thread, but it is quite logical at least.
The problem you are facing is explained nicely in this article. And the important part is:
Most typical case is when you want to order by two colums in different
directions: … ORDER BY price ASC, date DESC LIMIT 10 If you have
indexed on (price,date) in ascending order you will not be able to
optimize this query well – external sort (“filesort”) will be needed.
If you would be able to build index on price ASC, date DESC the same
query could retrive data in aready sorted order.
Also the article mentions a valid workaround for the problem as well: Having the second "order" clause reversed:
This is however something you can workaround by having something like
“reverse_date” column and using it for sort. With MySQL 5.0 you even
can use triggers to update it as real date updates so it becomes less
ugly. In fact this is for example why you would see
“reverse_timestamp” field in Wikipedia table structure.
Also from official MySQL documentation:
In some cases, MySQL cannot use indexes to resolve the ORDER BY,
although it still uses indexes to find the rows that match the WHERE
clause. These cases include the following:
.....
You mix ASC and DESC:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
As a suggestion you would better have a reversed_root column which is Integer.MAX_VALUE - root AND have an index on (reversed_root, path). Then you can have a query as:
SELECT * FROM Board ORDER by reversed_root ASC,path ASC LIMIT 0,100
Your original query
SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
Create a table to hold the negative value of root called BoardDisplayOrder, where you add the new column called rootinv.
First here is the sample data and your original query:
mysql> drop database if exists YourCommonSense;
Query OK, 2 rows affected (0.06 sec)
mysql> create database YourCommonSense;
Query OK, 1 row affected (0.00 sec)
mysql> use YourCommonSense
Database changed
mysql> CREATE TABLE `Board` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `path` varchar(255) NOT NULL DEFAULT '0',
-> `root` int(11) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`id`),
-> KEY `root` (`root`),
-> KEY `path` (`path`),
-> KEY `rootpath` (`root`,`path`)
-> );
Query OK, 0 rows affected (0.11 sec)
mysql> INSERT INTO Board (path,root) VALUES
-> ('Rolando Edwards',30),
-> ('Daniel Edwards',30),
-> ('Pamela Edwards',30),
-> ('Dominiuqe Edwards',40),
-> ('Diamond Edwards',40),
-> ('Richard Washington',50),
-> ('George Washington',50),
-> ('Synora Washington',50);
Query OK, 8 rows affected (0.05 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM Board;
+----+--------------------+------+
| id | path | root |
+----+--------------------+------+
| 2 | Daniel Edwards | 30 |
| 3 | Pamela Edwards | 30 |
| 1 | Rolando Edwards | 30 |
| 5 | Diamond Edwards | 40 |
| 4 | Dominiuqe Edwards | 40 |
| 7 | George Washington | 50 |
| 6 | Richard Washington | 50 |
| 8 | Synora Washington | 50 |
+----+--------------------+------+
8 rows in set (0.00 sec)
mysql> SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+--------------------+------+
| id | path | root |
+----+--------------------+------+
| 7 | George Washington | 50 |
| 6 | Richard Washington | 50 |
| 8 | Synora Washington | 50 |
| 5 | Diamond Edwards | 40 |
| 4 | Dominiuqe Edwards | 40 |
| 2 | Daniel Edwards | 30 |
| 3 | Pamela Edwards | 30 |
| 1 | Rolando Edwards | 30 |
+----+--------------------+------+
8 rows in set (0.00 sec)
mysql> EXPLAIN SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
| 1 | SIMPLE | Board | index | NULL | rootpath | 261 | NULL | 8 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)
mysql>
Next, create the table BoardDisplayOrder using rootinv and an index involving rootinv:
mysql> CREATE TABLE BoardDisplayOrder LIKE Board;
Query OK, 0 rows affected (0.09 sec)
mysql> ALTER TABLE BoardDisplayOrder DROP INDEX root;
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE BoardDisplayOrder DROP INDEX path;
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE BoardDisplayOrder DROP INDEX rootpath;
Query OK, 0 rows affected (0.08 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE BoardDisplayOrder ADD COLUMN rootinv int(11) NOT NULL;
Query OK, 0 rows affected (0.17 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW CREATE TABLE BoardDisplayOrder \G
*************************** 1. row ***************************
Table: BoardDisplayOrder
Create Table: CREATE TABLE `boarddisplayorder` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`path` varchar(255) NOT NULL DEFAULT '0',
`root` int(11) NOT NULL DEFAULT '0',
`rootinv` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `rootpathid` (`rootinv`,`path`,`id`,`root`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql>
Then, populate BoardDisplayOrder:
mysql> INSERT INTO BoardDisplayOrder (id,path,root,rootinv)
-> SELECT id,path,root,-root FROM Board;
Query OK, 8 rows affected (0.06 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM BoardDisplayOrder;
+----+--------------------+------+---------+
| id | path | root | rootinv |
+----+--------------------+------+---------+
| 7 | George Washington | 50 | -50 |
| 6 | Richard Washington | 50 | -50 |
| 8 | Synora Washington | 50 | -50 |
| 5 | Diamond Edwards | 40 | -40 |
| 4 | Dominiuqe Edwards | 40 | -40 |
| 2 | Daniel Edwards | 30 | -30 |
| 3 | Pamela Edwards | 30 | -30 |
| 1 | Rolando Edwards | 30 | -30 |
+----+--------------------+------+---------+
8 rows in set (0.00 sec)
mysql>
Now, run your query against BoardDisplayOrder but without DESC on rootinv:
mysql> SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+--------------------+------+
| id | path | root |
+----+--------------------+------+
| 7 | George Washington | 50 |
| 6 | Richard Washington | 50 |
| 8 | Synora Washington | 50 |
| 5 | Diamond Edwards | 40 |
| 4 | Dominiuqe Edwards | 40 |
| 2 | Daniel Edwards | 30 |
| 3 | Pamela Edwards | 30 |
| 1 | Rolando Edwards | 30 |
+----+--------------------+------+
8 rows in set (0.00 sec)
mysql> EXPLAIN SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
| 1 | SIMPLE | BoardDisplayOrder | index | NULL | rootpathid | 269 | NULL | 8 | Using index |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Give it a try!!!
CAVEAT
This was easy to do because root was INT.
If root was a VARCHAR, rootinv would have to be a flipflop of characters. In other words,
A -> Z
B -> Y
...
M -> N
N -> M
...
Y -> B
Z -> A
This would principly work for any field you need to perform DESC on. The problem stems from the fact that MySQL does not order keys internally within in index as ASC or DESC. Everything in an index is ascending. That is why when you see handler stats in SHOW GLOBAL STATUS LIKE 'handler%';, you see the following:
handler_read_first
handler_read_next
handler_read_prev
handler_read_last (MySQL 5.5 only)
and so forth.
According to the current MySQL Documentation
An index_col_name specification can end with ASC or DESC. These
keywords are permitted for future extensions for specifying ascending
or descending index value storage. Currently, they are parsed but
ignored; index values are always stored in ascending order.
Give it a try!!!
UPDATE 2012-05-04 06:54 EDT
#frail's comment about my answer
ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root) seems pretty unnecessary to me, ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path) should be enough
The reason my solution had ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root) is to provide a covering index. A covering index in this instance will:
always have the needed columns for retrieval
will improve the quality of the explain plan because
the query will never read from the table for data retrieval
the query will only read from the index for data retrieval
result in an index range scan
Think of the original query,
SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
This requires retrieving the three columns path, id, and root. Thus, they need to be in the index. Of course, the increased size of the index would be the tradeoff. If the Board table was very large, some would not worry about the space if the retrieval could be made faster. If the rootpath index were just (rootinv,path), then every index range scan would be accompanied by a ref lookup into the table for the remaining columns. This is the reason I chose ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);
In this situation where the data itself does not lend itself to retrieval in the way you need, it may be appropriate to create an additional column that has the information you need -- this will allow you to retrieve on the order you want.
It would especially be appropriate in this case as it seems the data itself isn't updated once you save it. Once your messages are posted, they aren't updated (or so it seems from my initial reading).
The steps I'd recommend, assuming you take this path, would be:
Add a new column root_path to the table.
Execute this update statement update Board set root_path = root + path. (You may have to adapt that based on the data types of the existing columns.)
Whenever you add a new row to the table, also add this new column. (This could be handled with a trigger, though I'd be wary of triggers since they can be overlooked when people are changing other parts of the code.)
Then you should be able to set an index on that new column and write your selects against that column -- hitting your indexes as you wish.
I believe this will work even though one of the keys has to be sorted in opposite order.
CREATE TABLE foo
(
id serial NOT NULL,
int_field integer DEFAULT 0,
varchar_field character varying(255),
composite_field character varying(255),
CONSTRAINT foo_pkey PRIMARY KEY (id )
);
CREATE INDEX composite_field_idx ON foo (composite_field);
INSERT INTO foo (int_field, varchar_field, composite_field) VALUES
(1,'t','t1'),
(2,'z','z2'),
(2,'w','w2'),
(4,'u','u4'),
(5,'u','u5'),
(5,'x','x5'),
(7,'v','v7');
explain select * from foo order by composite_field desc;
Run the above code and the explain statement should show the key composite_field_idx being referenced.
The result from the query is:
select * from foo order by composite_field desc;
id | int_field | varchar_field | composite_field
----+-----------+---------------+-----------------
2 | 2 | z | z2
6 | 5 | x | x5
3 | 2 | w | w2
7 | 7 | v | v7
5 | 5 | u | u5
4 | 4 | u | u4
1 | 1 | t | t1

Swapping column values in MySQL

I have a MySQL table with coordinates, the column names are X and Y. Now I want to swap the column values in this table, so that X becomes Y and Y becomes X. The most apparent solution would be renaming the columns, but I don't want to make structure changes since I don't necessarily have permissions to do that.
Is this possible to do with UPDATE in some way? UPDATE table SET X=Y, Y=X obviously won't do what I want.
Edit: Please note that my restriction on permissions, mentioned above, effectively prevents the use of ALTER TABLE or other commands that change the table/database structure. Renaming columns or adding new ones are unfortunately not options.
I just had to deal with the same and I'll summarize my findings.
The UPDATE table SET X=Y, Y=X approach obviously doesn't work, as it'll just set both values to Y.
Here's a method that uses a temporary variable. Thanks to Antony from the comments of http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ for the "IS NOT NULL" tweak. Without it, the query works unpredictably. See the table schema at the end of the post. This method doesn't swap the values if one of them is NULL. Use method #3 that doesn't have this limitation.
UPDATE swap_test SET x=y, y=#temp WHERE (#temp:=x) IS NOT NULL;
This method was offered by Dipin in, yet again, the comments of http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/. I think it’s the most elegant and clean solution. It works with both NULL and non-NULL values.
UPDATE swap_test SET x=(#temp:=x), x = y, y = #temp;
Another approach I came up with that seems to work:
UPDATE swap_test s1, swap_test s2 SET s1.x=s1.y, s1.y=s2.x WHERE s1.id=s2.id;
Essentially, the 1st table is the one getting updated and the 2nd one is used to pull the old data from.
Note that this approach requires a primary key to be present.
This is my test schema:
CREATE TABLE `swap_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`x` varchar(255) DEFAULT NULL,
`y` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `swap_test` VALUES ('1', 'a', '10');
INSERT INTO `swap_test` VALUES ('2', NULL, '20');
INSERT INTO `swap_test` VALUES ('3', 'c', NULL);
You could take the sum and subtract the opposing value using X and Y
UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;
Here is a sample test (and it works with negative numbers)
mysql> use test
Database changed
mysql> drop table if exists swaptest;
Query OK, 0 rows affected (0.03 sec)
mysql> create table swaptest (X int,Y int);
Query OK, 0 rows affected (0.12 sec)
mysql> INSERT INTO swaptest VALUES (1,2),(3,4),(-5,-8),(-13,27);
Query OK, 4 rows affected (0.08 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM swaptest;
+------+------+
| X | Y |
+------+------+
| 1 | 2 |
| 3 | 4 |
| -5 | -8 |
| -13 | 27 |
+------+------+
4 rows in set (0.00 sec)
mysql>
Here is the swap being performed
mysql> UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;
Query OK, 4 rows affected (0.07 sec)
Rows matched: 4 Changed: 4 Warnings: 0
mysql> SELECT * FROM swaptest;
+------+------+
| X | Y |
+------+------+
| 2 | 1 |
| 4 | 3 |
| -8 | -5 |
| 27 | -13 |
+------+------+
4 rows in set (0.00 sec)
mysql>
Give it a Try !!!
The following code works for all scenarios in my quick testing:
UPDATE swap_test
SET x=(#temp:=x), x = y, y = #temp
UPDATE table SET X=Y, Y=X will do precisely what you want (edit: in PostgreSQL, not MySQL, see below). The values are taken from the old row and assigned to a new copy of the same row, then the old row is replaced. You do not have to resort to using a temporary table, a temporary column, or other swap tricks.
#D4V360: I see. That is shocking and unexpected. I use PostgreSQL and my answer works correctly there (I tried it). See the PostgreSQL UPDATE docs (under Parameters, expression), where it mentions that expressions on the right hand side of SET clauses explicitly use the old values of columns. I see that the corresponding MySQL UPDATE docs contain the statement "Single-table UPDATE assignments are generally evaluated from left to right" which implies the behaviour you describe.
Good to know.
Ok, so just for fun, you could do this! (assuming you're swapping string values)
mysql> select * from swapper;
+------+------+
| foo | bar |
+------+------+
| 6 | 1 |
| 5 | 2 |
| 4 | 3 |
+------+------+
3 rows in set (0.00 sec)
mysql> update swapper set
-> foo = concat(foo, "###", bar),
-> bar = replace(foo, concat("###", bar), ""),
-> foo = replace(foo, concat(bar, "###"), "");
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from swapper;
+------+------+
| foo | bar |
+------+------+
| 1 | 6 |
| 2 | 5 |
| 3 | 4 |
+------+------+
3 rows in set (0.00 sec)
A nice bit of fun abusing the left-to-right evaluation process in MySQL.
Alternatively, just use XOR if they're numbers. You mentioned coordinates, so do you have lovely integer values, or complex strings?
Edit: The XOR stuff works like this by the way:
update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;
I believe have a intermediate exchange variable is the best practice in such way:
update z set c1 = #c := c1, c1 = c2, c2 = #c
First, it works always; second, it works regardless of data type.
Despite of Both
update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2
and
update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2
are working usually, only for number data type by the way, and it is your responsibility to prevent overflow, you can not use XOR between signed and unsigned, you also can not use sum for overflowing possibility.
And
update z set c1 = c2, c2 = #c where #c := c1
is not working
if c1 is 0 or NULL or zero length string or just spaces.
We need change it to
update z set c1 = c2, c2 = #c where if((#c := c1), true, true)
Here is the scripts:
mysql> create table z (c1 int, c2 int)
-> ;
Query OK, 0 rows affected (0.02 sec)
mysql> insert into z values(0, 1), (-1, 1), (pow(2, 31) - 1, pow(2, 31) - 2)
-> ;
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 0 | 1 |
| -1 | 1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.02 sec)
mysql> update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2;
ERROR 1264 (22003): Out of range value for column 'c1' at row 2
mysql> update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2;
ERROR 1264 (22003): Out of range value for column 'c1' at row 3
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 0 | 1 |
| 1 | -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.02 sec)
mysql> update z set c1 = c2, c2 = #c where #c := c1;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 0 | 1 |
| -1 | 1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.00 sec)
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 1 | 0 |
| 1 | -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.00 sec)
mysql> update z set c1 = #c := c1, c1 = c2, c2 = #c;
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 0 | 1 |
| -1 | 1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.00 sec)
mysql>update z set c1 = c2, c2 = #c where if((#c := c1), true, true);
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from z;
+------------+------------+
| c1 | c2 |
+------------+------------+
| 1 | 0 |
| 1 | -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.00 sec)
ALTER TABLE table ADD COLUMN tmp;
UPDATE table SET tmp = X;
UPDATE table SET X = Y;
UPDATE table SET Y = tmp;
ALTER TABLE table DROP COLUMN tmp;
Something like this?
Edit: About Greg's comment:
No, this doesn't work:
mysql> select * from test;
+------+------+
| x | y |
+------+------+
| 1 | 2 |
| 3 | 4 |
+------+------+
2 rows in set (0.00 sec)
mysql> update test set x=y, y=x;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from test;
+------+------+
| x | y |
+------+------+
| 2 | 2 |
| 4 | 4 |
+------+------+
2 rows in set (0.00 sec)
Two alternatives
1. Use a temporary table
2. Investigate
the XOR algorithm
As other answers point out, a simple swap won't work with MySQL because it caches the value of column 1 immediately before processing column 2, resulting in both columns being set to the value of column 2.
Given that the order of operations is not guaranteed in MySQL, using a temporary variable is also not reliable.
The only safe way to swap two columns without modifying the table structure is with an inner join, which requires a primary key (id in this case).
UPDATE mytable t1, mytable t2
SET t1.column1 = t1.column2,
t1.column2 = t2.column1
WHERE t1.id = t2.id;
This will work without any issues.
I've not tried it but
UPDATE tbl SET #temp=X, X=Y, Y=#temp
Might do it.
Mark
This surely works! I've just needed it to swap Euro and SKK price columns. :)
UPDATE tbl SET X=Y, Y=#temp where #temp:=X;
The above will not work (ERROR 1064 (42000): You have an error in your SQL syntax)
In SQL Server, you can use this query:
update swaptable
set col1 = t2.col2,
col2 = t2.col1
from swaptable t2
where id = t2.id
Assuming you have signed integers in your columns, you may need to use CAST(a ^ b AS SIGNED), since the result of the ^ operator is an unsigned 64-bit integer in MySQL.
In case it helps anyone, here's the method I used to swap the same column between two given rows:
SELECT BIT_XOR(foo) FROM table WHERE key = $1 OR key = $2
UPDATE table SET foo = CAST(foo ^ $3 AS SIGNED) WHERE key = $1 OR key = $2
where $1 and $2 are the keys of two rows and $3 is the result of the first query.
You could change column names, but this is more of a hack. But be cautious of any indexes that may be on these columns
Table name is customer.
fields are a and b, swap a value to b;.
UPDATE customer SET a=(#temp:=a), a = b, b = #temp
I checked this is working fine.
You can apply below query, It worked perfect for me.
Table name: studentname
only single column available: name
update studentnames
set names = case names
when "Tanu" then "dipan"
when "dipan" then "Tanu"
end;
or
update studentnames
set names = case names
when "Tanu" then "dipan"
else "Tanu"
end;
Swapping of column values using single query
UPDATE my_table SET a=#tmp:=a, a=b, b=#tmp;
cheers...!
I had to just move value from one column to the other (like archiving) and reset the value of the original column.
The below (reference of #3 from accepted answer above) worked for me.
Update MyTable set X= (#temp:= X), X = 0, Y = #temp WHERE ID= 999;
CREATE TABLE Names
(
F_NAME VARCHAR(22),
L_NAME VARCHAR(22)
);
INSERT INTO Names VALUES('Ashutosh', 'Singh'),('Anshuman','Singh'),('Manu', 'Singh');
UPDATE Names N1 , Names N2 SET N1.F_NAME = N2.L_NAME , N1.L_NAME = N2.F_NAME
WHERE N1.F_NAME = N2.F_NAME;
SELECT * FROM Names;
This example swaps start_date and end_date for records where the dates are the wrong way round (when performing ETL into a major rewrite, I found some start dates later than their end dates. Down, bad programmers!).
In situ, I'm using MEDIUMINTs for performance reasons (like Julian days, but having a 0 root of 1900-01-01), so I was OK doing a condition of WHERE mdu.start_date > mdu.end_date.
The PKs were on all 3 columns individually (for operational / indexing reasons).
UPDATE monitor_date mdu
INNER JOIN monitor_date mdc
ON mdu.register_id = mdc.register_id
AND mdu.start_date = mdc.start_date
AND mdu.end_date = mdc.end_date
SET mdu.start_date = mdu.end_date, mdu.end_date = mdc.start_date
WHERE mdu.start_date > mdu.end_date;
Let's say you want to swap the value of first and last name in tb_user.
The safest would be:
Copy tb_user. So you will have 2 tables: tb_user and tb_user_copy
Use UPDATE INNER JOIN query
UPDATE tb_user a
INNER JOIN tb_user_copy b
ON a.id = b.id
SET a.first_name = b.last_name, a.last_name = b.first_name
if you want to swap all the columns where x is to y and y to x; use this query.
UPDATE table_name SET column_name = CASE column_name WHERE 'value of col is x' THEN 'swap it to y' ELSE 'swap it to x' END;
Let's imagine this table and let's try to swap the m and f from the 'sex' table:
id
name
sex
salary
1
A
m
2500
2
B
f
1500
3
C
m
5500
4
D
f
500
UPDATE sex
SET sex = CASE sex
WHEN 'm' THEN 'f'
ELSE 'm'
END;
So the updated table becomes:
id
name
sex
salary
1
A
f
2500
2
B
m
1500
3
C
f
5500
4
D
m
500