Enforce unique values across two tables - mysql

Is it possible to enforce uniqueness across two tables in MySQL?
I have two tables, both describing users. The users in these tables were for two different systems previously, however now we're merging our authentication systems and I need to make sure that there are unique usernames across these two tables. (it's too much work to put them all into one table right now).

You can't declare a UNIQUE constraint across multiple tables. MySQL 8.0 supports CHECK constraints, but those constraints cannot reference other tables. But you can design a trigger to search for the matching value in the other table. Here's a test SQL script:
DROP TABLE IF EXISTS foo;
CREATE TABLE FOO (username VARCHAR(10) NOT NULL);
DROP TABLE IF EXISTS bar;
CREATE TABLE BAR (username VARCHAR(10) NOT NULL);
DROP TRIGGER IF EXISTS unique_foo;
DROP TRIGGER IF EXISTS unique_bar;
DELIMITER //
CREATE TRIGGER unique_foo BEFORE INSERT ON foo
FOR EACH ROW BEGIN
DECLARE c INT;
SELECT COUNT(*) INTO c FROM bar WHERE username = NEW.username;
IF (c > 0) THEN
-- abort insert, because foo.username should be NOT NULL
SET NEW.username = NULL;
END IF;
END//
CREATE TRIGGER unique_bar BEFORE INSERT ON bar
FOR EACH ROW BEGIN
DECLARE c INT;
SELECT COUNT(*) INTO c FROM foo WHERE username = NEW.username;
IF (c > 0) THEN
-- abort insert, because bar.username should be NOT NULL
SET NEW.username = NULL;
END IF;
END//
DELIMITER ;
INSERT INTO foo VALUES ('bill'); -- OK
INSERT INTO bar VALUES ('bill'); -- Column 'username' cannot be null
You also need similar triggers ON UPDATE for each table, but you shouldn't need any triggers ON DELETE.

the best way to do this is to declare another table with the unique columns, and have the multiple tables reference these tables

Maybe not direct answer to your question, but:
You should consider rewriting your code and restructuring your database to unite those two tables into one.
The design you are trying to enforce now will complicate your code and database schema and it will make any further upgrade to other database software or frameworks harder.

You could add an extra table with a single column as a primary key. Then create a trigger on each of your old user tables to insert the id into this extra table.
create table users1 (
user_id integer primary key,
username varchar(8) not null unique
);
create table users2 (
user_id integer primary key,
username varchar(8) not null unique
);
create table all_usernames (
username varchar(8) primary key
);
create trigger users1_insert before insert on users1 for each row
insert into all_usernames values(new.username);
create trigger users2_insert before insert on users2 for each row
insert into all_usernames values(new.username);
create trigger users1_update before update on users1 for each row
update all_usernames set username = new.username
where username = old.username;
create trigger users2_update before update on users2 for each row
update all_usernames set username = new.username
where username = old.username;
create trigger users1_delete before delete on users1 for each row
delete from all_usernames where username = old.username;
create trigger users2_delete before delete on users2 for each row
delete from all_usernames where username = old.username;
You can then populate the table with
insert into all_usernames select username from users1;
insert into all_usernames select username from users2;

Obviously if there are already duplicates in the two tables you will have to solve that problem by hand. Moving forward, you could write a trigger that checks both tables to see if the value already exists, and then apply that to both tables.

Would changing the type of the ID column be affordable? Then you could go for GUIDs which would be unique across as many tables as you want.

I don't know MySQL but this is how you can do it in Oracle and I believe MySQL does support materialized views too.
You create a materialized view on those two tables. And you add a unique constraint on this view.
This view needs to be refreshed every time a change to one of the two base tables is committed.

Related

How to connect two tables in phpmyadmin?

I'm running a wateranalyzer and want to save the sensor-data in a MariaDB.
I split the data into 2 tables: one for the automated part and one table which stores data I enter manually:
Tables:
I'm having a hard time (with just basic knowledge about databases) to figure out how I can "bind" ID and DateTime from one table to the other one, so if manual data is added, ID is incremented by 1 and the actual Date and Time is set in DateTime.
I bet I can do this somehow in PHPmyadmin?
thanks for your time!
using triger. this example for you.
DELIMITER //
CREATE TRIGGER contacts_after_insert
AFTER INSERT
ON contacts FOR EACH ROW
BEGIN
DECLARE vUser varchar(50);
-- Find username of person performing the INSERT into table
SELECT USER() INTO vUser;
-- Insert record into audit table
INSERT INTO contacts_audit
( contact_id,
deleted_date,
deleted_by)
VALUES
( NEW.contact_id,
SYSDATE(),
vUser );
END; //
DELIMITER ;
Is it anything more complex than having an ID in Wasser that matches the other ID? That is, first insert into Luft, then get the id and only then, INSERT INTO Wasser....
(A Trigger seems unnecessarily complicated.)
As Rick Suggested, you need to have an ID column in the second table that references ID in first table. Trigger is a better option if the process of getting the ID and inserting it along with other columns (pH, Redox...) into the second table is complicated.
Make ID in the second table as a foreign key to the ID in first table.

