Hard task with foreign keys - mysql

I have two related tables, quite common case: Clients(CID, name, surname) and Visits(VID, VCID, dateOfVisit) - VCID is the Client ID. How to manipulate foreign keys, when I want database to delete Visits records relative to some Client, who I DELETE, and to delete Client, when I DELETE the last Visit of that client left?

I would suggest you do a soft delete, so you can keep your Visits record.
Soft delete means you just add an extra field is_active default it to true, and when you delete the record flip it to false.

you can automatically delete visits for deleted clients by using "on delete cascade". something like:
create table clients (id integer primary key);
create table visits (id integer primary key,
client integer,
foreign key (client) references clients(id)
on delete cascade);
but going the other way (automatically deleting clients with no visits) is more difficult.
you can manually delete clients with no visits by executing:
delete from clients where id in
(select cid from
(select clients.id as cid, visits.id as vid
from clients left join visits on (clients.id = visits.client))
where vid is null);
(and maybe there's something simpler?). so either run that every now and then, or create a trigger that runs it when something is deleted from visits (although if you're going to add a trigger it could use the deletion info to do something smarter).
or maybe someone with more time/energy than me can write an answer with the trigger...?
(as others have said, automatically deleting clients is pretty drastic behaviour - it's not something you'd normally want to do in a production system - apart from anything else, if clients need more visits they are going to be pretty annoyed if they have to enter their details again...)

Related

MySQL : automatically update a column in another table with foreign key, trigger or what?

