Is there such a thing as a reverse foreign key? - mysql

In MySQL, is there a way to specify that a.column cannot exist in b.column - a reverse foreign key?
In other words:
# Would not return any rows ever
SELECT * FROM a
INNER JOIN b ON a.column = b.column;
# Would fail
INSERT INTO a
SELECT * FROM b;
# Would not insert any rows
INSERT IGNORE INTO a
SELECT * FROM b;

No there is no such thing.
You would need to do that in a trigger:
DELIMITER $$
CREATE TRIGGER bi_a_each BEFORE INSERT ON a FOR EACH ROW
BEGIN
DECLARE forbidden_key INTEGER;
SELECT id INTO forbidden_key FROM b WHERE b.id = NEW.acolumn LIMIT 1;
IF forbidden_key IS NOT NULL THEN
SELECT * FROM error_insertion_of_this_value_is_not_allowed;
END IF;
END $$
DELIMITER ;

To a point, if you want "can be in A or B, but not both"
This is the "super key/sub type" pattern
Create a new table AB that has a 2 columns
SomeUniqueValue, PK
WhichChild char(1), limited to 'a' or 'b'
There is also a unique constraint on both columns
Then
Add a WhichChild column to tables A and B. In A, it is always 'a'. In B, always 'b'
Add foreign keys from A to AB and B to AB on both columns
Now, SomeUniqueValue can be in only A or B.
Note: in proper RDBMS you'd use check constraints or computed columns to restrict WhichChild to 'a' or 'b' as needed. But MySQL is limited so you need to use triggers in MySQL. However, this is simpler then testing table B for each insert in A etc

Try to create a trigger and throw an error from it.

Related

Something wrong with my SQL statement

It tells me I have an error somewhere here:
if not exists (select * from ARCUS where CUSTOMER_NO = a)
begin
insert into ARCUS (CUSTOMER_NO) values (a)
end
#Barmar has the good solution but i can't +1 the answer...
Just add a unique key on "CUSTOMER_NO"
You should also write your field and tbl name in lowercase for more readability when you use mysql.
ALTER TABLE `arcus` ADD UNIQUE INDEX `unique_customer_no` (`customer_no`);
And then do :
INSERT IGNORE INTO `arcus` SET `customer_no` = 'a';
Assuming this code is inside a stored procedure, you're missing the semicolon after the INSERT query.
if not exists (select * from ARCUS where CUSTOMER_NO = a)
begin
insert into ARCUS (CUSTOMER_NO) values (a);
end
But if CUSTOMER_NO is a unique key in the table, you can do it with a single query:
insert ignore into ARCUS (CUSTOMER_NO) values (a);
This has the advantage that it doesn't have to be in a procedure.
Also, as variant, you can something like:
insert into ARCUS (CUSTOMER_NO)
select 'a'
where not exists (select 1 from ARCUS where CUSTOMER_NO = 'a');

Mysql Insert if not exist in two column