how to keep track of how many total records have been in the table

I have to Write one or more triggers that keep track of how many total records have been in the sakila_film table in a single variable, but I am having trouble figuring out how to do this trigger the table is taken from http://dev.mysql.com/doc/sakila/en/sakila-structure-tables-film.html .This is what I have tried the code below but I am getting an error and I don't know any other way i could do it.
create trigger records after Update on sakila_film Count(*) from sakila_film;
Where are you planning on storing this count of total rows in the film table?
If you are going to manage this with triggers, it seems like you'd need two triggers. One for INSERT, one for DELETE. An UPDATE statement won't change the number of rows in the table. (Note that triggers are NOT fired for foreign key actions; which is a concern if there's a foreign defined with a DELETE rule.) And the triggers will not be fired for a TRUNCATE statement.
Setting aside those concerns, we'd need somewhere to store the rowcount over time. As an example:
CREATE TABLE rowcount_history_film
( ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
, rowcount INT NOT NULL
, KEY rowcount_history_film_IX1 (ts)
) Engine=MyISAM;
And then we could use trigger to insert a row into that table whenever a DML statement changes the number of rows in the film table:
DELIMITER $$
CREATE TRIGGER film_ad
AFTER DELETE ON film
FOR EACH ROW
BEGIN
INSERT INTO rowcount_history_film (ts, rowcount)
SELECT NOW(), COUNT(*) FROM film;
END$$
CREATE TRIGGER film_ai
AFTER INSERT ON film
FOR EACH ROW
BEGIN
INSERT INTO rowcount_history_film (ts, rowcount)
SELECT NOW(), COUNT(*) FROM film;
END$$
DELIMITER ;

how to insert concate value using trigger when new row inserted in mysql

In my schema I had two columns ,I am new to MySQL how to concatenate columns using Computed Column in mysql
ordertbl schema
Autoorderid int auto-increment
Orderid varchar(45)
I want to value orderid value insert when new row inserted
i had created the trigger for but this trigger is not firing when i am inserting the row
insert into order_master(customerID,ItemID,quantity,Unitprice,Total) values ('07961A','1000',6,5,30)
use ntc_sales; DELIMITER
$$ CREATE TRIGGER order_master_BINS BEFORE INSERT ON order_master FOR EACH ROW
SET NEW.OrderID = CONCAT('ORD' ,NEW.AutoOrderID) ; end;
create table
enter code here
CREATE TABLE Demo(ID INT PRIMARY KEY,
IDwithChar AS 'ORD' + RIGHT('0000000' + CAST(ID AS VARCHAR(10)), 6) PERSISTED,valuevar nvarchar(45)
)
This is showing syntax error in mysql how to create table as above schema in mysql
You can't really do this using a trigger in MySQL.
A BEFORE trigger is run before the auto increment value is set, so NEW.AutoOrderID will always be 0.
An AFTER trigger cannot update the same table as it is triggered on.
In other words, instead of triggering on an INSERT, creating the row using a stored procedure that does the insert/update in a single transaction is probably the best way to proceed.
EDIT: You could use a separate table for the sequence, but that would not guarantee that the order number is the same value as AutoOrderID, just a unique value.
CREATE TABLE order_sequence (
seq INT PRIMARY KEY AUTO_INCREMENT
);
CREATE TRIGGER order_master_BINS BEFORE INSERT ON order_master
FOR EACH ROW
BEGIN
INSERT INTO order_sequence VALUES (0);
SET NEW.OrderID = CONCAT('ORD', LAST_INSERT_ID());
END
An SQLfiddle to test the alternate solution with.

MySQL Trigger - Update relation table with extra values

What I'm trying to achieve is, I want to automate the values of the table between the users and folders table. Since it's a many-to-many relationship I created the user_folders table. Currently the server (nodejs) gets the request with userid, clientfolderid and some an array of bookmarks (which are not important now). It checks if the user already has this folder, by selecting from the user_folders table and if it's not existing it inserts a new row into the folder table. Then it has to send another statement to insert into the user_folders table.
So I have to "manually" keep the users_folder table updated.I guess this is a common problem and wanted to know if there is a pattern or a proven solution? The odd thing is that MySQL automatically handles the deletion of rows with an AFTER DELETE trigger but there is no (at least that I know of) automation with an AFTER INSERT trigger.
As I already said an AFTER INSERT trigger could possibly solve it, but I think it's not possible to pass some extra parameters to the AFTER INSERT trigger. This would be the user_id and the folder_client_id in my case.
I was thinking of a solution that I could create another table called tmp_folder which would look like:
tmp_folder
-- id
-- title
-- changed
-- user_id
-- folder_client_id
Then create an AFTER INSERT trigger on this table which inserts into folders and user_folders and then removes the row from tmp_folder again. Would this be the right way or is there a better one?
I would basically do the same with the bookmarks and user_bookmarks table. The best thing would be if it's even possible to insert a folder then the owner into the user_folders table with user_id and folder_client_id and then multiple other users into user_folders with the user_id and an default folder_client_id of -1 or something which will be updated later.
Meanwhile thanks for reading and I hope you can help me :)
PS: Is there a name for the table between 2 other tables in an m-2-m relationship?
I don't see an easy way to do this via triggers, but a stored procedure may suit you:
DELIMITER //
CREATE PROCEDURE
add_user_folder(
IN u_user_id BIGINT UNSIGNED,
IN u_folder_client_id BIGINT UNSIGNED,
IN v_title VARCHAR(255)
)
BEGIN
DECLARE u_found INT UNSIGNED DEFAULT 0;
SELECT
1 INTO u_found
FROM
user_folders
WHERE
user_id = u_user_id AND
folder_client_id = u_folder_client_id;
IF IFNULL(u_found, 0) = 0 THEN
START TRANSACTION;
INSERT INTO
folders
SET
title = v_title,
changed = UNIX_TIMESTAMP();
INSERT INTO
user_folders
SET
user_id = u_user_id,
folder_id = LAST_INSERT_ID(),
folder_client_id = u_folder_client_id;
COMMIT;
END IF;
END;
//

