MySQL statement ON DUPLICATE KEY for not keys - mysql

I'm working on a game that requires the user (primarily kids) to combine a prefix and a suffix into a unique username, say, BlueBaron. Now there's only so many prefixes and suffixes, so if a user generates an existing one, a number is appended to it, say, BlueBaron2.
I have a table as follows:
| id | prefix_id | suffix_id | identifier_index | username | hashbrown | salt | coins | ranking | date_created | date_updated
The id is an auto-increment, unique, not-null primary key - I assume for this particular instance, I won't actually need to worry about the id. The prefix_id and suffix_id are not-null, but because they refer to common prefixes and suffixes, they are not unique. The rest of the rows are just not-nulls.
Ideally, I would like to check if a new user has the exact same prefix_id and suffix_id as another user, and increment the identifier_index.
I tried this with multiple (SELECT then INSERT) statements, but I fear the data might not be updated / unique (another user might have inserted between the time it took for you to insert, etc.).
Is this possible within a single insert statement? I've read of ON DUPLICATE KEY but I'm not sure that's applicable here.
UPDATE:
Per the comments and answers below, I've created a unique index for the three columns in question:
However, the identifier_index increments even when the prefix_id and suffix_id are different. And in the case of the last entry, wouldn't increment at all resulting in a duplicate entry error:

That's a good question. I'm no developer, but from a database admins view, I'd say that you need to do it like this.
You definitely need a unique index spanning over the 3 columns.
CREATE TABLE `a` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`prefix_id` varchar(10) NOT NULL,
`suffix_id` varchar(10) NOT NULL,
`identifier_index` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_psi` (`prefix_id`,`suffix_id`,`identifier_index`)
) ENGINE=InnoDB;
This is a must, you want to guarantee data integrity!
Your insert statement would look like this:
insert into a (prefix_id, suffix_id, identifier_index)
select 'asdf', 'qwer', coalesce(max(identifier_index) + 1, 1)
from a
where prefix_id = 'asdf' and suffix_id = 'qwer';
Be aware though, that you can run into deadlock issues. This happens when another transaction is trying to insert while this query is still running. Deadlocks are no serious issue, though. Typically an application is built in a way, that it simply tries again, until the insertion is successful.

Related

Need clarity of UNIQUE and DEFAULT Constraints in SQL

So I just started learning SQL online and while learning about constraints, below example was given for using DEFAULT constraint:
CREATE TABLE persons(
ID INT NULL DEFAULT 100,
f_name VARCHAR(25),
l_name VCARCHAR(25),
UNIQUE(ID)
);
My question is, if ID is defaulted to 100, there can be multiple columns having 100 as ID, so wouldn't that contradict UNIQUE constraint, which ensures all columns to have different values?
Thank you for reading and your inputs!
Rohan
Though it's valid SQL and mysql allows this, it is a bad practice to define DEFAULT value on an column with UNIQUE constraint. This poor schema will lead to inconsistency in your data.
mysql> show create table persons;
+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| persons | CREATE TABLE `persons` (
`id` int(11) DEFAULT '100',
`f_name` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
`l_name` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
You are right, the combination of DEFAULT 100 and UNIQUE makes no sense.
The column is defined as nullable, so there can be many rows with the value null. Only when a row has a value different from null, must it be unique.
In order to insert nulls, you'd explicitely have this in your INSERT statement. If you don't set null explicitly, the default 100 will be written. This works for the first row treated that way, but the second time the 100 will violate the unique constraint, just as you say.
Well, a nullable ID makes no sense either, and ideally an ID should be auto-incremented, so you don't have to worry about using an unused ID, especially in an environment where multiple processes may try to insert rows at the same time.
So, the given examle is just very bad.
The combination of DEFAULT 100 and UNIQUE makes sense.
This combination means that the newly inserted row should have explicitly specified ID column value primarily.
The scheme allows to insert one row without ID value specified. But only one row. If you need to insert another row with this default/generic ID value then you must edit existing row and alter its ID value previously (or delete it).
In practice - this allows to save raw, incomplete, row, and edit it completely in future. For example, you insert generic row, then calculate needed row parameters and set needed references, and finally you assign some definite ID value to this row. After this you may insert another generic row and work with it.
Of course this situation is rare. But it may be useful in some cases.

MySQL — ON DUPLICATE KEY UPDATE only when both unique fields match, else INSERT

I have the following query:
"INSERT INTO `occ_apps` (`occ_date`, `service_tag`, `counter`) VALUES (?, ?, '1') ON DUPLICATE KEY UPDATE `counter` = (`counter`+1)"
Currently it's incrementing the counter when either occ_date or service_tag is matching in a row.
Where occ_date and service_tag are unique fields, and I can't set primary key to both unique fields.
I ran the following:
ALTER TABLE occ_apps
DROP PRIMARY KEY,
ADD PRIMARY KEY (occ_date, service_tag);
And I get, the error:
`#1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key`
I want it to update (increment) the counter only when occ_date and service_tag both matches (already exists) in a single row, otherwise it should insert a new row.
Software version: 5.5.53-MariaDB-1~wheezy - mariadb.org binary distribution
when I ran DESC occ_apps I get:
Field Type Null Key Default Extra
serial_no int(255) NO PRI NULL auto_increment
occ_date varchar(255) NO UNI NULL
counter int(255) NO NULL
service_tag varchar(255) YES UNI NULL
I don't think you even need a counter field in your table. It looks like your counter is merely holding how many times a given value occurs. And that's something that can be generated easily using a GROUP BY
SELECT occ_date, COUNT(*) FROM occ_apps GROUP BY `occ_date`;
So you want to filter the query so that you get only items with at least 5 counts?
SELECT occ_date, COUNT(*) FROM occ_apps WHERE service_tag = 'service-1'
GROUP BY `occ_date` HAVING COUNT(*) > 5
These sorts of problems have been solved millions of times using GROUP BY. This is just the tip of the ice berge as far as what SQL query aggregation can do. Please take a moment to read up on it.