I want to do some automatic updates between MySQL many tables, but I wonder how to do it fine. Here is an example :
Table `articles` : ID, TEXT, DATE, DELETED
Table `comments` : ID, TEXT, DATE, DELETED
Table `users` : ID, NAME, AGE, DELETED
Table `link` : ID, ARTICLE_ID, COMMENT_ID, USER_ID, DELETED
As you guess, the link table contains IDs from others.
I have already designed my database like this, to keep tables with the lowest columns as possible.
I know there are lots of questions here about it, but I don't really know what is the best solution. So here is what I want to do :
When a comment is deleted (update comments set DELETED=1 where ID=...), I want to update the column link.DELETED (=> 1).
When a user is deleted (update users set DELETED=1 where ID=...), I want to update the columns link.DELETED (=> 1) and articles.TEXT (=> NULL).
I know I can use foreign keys between link.ARTICLE_ID and article.ID for example, and simply delete rows. But I have put limited rights on the user used by my website. I will cron a batch with a more powerful user to delete rows tagged DELETED to cleanup the database.
Is it possible to do that wih FK, or should I use triggers (I don't really know how to use them) or something else ?
I have tried to put FK between DELETED columns but when I update a comments row, all link ones are updated too :/
Thanks for your help.
I've done what I wanted with the 2 solutions :
With a trigger : pseudo-delete (update DELETED=1 ...) causes pseudo-delete in my link table.
With a foreign key : true delete (delete from ...) causes true delete in my link table.
So, I can pseudo-delete rows from my website, with the basic user.
And I can clean my base with my batch using a user with delete rights.

Why are multiple indexes being generated for a table?

I've just realized that one of my tables, "pclass", has multiple instances of several foreign keys. In the Structure tab, #2-5 are the foreign keys. I have no idea why multiple instances are being generated.
Could they be generated by the JOINS? Please let me know if I need to provide other information.
$brother_id = htmlspecialchars($_GET["brother_id"]);
$selected = $brother_id;
$query_brotherId = "SELECT b.id, b.firstname, b.lastname, b.pname, b.country, b.street01, b.street02, b.city, usStates.abv AS us_state, b.intl_state, b.postalcode, b.zipcode, b.phone, b.email, pclass.id AS pclass_id, greekAlphabet.name AS pclass01, prepclass.name AS prepclass, pclassSuffix.name AS pclass02, semester.name AS pclass_sem, pclass.year AS pclass_year, b.bigbrother_id AS bbID, bb.firstname AS bbFirst, bb.lastname AS bbLast, b.status, b.comments
FROM brothers AS b
LEFT JOIN pclass ON b.pclass_id = pclass.id
LEFT JOIN prepclass ON pclass.prepclass_id = prepclass.id
LEFT JOIN greekAlphabet ON pclass.greekAlphabet_id = greekAlphabet.id
LEFT JOIN pclassSuffix ON pclass.suffix_id = pclassSuffix.id
LEFT JOIN semester ON pclass.semester_id = semester.id
LEFT JOIN usStates ON b.us_state = usStates.id
LEFT JOIN brothers AS bb ON b.bigbrother_id = bb.id
WHERE b.id = $brother_id";
$result_brotherId = mysqli_query($link, $query_brotherId);
First your question:
Could they be generated by the JOINS?
No. Foreign Keys are generated by data definition statements like CREATE TABLE, ALTER TABLE and so on.
I have no idea why multiple instances are being generated.
The person who created the database must have thought they will be useful. Or if you created the database via some sql-tool (don't know) the tool created the foreign keys because it got told there is a relation between those fields.
Why it is probably not bad to have the keys:
Foreign Keys are created to display the relations between your different table.
Also they enforce a specific behaviour when you are doing actions which could disrupt the integrity of your data. You can change this behaviour in your last screenshot.
For each foreign key you can give a name which will be shown in error messages when you try to act against the constraing. And you can define how the foreign key acts if you change or delete the parent field.
For example
You have the following tables displaying which tool belongs to which person.
persons
personid
firstname
lastname
...
tools
toolid
personid (foreign key to persons)
name
....
So in the tools table you have a foreign key to the persons table, this field defines the owner of the tool.
Now let's define some use cases
Assumption: For some reason Peter is no longer able to wield any tools, so he no longer fits into the database.
What should happen to his tools? It depends what your database displays!
your database displays anyone who ever owned a tool.
This means, even if the person actually doesn't even exist anymore, the data should still remain. You would actually enforce this behaviour otherwise, but it would work in our current case to show what the foreign key can do.
So the action we choose for ON DELETE is RESTICT. (It also is the default action)
Now let's try to call: DELETE FROM persons WHERE firstname = 'Peter'
Result: the foreign key constraint will prompt you an error message. There are relations which depend on this entry in the persons table.
The database displays persons and some tools, tools don't have to have an owner
In this case we again want to delete the person Peter. His tools can remain in the database, instead of the personid they will get a null value into this field.
So we choose the action ON DELETE: SET NULL
This one is pretty straight forward. Important: the field with the foreign key must not have a NOT NULL constraint.
The database displays the people and the tools in a building or something..
So if Peter and his tools leave the building, we don't care about them anymore.
The action for ON DELETE: CASCADE.
If you now enter the DELETE-statement, the foreign key will take care of deleting all the other entries (the tools) connected to Peter.

MySQL delete row if there is no foreign constraint

I have two tables. One called peoples another called addresses. The peoples table has a foreign constraint on the address table (peoples.address_id = addresses.address_id) Multiple people may have the same address. There is a unique constraint on the address column on the addresses table
I came across a situation where I have to delete a person from the peoples table and the associated address on the on the addresses table IF AND ONLY IF there is no other record in the peoples table who have the same address.
How can I go about doing this in MySQL?
This should be more performant than doing a subquery:
DELETE
addresses
FROM
addresses
LEFT JOIN peoples
ON addresses.address_id = peoples.address_id
WHERE
peoples.address_id IS NULL
It should delete all addresses that aren't referenced in the peoples table. If you want to only delete the specific address in question, add something to the WHERE clause like AND addresses.address_id = '<address_id>'.
Example
You can use a NOT EXISTS clause like below
DELETE FROM addresses
WHERE NOT EXISTS
(SELECT * FROM peoples WHERE peoples.address_id=addresses.address_id)
The cons against this is it will be pretty slow as it will re-run the query for every address row that exists. Otherwise it should solve the problem.
Delete the person first. Then this query deletes if the address has no people

MySQL cascading tables

(My website is built using PHP and MySQL.)
My DB structure for users is mainly composed of 2 tables: "doctors" and "clients".
However, in order to integrate a chat system, I need to create a third table, namely 'chat_users', which combines both doctors and clients: fields of 'chat_users' table are
userid (unique integer),
username,
type (0:client, 1:doctor),
refid (id of the user in the associated clients or doctors table)
But I do not want to insert/delete/update this table manually each time a client or doctor is inserted/updated/deleted. I heard about cascading table some time ago...
What would be the best way performance-wise to do so? How can I achieve it?
I'm not sure you'll consider this an "answer", but may I comment on your database architecture?
You will be much happier in the long run having the following tables:
user_account: (ua_id, ua_email, ua_username, ua_password, etc.)
doctor: (d_id, ua_id, etc.)
customer: (c_id, ua_id, etc.)
In other words, have your relation going the other way. Then if you would like to be able to delete a doctor or customer by simply deleting the user_account, you can add the following relational constraint:
ALTER TABLE `doctor`
ADD CONSTRAINT `doctor_fk_user_account` FOREIGN KEY (`ua_id`) REFERENCES `user_account` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `customer`
ADD CONSTRAINT `customer_fk_user_account` FOREIGN KEY (`ua_id`) REFERENCES `user_account` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
What you need is an AFTER INSERT Trigger. This would allow you to create new users. In case if you want it to be updated on update and deleted on delete of the original record then you need those triggers as well.
CREATE TRIGGER `chat_users_insert` AFTER INSERT ON `doctors`
FOR EACH ROW BEGIN
INSERT INTO `chat_users` SET user_id= NEW.id;
END;
The above would insert a record and set the value of id. http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html can give you exact syntax. Let me know if you need any specific clarifications.
I know, this is not exactly an answer to your question but what about using an old fashioned view instead? This would save you from storing redundant data altogether.
CREATE VIEW chat_users AS
SELECT drid uid, drid userid, drname username, 0 FROM doctors
UNION ALL
SELECT clid+100000, clid, clname, 1 FROM clients
This view will have unique uids only if you don't have more than 100000 doctors in your table (otherwise choose a higher offset). The advantage of this approach would be that there is no dependent table data to maintain.
"I do not want to insert/delete/update this table manually each time a client or doctor is inserted/updated/deleted."
Why are you fretting about this? Just do it. You have application requirements that mandate it, so unless you can figure out how to unify your client and doctor tables, you will need a third that relates to your chat function.
The difficulty of adding this in an application framework is almost zero, it's just the case of creating an additional record when a client or doctor is created, and removing it when their respective record is deleted.
The other answers here propose using views or triggers to obscure what's really happening. This is generally a bad idea, it means your application isn't in charge of its own data, basically handing over control of certain application logic functions to the database itself.
The best solution is often the most obvious, as that leads to fewer surprises in the future.

Is there an alternative approach to this (unallowed) UPDATE with a nested SELECT?

I have an existing table of contacts that has about 140k records in it. I am introducing a parent table (let's call them "parent_contacts") such that one parent_contact can have many contacts; but initially, parent_contacts will be seeded to have one record for every contact that currently exists in the database.
I thought I was being clever in trying something like the following, which I now understand is not allowed (assume all the necessary parent_contact records have been created ahead of time):
UPDATE contacts
SET contacts.parent_id =
(SELECT parent_contacts.id FROM parent_contacts
WHERE NOT EXISTS
(SELECT * FROM contacts AS c WHERE c.parent_id = parent_contacts.id) LIMIT 1)
(If not readily apparent, the idea here being to set the parent_id of each contact to the id of the first parent_contact that another contact isn't already linked to)
Since this particular approach is not possible, is there another way of doing this that doesn't involve executing 140k individual update statements?
FOLLOW-UP: I resolved this by introducing a temporary child_id on the parent table, which I then removed after the seeding was finished. But in the context of the original question, I think Tony's answer below sounds apt.
You seem to have done this backwards
Add Parent_id to contacts (no constraint yet!)
Update Contacts filling Parent_id with a unique number.
Create ParentContracts, Don't put Identity in or Primary key.
The backfill ParentContacts with a Insert into ParentContacts Select Parent_id, .... From Contacts
Add the Identity (don't forget seed to next value) and Primary key to ParentContacts
Add the foreign key constraint to Contacts.
Nice easy steps and easy to check each one instead of this whole cloth manouvre you are trying now.