Split table into two tables with foreign keys

I have one table: drupal.comments, with amongst others, the columns:
cid: primary key
uid: foreign key to users table, optional
name: varchar, optional
email: varchar, optional
The description says: UID is optional, if 0, comment made by anonymous; in that case the name/email is set.
I want to split this out into two tables rails.comments and rails.users, where there is always a user:
id: primary key
users_id: foreign key, always set.
So, for each drupal.comment, I need to create either a new user from the drupal.comments.name/drupal.comments.email and a rails.comment where the rails.comment.users_id is the ID of the just created user.
Or, if username/email already exists for a rails.user, I need to fetch that users_id and use that on the new comment record as foreign key.
Or, if drupal.comment.uid is set, I need to use that as users_id.
Is this possible in SQL? Are queries that fetch from one source, but fill multiple tables possible in SQL? Or is there some (My)SQL trick to achieve this? Or should I simply script this in Ruby, PHP or some other language instead?
You could do this with a TRIGGER.
Here's some pseudo-code to illustrate this technique:
DELIMITER $$
DROP TRIGGER IF EXISTS tr_b_ins_comments $$
CREATE TRIGGER tr_b_ins_comments BEFORE INSERT ON comments FOR EACH ROW BEGIN
DECLARE v_uid INT DEFAULT NULL;
/* BEGIN pseudo-code */
IF (new.uid IS NULL)
THEN
-- check for existing user with matching name and email address
select user_id
into v_uid
from your_user_table
where name = new.name
and email = new.email;
-- if no match, create a new user and get the id
IF (v_uid IS NULL)
THEN
-- insert a new user into the user table
insert into your_user_table ...
-- get the new user's id (assuming it's auto-increment)
set v_uid := LAST_INSERT_ID();
END IF;
-- set the uid column
SET new.uid = v_uid;
END IF;
/* END pseudo-code */
END $$
DELIMITER ;
I searched further and found that, apparently, it is not possible to update/insert more then one table in a single query in MySQL.
The solution would, therefore have to be scripted/programmed outside of SQL.