MySQL duplication

Using SQLyog, I was testing whether the correct value was set into table.
And I tried
SELECT type_service FROM service WHERE email='test#gmail.com'
So, only one result was output.
type_service
0
To continue to test, I tried to set value, 1 by force which gave the warning
Warning
There are 2 duplicates of the row you are trying to update. Do you
want to update all the duplicates?
Note: You can turn off this warning by unchecking Tools -> Preferences -> Others -> Prompt if multiple rows are getting updated.
But I thought I already placed limitations with where clause. So I pushed yes.
As a result, the value of all the data in type_service column was changed to 1.
Why?
You have 2 exact duplicate rows in table. Exact. It is a friendly warning, but most likely needs to be addressed by a slight schema change.
The most simple solution is to alter the table and add an auto_increment Primary Key column.
Mysql Alter Table Manual page here.
See this Webyog FAQ link.
Whenever I am about to spook up another table, I usually stub it out like:
create table blah
(
id int auto_increment primary key,
...
...
...
);
for safety sake.
Were you not to have the auto_increment PK, see the following.
create table people
(
firstName varchar(40) not null,
lastName varchar(40) not null,
age int not null
);
insert people (firstName,lastName,age) values ('Kim','Billings',30),('Kim','Billings',30),('Kim','Billings',30);
select * from people;
-- this could be bad:
update people
set age=40
where firstName='Kim' and lastName='Billings';
ALTER TABLE people ADD id INT PRIMARY KEY AUTO_INCREMENT;
select * from people; -- much nicer now, schema has an id column starting at 1
-- you now at least have a way to uniquely identify a row

MYSQL Long super-keys

