I have table like this:
===============
| rank | name |
===============
| 3 | john |
| 6 | bob |
| 10 | alex |
| 11 | brad |
| 12 | matt |
| 34 | luke |
| 145 | ben |
===============
(this table is an example. In reality my table consists of ~5000 rows of data).
Is there a query to reorder the rank values starting from 1 and going up so it ends up like this:
===============
| rank | name |
===============
| 1 | john |
| 2 | bob |
| 3 | alex |
| 4 | brad |
| 5 | matt |
| 6 | luke |
| 7 | ben |
===============
It would be preferable to do this in 1 or 2 queries, not 1 query for each row since my table has 5000+ rows.
EDIT: Sorry I wasn't clear. I am trying to UPDATE the values in the database.
This is a little crude but will work in a pinch.
First order your table correctly just incase
ALTER TABLE tablename ORDER BY rank
Then drop the column
ALTER TABLE tablename DROP rank
Then add it again, with auto increment
ALTER TABLE tablename ADD COLUMN rank INT NOT NULL AUTO_INCREMENT FIRST
The auto increment will take care of numbering them in order, plus you don't have to loop through each row.
Here is the solution I came up with for this problem:
1.Create a temporary table without any keys
CREATE TEMPORARY TABLE tempTable (
id INT(11) NOT NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=MyISAM
ROW_FORMAT=DEFAULT;
2.Populate the temporary table with data from the original table, ordered by rank
INSERT INTO tempTable SELECT id FROM myTable ORDER BY rank;
3.Add auto-incrementing rank column, giving all rows a unique rank, counting up from 1
ALTER TABLE tempTable
ADD COLUMN `rank` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
ADD PRIMARY KEY (`rank`);
4.Update the original table with a join to the temp table, overriding the original ranks
UPDATE myTable
INNER JOIN tempTable
ON myTable.id = tempTable.id
SET myTable.rank = tempTable.rank;
5.Drop the temp table
DROP TABLE tempTable;
An alternative to a strict MySQL solution would be to loop through the rows with a scripting language. Not a great idea if you have a large table, but could be acceptable if this is a one time fix.
In PHP
$db = mysql_connect('localhost', 'user', 'password');
mysql_select_db('database', $db);
$result = mysql_query("SELECT rank
FROM myTable
ORDER BY rank");
$i = 1;
while ($row = mysql_fetch_assoc($result)) {
mysql_query("UPDATE myTable
SET rank = " . $i++ . "
WHERE rank = " . $row['rank']);
}
Note that this will only work if rank is unique and you traverse in an order.
set #a:=(select max(id) from mytable)+1;
update mytable set id=(#a:=#a+1)
order by id;
set #a := 0;
update mytable set id=(#a:=#a+1)
order by id;
simple way, work for me. easy way.
Related
I have a table with the following structure
CREATE TABLE `data` (
`type` varchar(64) DEFAULT NULL,
`subscr_id` varchar(64) DEFAULT NULL
)
In this table, there are many records with subscr_id of id100. I would like to select a record with subscr_id of id100, that was added to the table most recently.
How can I do that?
You add an ID - Indentify column. It's best performance in this/your situation.
ALTER TABLE data ADD COLUMN id INT NULL AUTO_INCREMENT FIRST, ADD KEY(id);
Run the below SQL, you will receive the record with subscr_id of id100, that was added to the table most recently most recently.
SELECT * FROM `data` WHERE subscr_id = 'id100' ORDER BY id DESC LIMIT 1;
I think you have to improve your table design and add auto-inctemental primary key or created_at field.
But if you can't do it or you need run query just once, you can try this approach (it's a bit tricky but it works 😉).
In general recent record will be present at the end of the table. For example we have table like this:
+------+-----------+
| type | subscr_id |
+------+-----------+
| a | id100 |
| b | id100 |
| c | id100 |
| a | id200 |
| b | id200 |
| d | id100 |
| c | id200 |
| e | id100 |
+------+-----------+
And here wee need calculate total count of interesting rows and use it for offset, like this:
set #offset = (select count(*) from data where subscr_id = 'id100') - 1;
set #sql = concat(
"select * from data where subscr_id = 'id100' limit 1 offset ",
#offset
);
prepare stmt1 from #sql;
execute stmt1;
The result will look like this:
+------+-----------+
| type | subscr_id |
+------+-----------+
| e | id100 |
+------+-----------+
SELECT time
FROM posts
ORDER BY time ASC;
This will order my posts for me in a list. I would like to reorder the table itself making sure that there are no missing table ids. Thus, if I delete column 2, I can reorder so that row 3 will become row 2.
How can I do this? Reorder a table by its date column so there is always an increment of 1, no non-existing rows.
Disclaimer: I don't really know why you would need to do it, but if you do, here is just one of many ways, fairly independent of the engine or the server version.
Setup:
CREATE TABLE t (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` time DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO t (`time`) VALUES ('13:00:00'),('08:00:00'),('02:00:00');
DELETE FROM t WHERE id = 2;
Initial condition:
SELECT * FROM t ORDER BY `time`;
+----+----------+
| id | time |
+----+----------+
| 3 | 02:00:00 |
| 1 | 13:00:00 |
+----+----------+
2 rows in set (0.00 sec)
Action:
CREATE TRIGGER tr AFTER UPDATE ON t FOR EACH ROW SET #id:=#id+1;
ALTER TABLE t ADD COLUMN new_id INT NOT NULL AFTER id;
SET #id=1;
UPDATE t SET new_id=#id ORDER BY time;
DROP TRIGGER tr;
Result:
SELECT * FROM t ORDER BY `time`;
+----+--------+----------+
| id | new_id | time |
+----+--------+----------+
| 3 | 1 | 02:00:00 |
| 1 | 2 | 13:00:00 |
+----+--------+----------+
2 rows in set (0.00 sec)
Cleanup:
Further you can do whatever is more suitable for your case (whatever is faster and less blocking, depending on other conditions). You can update the existing id column and then drop the extra one:
UPDATE t SET id=new_id;
ALTER TABLE t DROP new_id;
SELECT * FROM t ORDER BY `time`;
+----+----------+
| id | time |
+----+----------+
| 1 | 02:00:00 |
| 2 | 13:00:00 |
+----+----------+
2 rows in set (0.00 sec)
Or you can drop the existing id column and promote new_id to the primary key.
Comments:
A natural variation of the same approach would be to wrap it into a stored procedure. It's basically the same, but requires a bit more text. The benefit of it is that you could keep the procedure for the next time you need it.
Assuming you have a unique index on id, a temporary column new_id is needed in a general case, because if you start updating id directly, you can get a unique key violation. It shouldn't happen if your id is already ordered properly, and you are only removing gaps.
My schema is this:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(10) NOT NULL,
`account_type` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1
INSERT INTO user VALUES (1, "zhangsan", "premiumv"), (2, "lisi", "premiumv"), (3, "wangwu", "p"), (4, "maliu", "p"), (5, "hengqi", "p"), (6, "shuba", "p");
I have the following 6 rows in the table:
+----+-----------+--------------+
| id | user_name | account_type |
+----+-----------+--------------+
| 1 | zhangsan | premiumv |
| 2 | lisi | premiumv |
| 3 | wangwu | p |
| 4 | maliu | p |
| 5 | hengqi | p |
| 6 | shuba | p |
+----+-----------+--------------+
Here is mysql to query the table by id:
SELECT * FROM user WHERE id = floor(rand()*6) + 1;
I expect it to return one row, but the actual result is non-predictive. It either will return 0 row, 1 row or sometimes more than one row. Can somebody help clarify this? Thanks!
You're testing each row against a different random number, so sometimes multiple rows will match. To fix this, calculate the random number once in a subquery.
SELECT u.*
FROM user AS u
JOIN (SELECT floor(rand()*6) + 1 AS r) AS r
ON u.id = r.r
This method of selecting a random row from a table seems like a poor design. If there are any gaps in the id sequence (which can happen easily -- MySQL doesn't guarantee that they'll always be sequential, and deleting rows will leave gaps) then it could return an empty result. The usual way to select a random row from a table is with:
SELECT *
FROM user
ORDER BY RAND()
LIMIT 1
The WHERE part must be evaluated for each row to see if there is a match. Because of this, the rand() function is evaluated for every row. Getting an inconsistent number of rows seems reasonable.
If you add LIMIT 1 to your query, the probability of returning rows from the end diminishes.
It's because the WHERE clause floor(rand()*6) + 1 is evaluated against every rows in the table to see if the condition matches the criteria. The value could be different each time it is matched against the row from the table.
You can test with a table that has same values in the column used in WHERE clause, and you can see the result:
select * from test;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 1 | c |
| 2 | d |
| 1 | e |
| 2 | f |
+------+------+
select * from test where id = floor(rand()*2) + 1;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | d |
| 1 | e |
+------+------+
In the above example, the expression floor(rand()*2) + 1 returns 1 when matching against the first row (with name = 'a') so it is included in the result set. But then it returns 2 when matching against the forth row (with name = 'd'), so it is also included in the result set even the value of id is different from the value of the first row in the result set.
I have a table that I added a column called phone - the table also has an id set as a primary key that auto_increments. How can I insert a random value into the phone column, that won't be duplicated. The following UPDATE statement did insert random values, but not all of them unique. Also, I'm not sold I cast the phone field correctly either, but ran into issues when trying to set it as a int(11) w/ the ALTER TABLE command (mainly, it ran correctly, but when adding a row with a new phone number, the inserted value was translated into a different number).
UPDATE Ballot SET phone = FLOOR(50000000 * RAND()) + 1;
Table spec's
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| phone | varchar(11) | NO | | NULL | |
| age | tinyint(3) | NO | | NULL | |
| test | tinyint(4) | NO | | 0 | |
| note | varchar(100) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
-- tbl_name: Table
-- column_name: Column
-- chars_str: String containing acceptable characters
-- n: Length of the random string
-- dummy_tbl: Not a parameter, leave as is!
UPDATE tbl_name SET column_name = (
SELECT GROUP_CONCAT(SUBSTRING(chars_str , 1+ FLOOR(RAND()*LENGTH(chars_str)) ,1) SEPARATOR '')
FROM (SELECT 1 /* UNION SELECT 2 ... UNION SELECT n */) AS dummy_tbl
);
-- Example
UPDATE tickets SET code = (
SELECT GROUP_CONCAT(SUBSTRING('123abcABC-_$#' , 1+ FLOOR(RAND()*LENGTH('123abcABC-_$#')) ,1) SEPARATOR '')
FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS dummy_tbl
);
Try this
UPDATE Ballot SET phone = FLOOR(50000000 * RAND()) * id;
I'd tackle this by generating a (temporary) table containing the numbers in the range you need, then looping through each record in the table you wish to supply with random numbers. Pick a random element from the temp table, update the table with that, and remove it from the temp table. Not beautiful, nor fast.. but easy to develop and easy to test.
I am trying to delete a project from the projects table and all the images associated with that project in the images table.
Lets say p_id = 10
DELETE FROM projects, images WHERE projects.p_id = ? AND images.p_id = ?
What is wrong with this query?
DELETE projects, images
FROM projects, images
WHERE projects.p_id = ?
AND projects.p_id = images.p_id;
As Chacha102 noted, the problem of your query was the AND in the WHERE clause.
However, you may want to use the JOIN syntax for multi-table DELETEs, which I find easier to read:
DELETE projects, images
FROM projects
LEFT JOIN images ON images.p_id = projects.p_id
WHERE projects.p_id = 10;
DELETE FROM projects, images WHERE projects.p_id = ? or images.p_id = ?
When being deleted, an item will never meet both of these requirements, therefore it must be OR not AND
The answer
DELETE FROM p, i
USING projects p, images i
WHERE p.p_id = ?
AND p.p_id = i.p_id
The test
projects
create table projects (
p_id int unsigned not null auto_increment primary key
);
insert into projects (p_id) values (1),(2),(3);
select * from projects;
-- +------+
-- | p_id |
-- +------+
-- | 1 |
-- | 2 |
-- | 3 |
-- +------+
images
create table images (
i_id int unsigned not null auto_increment primary key,
p_id int unsigned default null
);
insert into images (p_id) values (1),(1),(1),(2),(2),(3),(3);
select * from images;
-- +------+------+
-- | i_id | p_id |
-- +------+------+
-- | 1 | 1 |
-- | 2 | 1 |
-- | 3 | 1 |
-- | 4 | 2 |
-- | 5 | 2 |
-- | 6 | 3 |
-- | 7 | 3 |
-- +------+------+
the delete
delete from p, i
using projects p, images i
where p.p_id = i.p_id
and p.p_id = 1;
the result
select * from projects;
-- +------+
-- | p_id |
-- +------+
-- | 2 |
-- | 3 |
-- +------+
select * from images;
-- +------+------+
-- | i_id | p_id |
-- +------+------+
-- | 4 | 2 |
-- | 5 | 2 |
-- | 6 | 3 |
-- | 7 | 3 |
-- +------+------+
works a treat!
You should use two separate queries to do that :
delete from images where p_id = 123;
delete from projects where p_id = 123;
i.e. :
First, delete the images, that depend on the project (foreign key ? )
And, when nothing depends on the project anymore, delete the project itself.
And, as a security precaution, you should wrap all this in a transaction, to get a all or nothing behavior -- well, if you are using a storage engine that suppors transactions, like InnoDb.
See 12.3.1. START TRANSACTION, COMMIT, and ROLLBACK Syntax, about that, in the MySQL Manual.
Change the AND into an OR.
You might want to use a foreign key constraint with a cascading delete, much easier, but you have to use innoDB and create this FK-constraint. Delete the project and all related images will be deleted as well.
(Wrong answer, MySQL allows this)
You can't delete from two tables in one query.
The closest you can get is wrap the two deletes in a transaction:
begin transaction
delete from projects where p_id = ?
delete from images where p_id = ?
commit transaction