Mysql/Maridb Update is not working - mysql

Suppose a table contains data like
MariaDB [c]> select * from t2;
+-----+
| abc |
+-----+
| 1 |
| 3 |
| 5 |
+-----+
Suppose my update command is
MariaDB [c]> update t2 set abc = abc+2;
It give following error
ERROR 1062 (23000): Duplicate entry '3' for key 'PRIMARY'
While the above command works fine in oracle 10g, Is it some kind of bug or what?

The following is just an illustration and trivial.
create table t2
( id int auto_increment primary key,
abc int not null,
unique key(abc)
);
insert t2(abc) values (1),(3),(5); -- 3 rows added
update t2 set abc = abc+2; -- Error Code 1062: Duplicate entry '3' for key 'abc'
The above error occurred because the update marched in the order of the primary key, also the physical ordering, and changing the 1 to a 3 violated the 3 that was already in place via the unique key. The fact that the end state would make every thing OK, ideally, doesn't keep it from failing at that moment.
To illustrate this working in this highly rigged example knowing there is no other data:
truncate table t2; -- the data wasn't modified but for the paranoid, clear and re-insert
insert t2(abc) values (1),(3),(5); -- 3 rows added
Try it bottom up (so that the Unique constraint is not violated):
update t2 set abc = abc+2 order by abc desc;
select * from t2;
+----+-----+
| id | abc |
+----+-----+
| 1 | 3 |
| 2 | 5 |
| 3 | 7 |
+----+-----+
It leverages the ability to have an order by in an update statement.
So it comes down to knowing your data and what you can get away with. Saying it worked on Oracle as you did in comments is on another db platform and with some other schema. So that is mute.

Related

Limit number of rows inserted in a MySQL DB table

I need to limit the number of rows inserted in a table of my DB.
I 'd like to implement a logic to check if the limit is reached when a new insert is executed: if the max number of records is reached I will delete the oldest record in the table.
I tried to implement this with a trigger as suggested here, but I'm getting an error:
ERROR 1442: 1442: Can't update table 'tableName' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
So how can I implement this?
NOTE I'm using MySQL v5.7.25
Elaborating on my comment, why not just fill up the table, and then only execute updates?
E.g.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,val CHAR(1)
,ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO my_table (val) VALUES ('a');
INSERT INTO my_table (val) VALUES ('b');
INSERT INTO my_table (val) VALUES ('c');
SELECT * FROM my_table;
+----+------+---------------------+
| id | val | ts |
+----+------+---------------------+
| 1 | a | 2019-05-20 09:54:09 |
| 2 | b | 2019-05-20 09:54:15 |
| 3 | c | 2019-05-20 09:54:19 |
+----+------+---------------------+
3 rows in set (0.00 sec)
UPDATE my_table SET val = 'd' ORDER BY ts, id LIMIT 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
SELECT * FROM my_table;
+----+------+---------------------+
| id | val | ts |
+----+------+---------------------+
| 1 | d | 2019-05-20 09:54:37 |
| 2 | b | 2019-05-20 09:54:15 |
| 3 | c | 2019-05-20 09:54:19 |
+----+------+---------------------+
Within a stored function or trigger, it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
MySQL does not allow this, see Restrictions
All other major DBMS support this feature so hopefully MySQL will add this support soon.
In this case you may handle this in pragmatically whatever you prefer.
Before insert data just check total number of row of that table. If reach rows limit, then call delete method.

How do I use INSERT ... ON DUPLICATE KEY UPDATE?