I am currently working on a project, which involves altering data stored in a MYSQL database. Since the table that I am working on does not have a key, I add a key with the following command:
ALTER TABLE deCoupledData ADD COLUMN MY_KEY INT NOT NULL AUTO_INCREMENT KEY
Due to the fact that I want to group my records according to selected fields, I try to create an index for the table deCoupledData that consists of MY_KEY, along with the selected fields. For example, If I want to work with the fields STATED_F and NOT_STATED_F, I type:
ALTER TABLE deCoupledData ADD INDEX (MY_KEY, STATED_F, NOT_STATED_F)
The real issue is that the fields that I usually work with are more than 16, so MYSQL does not allow super-keys longer than 16 fields.
In conclusion, Is there another way to do this? Can I make (somehow) MYSQL to order the records according to the desired super-key (something like clustering)? I really need to make my script faster and the main overhead is that each group may contain records which are not stored on the same page of the disk, and I assume that my pc starts random I/Os in order to retrieve records.
Thank you for your time.
Nick Katsipoulakis
CREATE TABLE deCoupledData (
AA double NOT NULL DEFAULT '0',
STATED_F double DEFAULT NULL,
NOT_STATED_F double DEFAULT NULL,
MIN_VALUES varchar(128) NOT NULL DEFAULT '-1,-1',
MY_KEY int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (MY_KEY),
KEY AA (AA) )
ENGINE=InnoDB AUTO_INCREMENT=74358 DEFAULT CHARSET=latin1
Okay, first of all, when you add an index over multiple columns and you don't really use the first column, the index is useless.
Example: You have a query like
SELECT *
FROM deCoupledData
WHERE
stated_f = 5
AND not_stated_f = 10
and an index over (MY_KEY, STATED_F, NOT_STATED_F).
The index can only be used, if you have another AND my_key = 1 or something in the WHERE clause.
Imagine you want to look up every person in a telephone book with first name 'John'. Then the knowledge that the book is sorted by last name is useless, you still have to look up every single name.
Also, the primary key does not have to be a surrogate / artificial one. It's nearly always better to have a primary key which is made up of columns which identify each row uniquely anyway.
Also it's not always good to have many indexes. Not only do indexes slow down INSERTs and UPDATEs, sometimes they just cause an extra lookup, since first a look at the index is taken and a second look to find the actual data.
That's just a few tips. Maybe Jordan's hint is not a bad idea, "You should maybe post a new question that has your actual SQL query, table layout, and performance questions".
UPDATE:
Yes, that is possible. According to manual
If you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index.
which means that the data is practically sorted on disk, yes.
Be aware that it's also possible to define a primary key over multiple columns!
Like
CREATE TABLE deCoupledData (
AA double NOT NULL DEFAULT '0',
STATED_F double DEFAULT NULL,
NOT_STATED_F double DEFAULT NULL,
MIN_VALUES varchar(128) NOT NULL DEFAULT '-1,-1',
MY_KEY int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (NOT_STATED_F, STATED_F, AA),
KEY AA (AA) )
ENGINE=InnoDB AUTO_INCREMENT=74358 DEFAULT CHARSET=latin1
as long as the combination of the columns is unique.

Remove duplicates records in a MySQL table with key but not primary key

I need to remove duplicate records (just to keep one copy) from a MySQL table in MyISAM format. I have read many questions in Stackoverflow about similar issues but always the people use an ID field as unique id or similar and I haven't this field because my "Key" could be duplicated. I want to remove rows with the 'Fecha' and 'Equip' duplicated.
CREATE TABLE `pfc` (
`Fecha` datetime NOT NULL,
`Equip` int(10) unsigned NOT NULL,
`Value` double NOT NULL,
KEY `Fecha` (`Fecha`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AVG_ROW_LENGTH=21 ROW_FORMAT=FIXED;
An example table data:
Fecha | Equip | Value
06/02/2011 0:00:11 | 22 | 29.0
06/02/2011 0:22:11 | 22 | 45.3
06/02/2011 0:00:11 | 22 | 29.0
The result should be:
Fecha | Equip | Value
06/02/2011 0:00:11 | 22 | 29.0
06/02/2011 0:22:11 | 22 | 45.3
This structure is not possible to change. I cannot use PRIMARY KEY or UNIQUE INDEX solutions. To create a temporal table without duplicates and then rename would be a poor solutions also, because the KEY and another parameters will be lost.
Thanks
ALTER IGNORE TABLE `pfc` ADD UNIQUE(`Fetcha`, `Equip`);
That will keep the first record it finds and remove duplicates from your table.
From MySQL manual:
IGNORE is a MySQL extension to standard SQL. It controls how ALTER
TABLE works if there are duplicates on unique keys in the new table or
if warnings occur when strict mode is enabled. If IGNORE is not
specified, the copy is aborted and rolled back if duplicate-key errors
occur. If IGNORE is specified, only the first row is used of rows with
duplicates on a unique key. The other conflicting rows are deleted.
Incorrect values are truncated to the closest matching acceptable
value.
Edit:
To create an exact copy of your table, use the following:
CREATE TABLE table_copy SELECT * FROM pfc;
That will copy the table structure and the data. After that, run the ALTER command to add the UNIQUE constraint and filter out the records that are duplicated.
Just as a test, try this to see if it is what you want
CREATE TABLE new_pfc AS
SELECT * FROM pfc GROUP BY fecha, equip
DELETE n1 FROM pfc n1, pfc n2 WHERE n1.Fecha = n2.Fecha AND n1.Equip=n2.Equip AND n1.Value=n2.Value