I looked into MySQL duplicate key but cant figure it out.
I have a table like below:
id series chapter path(can be unique)
I want only insert data and not update. Lets say I have data like below:
seri:Naruto, klasor:567 ==> If both of these exist in table then do not insert.
seri:Naruto, klasor:568 ==> If Naruto exist but 568 does not exist then do insert.
How can I achieve this?
Easiest way would be to define unique index with two columns on that table:
ALTER TABLE yourtable ADD UNIQUE INDEX (seri,klasor);
You may also define two column primary key, which would work just as well.
Then use INSERT IGNORE to only add rows when they will not be duplicates:
INSERT IGNORE INTO yourtable (seri, klasor) VALUES ('Naruto',567);
INSERT IGNORE INTO yourtable (seri, klasor) VALUES ('Naruto',568);
Edit: As per comments, you can't use UNIQUE INDEX which complicates things.
SET #seri='Naruto';
SET #klasor=567;
INSERT INTO yourtable
SELECT seri,klasor FROM (SELECT #seri AS seri, #klasor AS klasor)
WHERE NOT EXISTS (SELECT seri, klasor FROM yourtable WHERE seri=#seri AND klasor=#klasor);
You may use the above query with two local variables or convert it to single statement by replacing the local variables with actual values.
Better way would be to use stored procedure:
CREATE PROCEDURE yourinsert (vseri VARCHAR(8), vklasor INT)
BEGIN
DECLARE i INT;
SELECT COUNT(*) INTO i FROM yourtable WHERE seri=vseri AND klasor=vklasor;
IF i=0 THEN
INSERT INTO yourtable (seri,klasor) VALUES (vseri, vklasor);
END IF;
END;
This would allow you to perform the INSERT using:
CALL yourinsert('Naruto',567);
INSERT INTO table_name (seri, klasor) VALUES ('Naruto',567)
WHERE NOT EXISTS( SELECT seri,klasor FROM table_name WEHERE seri='Naruto' AND klasor=567
)
Hope this helps..

Update if tuple is in the set, else insert in MySQL

I ran some query and got a set of tuples like ((A1,B1), (A2,B2), (A3,B3)....).
I need to check if a tuple from above set exists in a table XYZ(A,B,C). If it exists, then update C else insert (Ax,Bx,C) into the table.
I tried using the below query but it doesn't work. Is there any other way?
CASE WHEN EXISTS (SELECT * from XYZ as t where (t.A, t.B) in (select u.A, u.B from diff_table as u)) THEN
THEN UPDATE XYZ as v SET C = 1 WHERE (v.A, v.B) = (t.A, t.B) ELSE
INSERT INTO XYZ (A,B,C) values (u.A, u.B, 1) END;
I'd do it the other way round:
Update the ones that exist
Insert the ones that don't
Or if you have primary keys
Insert all with default value for C (existing will get rejected)
Update all
The functionality you want is insert . . . on duplicate key insert.
First, you need an appropriate index (if you don't have one):
create index idx_xyz_a_b on xyz(a, b)
Then:
insert into xyz(a, b, c)
select dt.a, dt.b, 1
from diff_table dt
on duplicate key update set c = 1;
By the way, if the value already exists, are you sure you want to set it to 1 instead of incrementing it?
insert into xyz(a, b, c)
select dt.a, dt.b, 1
from diff_table dt
on duplicate key update set c = c + 1;

MYSQL How to create a UNIQUE constraint on two columns any combinations (in both directions)

If I have mySQL table with two row, how can I make make a constraint that same value can't be on both cols to the same time and prevent creating doubles of any combinations of both columns?
For example, if I insert the following :
col1 col2
a b ok
c a ok
c b ok
c a not ok sure it's already in the table
a c not ok because it's already in the table in other combination (c a)
f f not ok because same value in both columns
Long time ago, I found this on net.
Lets say, you have table, named tableName then you have to create a primary key which includes both column.
create table tableName
(
col1 int not null,
col2 int not null,
primary key (col1,col2)
);
But creating primary key does not solve your problem for checking vice versa combination. For that, you have to create a trigger which check uniqueness for both side.
DELIMITER $$
CREATE TRIGGER tableName_bi BEFORE INSERT ON tableName FOR EACH ROW
BEGIN
DECLARE found_count,newcol1,newcol2,dummy INT;
SET newcol1 = NEW.col1;
SET newcol2 = NEW.col2;
SELECT COUNT(1) INTO found_count FROM tableName
WHERE col1 = newcol2 AND col2 = newcol1;
IF found_count = 1 THEN
SELECT 1 INTO dummy FROM information_schema.tables;
END IF;
END; $$
DELIMITER ;
Note : Change your table and column name according to yours.

MySQL swap primary key values

The accepted answer to sql swap primary key values fails with the error Can't reopen table: 't' - presumably this has something to do with opening the same table for writing twice, causing a lock.
Is there any shortcut, or do I have to get both, set one of them to NULL, set the second one to the first one, then set the first one to the previously fetched value of the second?
Don't use temporary tables for this.
From the manual:
You cannot refer to a TEMPORARY table more than once in the same query.
For example, the following does not work:
mysql> SELECT * FROM temp_table, temp_table AS t2;
ERROR 1137: Can't reopen table: 'temp_table'
This error also occurs if you refer to a temporary table multiple
times in a stored function under different aliases, even if the
references occur in different statements within the function.
UPDATE:
Sorry if I don't get it right, but why does a simple three way exchange not work?
Like this:
create table yourTable(id int auto_increment, b int, primary key(id));
insert into yourTable(b) values(1), (2);
select * from yourTable;
DELIMITER $$
create procedure pkswap(IN a int, IN b int)
BEGIN
select #max_id:=max(id) + 1 from yourTable;
update yourTableset id=#max_id where id = a;
update yourTableset id=a where id = b;
update yourTableset id=b where id = #max_id;
END $$
DELIMITER ;
call pkswap(1, 2);
select * from yourTable;
To swap id values of 1 and 2, I would use a SQL statement like this:
EDIT : this does NOT work on an InnoDB table, only works on a MyISAM table, per my testing.
UPDATE mytable a
JOIN mytable b ON a.id = 1 AND b.id = 2
JOIN mytable c ON c.id = a.id
SET a.id = 0
, b.id = 1
, c.id = 2
For this statement to work, the id value of 0 must not exist in the table, any unused value would be suitable... but to get this to work in a single SQL statement, you need to (temporarily) use a third id value.
This solution works for regular MyISAM tables, not temporary tables. I missed that this was being performed on a temporary table, I was confused by the error message you reported Can't reopen table:.
To swap id values 1 and 2 in a temporary table, I'd run three separate statements, again, using a temporary placeholder value of 0:
UPDATE mytable a SET a.id = 0 WHERE a.id = 1;
UPDATE mytable b SET b.id = 1 WHERE b.id = 2;
UPDATE mytable c SET c.id = 2 WHERE c.id = 0;
Edit: Fixed errors