Update of primary key would cause duplicate entries in foreign table - mysql

I have two tables described by the following SQL Fiddle. My application needs to insert new records in tblA in between two already existing records. For example, if tblA has 6 records with AID ranging from 0 to 5 and I want to insert a new record with AID being 4, I increment the AID of tuple 4 and tuple 5 by one and then insert the new record. Thus, I use the following prepared statement to increment the value of the column AID of the tuples of both tblA and tblB (via cascading) by one:
update tblA set AID = (AID + 1) where AID >= ? order by AID desc;
On my test Installation the above Statement works great. However, on our production system we get the following error message in some, but not all cases:
Foreign key constraint for table 'tblA', record '4' would lead to a duplicate entry in table 'tblB'
Now, it is unclear to me what exactly causes the problem and how to solve the issue.
I appreciate any tips. Thanks in advance!

About tblB
This
create table if not exists tblB(
BID integer not null,
AID integer not null,
constraint fkB_A foreign key(AID) references tblA(AID),
primary key(AID, BID)
);
should probably be
create table if not exists tblB(
BID integer not null,
AID integer not null,
constraint fkB_A foreign key(AID) references tblA(AID)
on update cascade,
-- ^^^^^^^^^^^^^^^^
primary key(AID, BID)
);
Surrogate ID numbers in the relational model of data and in SQL databases are meaningless. Unless you know more than you've included in your question, AID and BID are meaningless. In a properly designed database, there's never a need to insert a row between two other rows based solely on their surrogate ID numbers.
If your real-world requirement is simply to insert a timestamp between "2015-12-01 23:07:00" and "2015-12-04 14:58:00", you don't need the ID number 4 to do that.
-- Use single quotes around timestamps.
insert into tblA values (-42, '2015-12-03 00:00:00');
select * from tblA order by RecordDate;
AID RecordDate
--
0 2015-11-07 16:55:00
1 2015-11-08 22:16:00
2 2015-11-10 14:26:00
3 2015-12-01 23:07:00
-42 2015-12-03 00:00:00
5 2015-12-04 14:58:00
6 2015-12-13 10:07:00
About tblA
This
create table if not exists tblA(
AID integer not null,
RecordDate varchar(25),
constraint pkA primary key(AID)
);
should probably be
create table if not exists tblA(
AID integer not null,
RecordDate varchar(25) not null,
-- ^^^^^^^^
constraint pkA primary key(AID)
);
Without that not null, you can insert data like this.
AID RecordDate
--
17 Null
18 Null
19 Null
Since surrogate ID numbers are meaningless, these rows are all essentially both identical and identically useless.
About the update statement
update tblA
set AID = (AID + 1)
where AID >= 4
order by AID desc;
Standard SQL doesn't permit order by in this position in update statement. MySQL documents this as
If the ORDER BY clause is specified, the rows are updated in the order
that is specified.
The relational model and SQL are set-oriented. Updates are supposed to happen "all at once". IMHO, you'd be better off learning standard SQL and using a dbms that better supports standard SQL. (PostgreSQL springs to mind.) But adding on update cascade to tblB (above) will let your update statement succeed in MySQL.
update tblA
set AID = (AID + 1)
where AID >= 4 order by AID desc;

adding on update cascade might solve your problem
create table if not exists tblB(
BID integer not null,
AID integer not null,
constraint fkB_A foreign key(AID)
references tblA(AID)
on update cascade,
primary key(AID, BID));

Related

Avoid duplicate data in mySQL table