State Problem
Apart from 1st col in MySQL server which is PK and auto_increment, col "C" is STRING/VARCHAR. How do I use INSERT ... ON DUPLICATE KEY UPDATE on col "C".
Should I also set col "C" as UNIQUE as well?
Example
Assume that my table in MySQL server looks similar to this...
---------------------------
|ID(PK)| A | B | C | D | E |
+------+---+---+---+---+---+
| 1 | | | | | |
| 2 | | | | | |
| 3 | | | | | |
---------------------------
What I expect
The duplicate input must be ignored from the MySQL server.
Can I also send alert pop-up/session pop-up, if the duplicate has been inserted into MySQL server as "This query has already existed."?
Column C must be set to NOT NULL, UNIQUE then SQL will return error against attempt of duplicate value insert. You may handle that error in your backend code. Or you can handle and alter the duplicate value with 'ON DUPLICATE KEY UPDATE' clause:
INSERT INTO t1 (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;

MySQL reorder row id by date

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.

The Foreign Key Options, ON DELETE CASCADE not working?

I am trying to use ON DELETE CASCADE for a database I'm working on. Didn't seem to work so I tested it out on a simple example with no success.
CREATE TABLE foo (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
data VARCHAR(10),
PRIMARY KEY (id)
)ENGINE=InnoDB;
CREATE TABLE foo2 (
id INT UNSIGNED NOT NULL,
data2 VARCHAR(10),
PRIMARY KEY (id),
CONSTRAINT fk_foo2_id FOREIGN KEY (id) REFERENCES foo(id) ON DELETE CASCADE
)ENGINE=InnoDB;
INSERT INTO foo (data) VALUE ('hello'),('world'),('mysql');
INSERT INTO foo2 (data2) VALUE ('hello2'),('world2'),('mysql2');
SELECT * FROM foo;
+----+-------+
| id | data |
+----+-------+
| 1 | hello |
| 2 | world |
| 3 | mysql |
+----+-------+
3 rows in set (0.00 sec)
SELECT * FROM foo2;
+----+--------+
| id | data2 |
+----+--------+
| 1 | hello2 |
| 2 | world2 |
| 3 | mysql2 |
+----+--------+
3 rows in set (0.00 sec)
DELETE FROM foo WHERE id=2;
SELECT * FROM foo;
+----+-------+
| id | data |
+----+-------+
| 1 | hello |
| 3 | mysql |
+----+-------+
2 rows in set (0.00 sec)
SELECT * FROM foo2;
+----+--------+
| id | data2 |
+----+--------+
| 1 | hello2 |
| 2 | world2 |
| 3 | mysql2 |
+----+--------+
3 rows in set (0.00 sec)
I can't for the life of me figure out why this isn't working. I looked at similar questions and answers on here and I did exactly what they said and it still didn't work. Most of them just said to change to ENGINE=InnoDb, but I tried it and no success.
There must be something I'm missing here, and it's probably very obvious.. Monday mornings.
If anyone can shed some light on this little noob problem of mine, I would greatly appreciate it!
Edit: removed the auto_increment from id in foo2 as it did not belong there
The first thing that pops to mind is to check the setting of the foreign_key_checks variable. If that's set to 0 (FALSE), then foreign key constraints are NOT enforced.
SHOW VARIABLES LIKE 'foreign_key_checks'
To enable foeign key constraints, set to the variable to 1
SET foreign_key_checks = 1;
NOTE: this affects only the current session. New sessions inherit the GLOBAL setting.
Also, verify that your tables are actually using the InnoDB engine, and that the foreign keys are defined. Easiest way is to get the output from:
SHOW CREATE TABLE foo;
SHOW CREATE TABLE foo2;
FOLLOWUP
This is something that we expect NOT to be broken in MySQL 5.1.61.
As a workaround, try defining the foreign key constraint as a separate ALTER TABLE statement.
ALTER TABLE foo2
ADD CONSTRAINT fk_foo2_id FOREIGN KEY (id) REFERENCES foo(id) ON DELETE CASCADE ;
I don't see much use in a foreign key constraint between two columns that are both defined with "auto_increment". In your example, you could easily create several rows in table "foo" (without a counterpart in "foo2"), and from then onwards you could not control whether "id" values in both tables match.
I admit I didn't check the documentation, but it would not surprise me if MySQL silently ignored a foreign key constraint for an auto-generated column.
IMNSHO, your table "foo2" should use "id" values which are set explicitly and reference specific rows in "foo", because then it would make sense that deleting such "foo" rows should cascade onto "foo2".

Deleting rows from multiple tables in MySQL

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