This question already has answers here:
How to handle fragmentation of auto_increment ID column in MySQL
(5 answers)
how to reindex mysql table
(7 answers)
Closed 9 years ago.
I currently have a database which looks a lot like the one below:
1 citrus
2 pear
4 apple
5 melon
8 mango
The numbers represent a column that hold the row numbers but because rows are often deleted they get messed up quite often. With what MySQL query in PHP could I recount these rows so they would make sense again?
You should never re-arrange ID's in a relational database. At least not if they are to be used as a foreign key. (Which I bet they would be, otherwise what's the sense of that ID?)
Your fruits table: 1=>citrus, 2=>pear, 4=>apple, 5=>melon, 8=>mango
Consider having another table, colors holding 1=>red, 2=>yellow, 3=>green.
And now consider having a table fruit_color holding 1=>2 , 2=>3, 4=>3, 5=>2, 8=>1.
Now what would happen if I were to just go rearrange your fruits table?... The relations would get messed up.
The ID associated with each row is a primary key, and is generally auto_increment'd on each insert. This id is used as the unique identification for each row, so that a query can be used to select it, and it alone.
As you delete data, the rows will remain lined up in order from lowest id to highest id, the highest id being the last row inserted.
It is normal for the database to have gaps, but you can also manually assign the id, granted you know that it does not already exist in the database.
If you want to keep your data in a specific order, you could assign an index to each object, representing its rank, and using an order by command in the query.
SELECT id, fruit, rank FROM fruits ORDER BY rank ASC;
What you are trying to do is going to be (A) complicated code, and (B) a nightmare to update, which means that (C) odds are good it is going to screw up your database. As nl-x suggests, your best bet is to leave your PRIMARY KEYs as is. I would recommend, however, if you want to have a set ID, to assign it when you pull it with PHP.
function get_fruits() {
//SQL query
$query = ...
//SQL result
$i = 0;
while($row = $query->fetch_assoc()) {
$rows[$i] = $row;
}
return $rows;
}
Now when you cycle through your rows you'll be able to treat each array key as the rank, and the end user will be none the wiser, all while keeping your database's integrity up to snuff.
** Pulling Data by Order**
If for whatever reason you needed to be able to pull by the record number rather than the ID, you can also get the 5th record from the following table with the following query.
ID | fruit_name
----------------
1 | Apple
2 | Banana
4 | Kiwi
7 | Coconut
9 | Strawberry
The Query in PHP:
$sql = "SELECT fruit_name FROM fruit_table LIMIT $i,1"
That will pull the $i+1 record. Meaning if you want to get the first record, $i=0. This is the way most for loops are executed, and MySQL auto increments by default will start with 1.
Related
In my current application I am making a menu structure that can recursively create sub menu's with itself. However due to this I am finding it difficult to also allow some sort of reordering method. Most applications might just order by an "ordering" column, however in this case although doing that does not seem impossible, just a little harder.
What I want to do is use the ID column. So if I update id 10 to be id 1 then id 1 that was there previously becomes 2.
What I was thinking at a suggestion from a friend was to use cascades. However doing a little more research that does not seem to work as I was thinking it might.
So my question is, is there an ability to do this naively in MySQL? If so what way might I do that? And if not what would you suggest to come to the end result?
Columns:
id title alias icon parent
parents have a lower id then their children, to make sure the script creates the array to put the children inside. That part works, however If I want to use an ordering column I will have to make a numbering system that would ensure a child element is never higher then its parent in the results. Possible, but if I update a parent then I must uniquely update all its children as well, resulting in more MySQL queries that I would want.
I am no MySQL expert so this is why I brought up this question, I feel there might be a perfect solution to this that can allow the least overhead when it comes to the speed of the application.
Doing it on the ID column would be tough because you can't ever have 2 rows with the same ID so you can't set row 10 to row 1 until after you've set row 1 to row 2 but you can't set row 1 to row 2 until you set row 2 to row 3, etc. You'd have to delete row 10 and then do an update ID += 1 WHERE ID < 10... but you'd also have to tell MySQL to start from the highest number and go down....
You'd have to do it in separate queries like this:
Move ID 10 to ID 2
DELETE FROM table WHERE id = 10;
UPDATE table SET id = id + 1 WHERE id >= 2 AND id < 10 ORDER BY id DESC
INSERT INTO table (id, ...) VALUES (2, ...);
Another option, if you don't want to delete and reinsert would be to set the id for row 10 to be MAX(id) + 1 and then set it to 1 after
Also if you want to move row 2 to row 10 you'd have to subtract the id:
Move ID 2 to ID 10
DELETE FROM table WHERE id = 2;
UPDATE table SET id = id - 1 WHERE id > 2 AND id <= 10 ORDER BY id DESC
INSERT INTO table (id, ...) VALUES (10, ...);
If you don't have your ID column set as UNSIGNED you could make all the IDs you want to switch to negative ids since AUTO_INCREMENT doesn't do negative numbers. Still this is pretty hacky and I wouldn't recommend it. You also probably need to lock the table so no other writes happen while this is running.:
Move ID 2 to ID 10
UPDATE table SET id = id * -1 WHERE id > 2 AND id <= 10;
UPDATE table SET id = 10 WHERE id = 2;
UPDATE table SET id = id * -1 - 1 WHERE id < -2 AND id >= -10;
I am quite new to PHP and MySQL, but have experience of VBA and C++. In short, I am trying to count the occurrences of a value (text string), which can appear in 11 columns in my table.
I think I will need to populate a single-dimensional array from this table, but the table has 14 columns (named 'player1' to 'player14'). I want each of these 'players' to be entered into the one-dimensional array (if not NULL), before proceeding to the next row.
I know there is the SELECT DISTINCT statement in MySQL, but can I use this to count distinct occurrences across 14 columns?
For background, I am building a football results database, where player1 to player14 are the starting 11 (and 3 subs), and my PHP code will count the number of times a player has made an appearance.
Thanks for all your help!
Matt.
Rethink your database schema. Try this:
Table players:
player_id
name
Table games:
game_id
Table appearances:
appearance_id
player_id
game_id
This reduces the amount of duplicate data. Read up on normalization. It allows you to do a simple select count(*) from appearances inner join players on player_id where name='Joe Schmoe'
First of all, the database schema you're using is terrible, and you just found out a reason why.
That being said, I see no other way then to first get a list of all players by distinctly selecting the names of players into an array. Before each insertion, you would have to check if the name is already in the array (if it is already in, don't add it again).
Then, when you have the list of names, you would have to run an SQL statement for each player, adding up the number of occurences, like so:
SELECT COUNT(*)
FROM <Table>
WHERE player1=? OR player2=? OR player3=? OR ... OR player14 = ?
That is all pretty complicated, and as I said, you should really change your database schema.
This sounds like a job for fetch_assoc (http://php.net/manual/de/mysqli-result.fetch-assoc.php).
If you use mysqli, you would get each row as an associative array.
On the other hand the table design seems a bit flawed, as suggested before.
If you had on table team with team name and what not and one table player with player names.
TEAM
| id | name | founded | foo |
PLAYER
| id | team_id | name | bar |
With that structure you could add 14 players, which point at the same team and by joining the two tables, extract the players that match your search.
For example, I have a table which looks like this :
id | name
1 | Mike
2 | Adam
3 | John
4 | Sarah
...
Now, when I execute query select * from table order by id desc it will output something like this:
4 | Sarah
3 | John
2 | Adam
1 | Mike
Now what do I do if I want to move John's row up or down, or move Adam's row up or down ( with a MySQL query ( I need basic one, just to know from where to start )).
My solution :
First of all, I created another column named orderID which has the same value as id.
Here is an example which moves up a user:
$query = "
SELECT (
SELECT orderID
FROM test WHERE id = 'user id that i want to move up'
) AS user_order,
(
SELECT orderID
FROM test WHERE orderID > user_order
ORDER BY orderID
LIMIT 0,1
) AS nextUser_order
";
$result = mysql_query($query);
$data = mysql_fetch_assoc($result);
$query = "
UPDATE test SET orderID = IF(orderID='{$data[nextUser_order]}',
'{$data[user_order]}', '{$data[nextUser_order]}')
WHERE orderID IN ('{$data[nextUser_order]}', '{$data[user_order]}');
";
$result = mysql_query($query);
Is there a better way to do that?
You have to switch IDs, or to order it by another column. That's the only way.
Changing the id is not what you want to do. You never want to mess with your primary key especially because later down the road it would be easier (and take up much less space, one is an int the other a varchar) to reference your users using their id rather than their name from other tables, it is nice to have a field that you know will never change.
Make another field such as order as a floating point number.
When you move foo between bar and foobar, set foo's order to the average of bar and foobar's order.
You can put arbitrary values into an order by clause in a query, but none will work easily for a simple "move up/down a row" type things. You can force certain values to sort first or last, but not "put this value after that value, but let that value go into its natural place". You'd need to have an extra field to specify sorting order.
SQL tables aren't inherently ordered - they effectively behave like a "bag of rows". If you want the results in a specific order, you will need to sort them (using ORDER BY ...) when you pull them out of the bag -- otherwise, the SQL server will return them in whatever order it feels is easiest. (In this case, they're coming out in the reverse order you inserted them, but that's not guaranteed at all.)
You should def be using another column which holds the order of the display. id is just a unique identifier. On a relational database moving up and down rows might result in a lot of queries because of the updates on the related tables so I stick with the idea of defining a special row for this purpose.
How can I number my results where the lowest ID is #1 and the highest ID is the #numberOfResults
Example: If I have a table with only 3 rows in it. whose ID's are 24, 87, 112 it would pull like this:
ID 24 87 112
Num 1 2 3
The reason why I want this, is my manager wants items to be numbered like item1, item2, etc. I initially made it so it used the ID but he saw them like item24, item87, item112. He didn't like that at all and wants them to be like item1, item2, item3. I personally think this is going to lead to problems because if you are deleting and adding items, then item2 will not always refer to the same thing and may cause confusion for the users. So if anyone has a better idea I would like to hear it.
Thanks.
I agree with the comments about not using a numbering scheme like this if the numbers are going to be used for anything other than a simple ordered display of items with numbers. If the numbers are actually going to be tied to something, then this is a really bad idea!
Use a variable, and increment it in the SELECT statement:
SELECT
id,
(#row:=#row+1) AS row
FROM table,
(SELECT #row:=0) AS row_count;
Example:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
INSERT INTO table1 VALUES (24), (87), (112);
SELECT
id,
(#row:=#row+1) AS row
FROM table1,
(SELECT #row:=0) AS row_count;
+-----+------+
| id | row |
+-----+------+
| 24 | 1 |
| 87 | 2 |
| 112 | 3 |
+-----+------+
How it works
#row is a user defined variable. It is necessary to set it to zero before the main SELECT statement runs. This can be done like this:
SELECT #row:=0;
or like this:
SET #row:=0
But it is handy to tie the two statements together. This can be done by creating a derived table, which is what happens here:
FROM table,
(SELECT #row:=0) AS row_count;
The the second SELECT actually gets run first. Once that's done, it's just a case of incrementing the value of #row for every row retrieved:
#row:=#row+1
The #row value is incremented every time a row is retrieved. It will always generate a sequential list of numbers, no matter what order the rows are accessed. So it's handy for some things, and dangerous for other things...
Sounds like it would be better just making that number in your code instead of trying to come up with some sort of convoluted way of doing it using SQL. When looping through your elements, just maintain the sequentiality there.
What is the ID being used for?
If it's only for quick and easy reference then that's fine, but if it's to be used for deleting or managing in any way as you mentioned then your only option would be to assign a new ID column that is unique for each row in the table. Doing this is pointless though because that duplicates the purpose of your initial ID column.
My company had a similar challenge on a CMS system that used an order field to sort the articles on the front page of the site. The users wanted a "promote, demote" icon that they could click that would move an article up or down.
Again, not ideal, but the strategy we used was to build a promote function and accompanying demote function that identified the current sort value via query, added or subtracted one from the previous or next value, respectively, then set the value of the initially promoted/demoted item. It was also vital to engineer the record insert to accurately set the initial value of newly added records so inserts wouldn't cause a duplicate value to be added. This was also enforced at the DB level for safety's sake. The user was never allowed to directly key in the value of the sort, only promote or demote via icons. To be honest, it worked quite well for the user.
If you have to go this route.....it's not impossible. But there is brain damage involved....
Let's say that I've got a table, like that (id is auto-increment):
id | col1 | col2
1 | 'msg'| 'msg'
2 | 'lol'| 'lol2'
3 | 'xxx'| 'x'
Now, I want to delete row number 2 and I get something like this
id | col1 | col2
1 | 'msg'| 'msg'
3 | 'xxx'| 'x'
The thing is, what I want to get is that:
id | col1 | col2
1 | 'msg'| 'msg'
2 | 'xxx'| 'x'
How can I do that in the EASIEST way (my knowledge about MySQL is very poor)?
You shouldn't do that.
Do not take an auto-incremented unique identifier as an ordinal number.
The word "unique" means that the identifier should be stuck to its row forever.
There is no connection between these numbers and enumerating.
Imagine you want to select records in alphabetical order. Where would your precious numbers go?
A database is not like an ordered list, as you probably think. It is not a flat file with rows stored in a predefined order. It has totally different ideology. Rows in the database do not have any order. And will be ordered only at select time, if it was explicitly set by ORDER BY clause.
Also, a database is supposed to do a search for you. So you can tell that with filtered rows or different ordering this auto-increment number will have absolutely nothing to do with the real rows positions.
If you want to enumerate the output - it's a presentation layer's job. Just add a counter on the PHP side.
And again: these numbers supposed to identify a certain record. If you change this number, you'd never find your record again.
Take this very site for example. Stack Overflow identifies its questions with such a number:
stackoverflow.com/questions/3132439/mysql-auto-decrementing-value
So, imagine you saved this page address to a bookmark. Now Jeff comes along and renumbers the whole database. You press your bookmark and land on the different question. Whole site would become a terrible mess.
Remember: Renumbering unique identifiers is evil!
I think there is no way to this directly. Maybe you can do "update" operation. But you must do it for all record after your deleted record. It is very bad solution for this.
Why using an auto-increment if you want to change it manually?
It is not good practice to change the value of an auto_increment column. However, if you are sure you want to, the following should help.
If you are only deleting a single record at a time, you could use a transaction:
START TRANSACTION;
DELETE FROM table1 WHERE id = 2;
UPDATE table1 SET id = id - 1 WHERE id > 2;
COMMIT;
However if you delete multiple records, you will have to drop the column and re-add it. It is probably not guaranteed to put the rows in the same order as previously.
ALTER TABLE table1 DROP id;
ALTER TABLE table1 ADD id INTEGER NOT NULL AUTO_INCREMENT;
Also, if you have data that relies on these IDs, you will need to make sure it is updated.
You can renumber the whole table like this:
SET #r := 0;
UPDATE mytable
SET id = (#r := #r + 1)
ORDER BY
id;