Here's my mildly daft solution: add one varchar column (called e.g. username_string_part
) to your user table to store the string parts of the username, and a second int column (e.g. username_number_part
) to store the numeric part. So superman1 is split into "superman" in the username_string_part
column and "1" in username_number_part
. Also create an index, possibly over both columns or just over the username_string_part if you're not expecting large numbers of duplicate username_string_part entries. So, in MySQL, your create table is something like this):
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(25) NOT NULL default '',
`username_string_part` varchar(25) NOT NULL default '',
`username_number_part` int(11) NOT NULL default 0,
PRIMARY KEY (`id`),
KEY `ix_username_string_part` (`username_string_part`)
) TYPE=MyISAM AUTO_INCREMENT=1;
(Note that the username "superman" has a default username_number_part
of zero - this is important.)
Once you have a few entries, your data would look something like this:
+----+-----------+----------------------+----------------------+
| id | username | username_string_part | username_number_part |
+----+-----------+----------------------+----------------------+
| 1 | superman | superman | 0 |
| 2 | superman1 | superman | 1 |
| 3 | superman3 | superman | 3 |
+----+-----------+----------------------+----------------------+
Then it's a case of selecting minimum value of username_number_part
that doesn't have a username_number_part
value of "itself plus one" in the database. So for the username "superman":
select min(username_number_part) + 1 as min_number_available from users
where username_string_part = 'superman' and username_number_part not in
(select username_number_part - 1 from users where
username_string_part = 'superman');
The return value, min_number_available
, is NULL
if this is the first instance of that username - so they can have it - or an integer for the next free slot otherwise. You then build the recommended username as "superman" + min_number_available
. You could do the concat in the query or not as you like. With the example data above you'll get the value "2" returned.
Downsides: it's going to add storage (column and index), and slow down inserts very slightly. It also doesn't naturally distinguish between "superman001" and "superman01". (Although it could if you treated leading zeroes as part of the username_string_part
, so "superman001" would be split as "superman00" and "1".)
Upsides: it's a single query on indexed columns.
After all this, I'd be surprised if a site had so many username duplicates that doing a for loop with multiple database queries was really all that bad.