I have the following table in my DDBB:
CREATE TABLE subjects (
subject_id int(11) NOT NULL AUTO_INCREMENT,
subject text,
PRIMARY KEY (subject_id, subject)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;
This is an example of my table:
id | subject |
1 test
2 ICT
3 ICT
The key (id) is not duplicate since it is automatically generated by MySQL, but the las two rows are repeating.
How can I avoid repeating the subject name?
I have read that it can be done with a 'constraint' like this:
ALTER TABLE subjects
ADD CONSTRAINT constraint_subject UNIQUE KEY(subject);
But I've tried it and I get an error every time.
I know that this has been asked before but I'm still entering from my PHP, some subjects with the same name and the program always lets me enter them.
Adding the constraint gives you an error because it wouldn't be satisfied since you already have duplicated data.
You have to delete the duplicates and then add the constraint, which would then work.
If you want to select only distinct rows right now, even when having duplicates in your database, you can run the following:
SELECT
*
FROM
subjects AS s1
WHERE
NOT EXISTS (
SELECT
id
FROM
subjects AS s2
WHERE
s1.subject = s2.subject
AND s1.id != s2.id
);
Or
SELECT
s1.*
FROM
subjects AS s1
LEFT JOIN
subjects AS s2
ON (s1.subject = s2.subject AND s1.id != s2.id)
WHERE
s2.id IS NULL
Both will give the same result but I find the first one to be more explicit about what you're trying to achieve.
You can't create an index on a column of data type TEXT because that's too long for an index.
You can create an index, even a unique index, on a prefix of that column.
ALTER TABLE subjects
ADD CONSTRAINT constraint_subject UNIQUE KEY(subject(191));
This means two subjects cannot have exactly the same leading 191 characters.
I don't think you should declare the PRIMARY KEY including the subject. It's more typical to use the auto-increment integer column alone as the primary key.
So your table ends up with this definition:
CREATE TABLE `subjects` (
`subject_id` int(11) NOT NULL AUTO_INCREMENT,
`subject` text,
PRIMARY KEY (`subject_id`),
UNIQUE KEY `constraint_subject` (`subject`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
I chose the length 191 because it's the longest I could fit in the 767 byte limit that InnoDB has on indexes (utf8mb4 characters count as 4 bytes).

mySql trigger not working as intended

I have 2 tables set up with a trigger, such that an insertion upon one table, will trigger an increment in the column of the second table at the same primary key (as both tables a linked by a foreign key), however at the moment the increment only once, then it does not increment for any subsequent insertions. My thinking is that it's probably with the way I set up the keys but I'm unsure, could someone please shed some light on this:
CREATE TABLE Members(
ID INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
FIRST_NAME TEXT(16),
LAST_NAME TEXT(16),
TITLE TEXT(7), /** 7 CHARS for 'Student'*/
INSTITUTION VARCHAR(2048),
No_Publications INT NOT NULL,
PRIMARY KEY(ID)
);
CREATE TABLE Papers(
ISBN INT UNSIGNED NOT NULL UNIQUE,
Title TEXT(4),
Publish_Date DATE NOT NULL,
Topic TEXT(128),
PRIMARY KEY(ISBN)
);
CREATE TABLE Publications(
Author_ID INT UNSIGNED NOT NULL UNIQUE,
ISBN INT UNSIGNED NOT NULL UNIQUE,
PRIMARY KEY (Author_ID, ISBN),
FOREIGN KEY(Author_ID) REFERENCES Members(ID) ON DELETE RESTRICT ON UPDATE CASCADE,
FOREIGN KEY(ISBN) REFERENCES Papers(ISBN) ON DELETE RESTRICT ON UPDATE CASCADE /**Used in
many to many relations*/
);
CREATE TRIGGER New_Publication AFTER INSERT ON Publications
FOR EACH ROW
UPDATE Members SET No_Publications = No_Publications + 1
WHERE Members.ID = Publications.Author_ID;
EDIT: I want the No_Publications to increase with insertion upon the publications table
EDIT 2: After implementing the NEW keyword the auto-incrementation now works however now the following occurs:
Upon adding 1 new paper to 1 new member, the paper is registered in the database, however, subsequent additions of papers to the same member are not registered, despite and increment occuring in the members table.
Insertion into the Publications table occurs as a result of the papers table which has been appended above for transparency.
The trigger should be:
CREATE TRIGGER New_Publication AFTER INSERT ON Publications
FOR EACH ROW
UPDATE Members SET No_Publications = No_Publications + 1
WHERE Members.ID = NEW.Author_ID;
Even though his column should be updated also on deletes and having this column goes agains database normalization as you can get this information without having the column so it can be only useful in certain cases to improve performance and avoid counts.

MYSQL table for related records in another table

What is the best way to have a table to maintain related records of another table.
Example:
mytbl
-----
id sku
1 sk1
2 sk2
3 sk3
4 sk4
5 sk5
6 sk6
7 sk7
Lets say records 1, 4 and 3 are 'related'
So I want to maintain a table that tells me that they are.
relatedTbl
----------
sku related_sku
sk1 sk3
sk1 sk4
sk3 sk4
This solution would work but, is there a better solution?
EDIT: I used skus in the relatedTbl but I know I could (better) to use ids. The question is about the structure of the table more than what foreign key to use.
You have the correct solution. As you indicated, use the ID. If sku is unique, consider using it as a natural PK.
CREATE TABLE IF NOT EXISTS `mytbl` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`sku` VARCHAR(45) NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `relatedTbl` (
`mytbl_id` INT UNSIGNED NOT NULL,
`mytbl_id1` INT UNSIGNED NOT NULL,
PRIMARY KEY (`mytbl_id`, `mytbl_id1`),
INDEX `fk_mytbl_has_mytbl_mytbl1_idx` (`mytbl_id1` ASC),
INDEX `fk_mytbl_has_mytbl_mytbl_idx` (`mytbl_id` ASC),
CONSTRAINT `fk_mytbl_has_mytbl_mytbl`
FOREIGN KEY (`mytbl_id`)
REFERENCES `mytbl` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_mytbl_has_mytbl_mytbl1`
FOREIGN KEY (`mytbl_id1`)
REFERENCES `mytbl` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
You may want to consider adding a third field to 'mytbl' in to store a unique key for common records. for instance, field 3 would be named "uniqID", and records 1, 4 and 3 are 'related' the table would then be:
mytbl
id sku uniqID
1 sk1 1
2 sk2
3 sk3 1
4 sk4 1
5 sk5
6 sk6
7 sk7
you can then use a 'WHERE uniqID=1' clause at the end of your select statement to get the common attributes

MySql replace with multiple primary keys

I have a table which has three primary keys and references three other tables
Here is the table scheema:
CREATE TABLE IF NOT EXISTS training_matrix_reference(
employee INT NOT NULL,
training_matrix INT NOT NULL,
training_record INT UNSIGNED NOT NULL,
PRIMARY KEY (employee, training_matrix,training_record),
FOREIGN KEY (employee) REFERENCES employees(id),
FOREIGN KEY (training_matrix) REFERENCES training_matrix_courses(id),
FOREIGN KEY (training_record) REFERENCES training_records(m_id)
)
I'm trying to craft a REPLACE statement which updates the training_record column or training_matrix column or both columns or creates a new row if not exists, but I also need to check that the employee belongs to the same company.
Here's what I tried so far:
REPLACE INTO `training_matrix_reference`
( employee, training_matrix, training_record ) (
SELECT id, '5', '100'
FROM employees
WHERE id =22
AND company =64
)
So my theory was that this should have replaced the first row in the table, updating training_record to 100 but in fact it actually created a new row:
22 | 5 | 100
My guess is that this happened because training_record is a primary key?
But I'm not sure that removing the primary keys/references is the right way to go as this table is used as a many to many table in other queries.
Effectively what I'm trying to do is:
REPLACE INTO `training_matrix_reference`
( employee, training_matrix, training_record )
VALUES
(22,33,18)
WHERE
employee = 22
and training_matrix = 5
and training_record = 2189
But obviously a replace statement doesn't have a where clause.
I did check out these similar questions:
MySQL REPLACE INTO on multiple keys?
mysql REPLACE query with multiple primary keys
But unfortunately MySql is not my strong suit and I could really use some help.
I hope I explained things clearly, Thanks
The PRIMARY KEY of the training_matrix_reference table is the combination of three columns. The table doesn't have multiple primary keys, it has a single PRIMARY KEY.
The REPLACE syntax you have is equivalent to performing:
DELETE FROM training_matrix_reference
WHERE employee = 22
AND training_matrix = 5
AND training_record = 100
;
INSERT INTO training_matrix_reference (employee, training_matrix, training_record)
VALUES (22, 5, 100);
;
The DELETE action only removes rows where the entire primary key is matched. Given the information you provided, we'd expect a row to be added to the table.
Did you have a question?
you should make a joining table between (employee, training_matrix_reference)
or dispense at lest one relation

Cannot insert duplicate key: MySQL to SQL server

Suppose we have the following chunk of data (SQL table):
Col-A Col-B Col-C Col-D
1 1 1 1
1 1 1 2
1 1 1 3
2 2 2 4
2 2 2 5
In MySQL the table is defined as:
CREATE TABLE `my_table` (
`Col-A` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`Col-B` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`Col-C` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`Col-D` INT(10) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`Col-A`, `Col-B`, `Col-C`),
KEY `my_index` (`Col-D`) USING BTREE
);
I need to convert MySQL database to SQL Server. Here is my initial attempt:
CREATE TABLE my_table (
Col-A INT NOT NULL DEFAULT(0),
Col-B INT NOT NULL DEFAULT(0),
Col-C INT NOT NULL DEFAULT(0),
Col-D INT NOT NULL DEFAULT(0),
CONSTRAINT my_pk PRIMARY KEY NONCLUSTERED (Col-A, Col-B, Col-C)
)
CREATE INDEX my_idx ON my_table(Col-D)
When I try to import data (I use bcp), the following error occurs:
Cannot insert duplicate key ... The duplicate key is (1, 1, 1)
I suspect that something is wrong with my_pk and my_idx definitions. Any pointers or suggestions?
You know the definition of a primary key?
http://en.wikipedia.org/wiki/Primary_key
In the relational model of database design, a unique key or primary key is a set of attributes whose values uniquely define the characteristics of each row.
When the combination of Col-A + Col-B + Col-C is not unique you violate the primary key constraint and thus SQL server won't allow it.
Your options are:
Extend the PK to include Col-D
Drop the PK and use a (clustered or not) Index on Col-A, Col-B and Col-C
Fix the data so it doesn't violate the PK constraint (either drop records or alter/correct incorrect records)
Add a synthetic (or surrogate) key (see mrjoltcola's answer)
Which option to choose is up to you and depends on your requirements. We can't answer that for you based only on the information in your question.
Why MySQL allowed this data to get in there in the first place... *shrugs* MySql is a "funny" beast. Maybe the PK constraint was added after the data was already in the table, maybe it's a really old version, maybe you're using MyISAM instead of InnoDB. I'm not sure which but each of these reasons (or combination of them) are a good guess or, at least, were decent guesses some time / versions ago. Either way: it shouldn't have been possible (even if the PK constraint was added later; MySQL should've denied adding it since the data in the table was conflicting) but MySQL had, and does have, it's own weird ways of reasoning about these kind of things. Strict mode helps if I recall correctly but I can't remember if that only works on InnoDB tables or also on MyISAM etc. Either way; they made a nice mess of it back in the day; I (or you) shouldn't have to worry about remembering the differences in underlying MyISAM/InnoDB/Whatevs etc. or which specific version allows what (not) to happen or if you need strict mode or not for this-or-that for basic stuff like PK's to work correctly*
* Each RDBMS has it's quirks; I'm sure there's a good reason for some switches/toggles/settings/whatevs to tweak some details, I'm saying PK's should be PK's no matter what.
For your data requirement, you cant use cols (A,B,C) as primary key. You need to either add (D) to the key, or add a surrogate key. See RobIII's answer https://stackoverflow.com/a/24703970/257090 for why.
I recommend you go with the latter, add an ID primary key so you have a single field key:
CREATE TABLE my_table (
ID INT IDENTITY PRIMARY KEY,
ColA INT NOT NULL DEFAULT(0),
ColB INT NOT NULL DEFAULT(0),
ColC INT NOT NULL DEFAULT(0),
ColD INT NOT NULL DEFAULT(0),
UNIQUE(ColA,ColB,ColC,ColD)
)
INSERT INTO my_table(cola, colb, colc, cold) VALUES(1,1,1,1)
INSERT INTO my_table(cola, colb, colc, cold) VALUES(1,1,1,2)
INSERT INTO my_table(cola, colb, colc, cold) VALUES(1,1,1,3)
INSERT INTO my_table(cola, colb, colc, cold) VALUES(2,2,2,4)
INSERT INTO my_table(cola, colb, colc, cold) VALUES(2,2,2,5)
SELECT * FROM my_table
ID ColA ColB ColC ColD
----------- ----------- ----------- ----------- -----------
1 1 1 1 1
2 1 1 1 2
3 1 1 1 3
4 2 2 2 4
5 2 2 2 5
(5 row(s) affected)
Now I can identify each row by a single key value.
delete from my_table where ID = 5
This is much more practical for any code you write against the database or ORMs you use.
NOTE: with surrogate (or synthetic keys) it is still important that you add any additional constraints to enforce data integrity of the actual data. A surrogate key doesn't keep you from inserting 1,1,1,1 multiple times, so add a unique constraint/index to those fields in addition to the primary key ID.