Limit number of associated records on DB level - mysql

Let's assume I have two tables parents and children. Naturally parents may have many children with one-to-many association. Is there any construct within MySQL or PostgreSQL which would allow to limit the number of associated objects, something like:
FOREIGN KEY (parent_id)
REFERENCES parent(id)
LIMIT 3
Does anything like this exists or do we need to have a custom trigger?

Not in the definition of foreign keys. I would solve that by adding a sequential number for each child per parent like this (code for PostgreSQL, the principal is standard SQL):
CREATE TABLE child (
child_id serial PRIMARY KEY
, parent_id int NOT NULL REFERENCES parent
, child_nr int NOT NULL
, CHECK (child_nr BETWEEN 1 AND 3)
, UNIQUE (parent_id, child_nr)
);
This way you can have children 1 through 3 for each parent or some or none of those. But no others.
Since you have a natural PK with (parent_id, child_nr) now, you could drop the surrogate PK column child_id. But I like to have a single-column surrogate PK for almost every table ...
You could use a trigger to limit the number, which checks how many children are there already before inserting a new one. But you'd run into concurrency issues, and it's more expensive, less reliable, easier to circumvent and vendor-specific.
How to manage child_nr?
The RDBMS just enforces (reliably) that no illegal state can ever exist in the table. How you figure out the next child_nr is up to you. Many different approaches are possible.
For just three children, you could insert all children automatically when creating a parent (with a trigger, a rule or in your application). With given (parent_id, child_nr) and additional columns NULL.
Then you would only allow UPDATE and not INSERT or DELETE for the child table (GRANT / REVOKE), or even make sure with another trigger, so that superusers cannot circumvent it. Make the FK to parent with ON DELETE CASCADE, so children die with the parent automatically.
Alternative
Somewhat less reliable, but cheaper: Keep a running count of children in the parent table and restrict it to be <= 3. Update it with every change in the child table with triggers. Be sure to cover all possible ways to alter data in the child table.

Related

Copying from one table to other, how to enforce foreign key check on the whole data set but not on separate rows?

I'm using MySQL. Let's assume I have a table hierarchy with two columns: id, parent_id.
The parent_id refers to id of other row of the same table, so I have the foreign key there.
The hierarchy table contains some data, but they are not relevant now.
I also have a second table called new_hierarchy_entries that has the same columns, but there are no foreign key restrictions set.
new_hierarchy_entries contains:
id parent_id
2 1
1 null
Now I want to copy all the rows from new_hierarchy_entries into hierarchy. When I run naively:
INSERT INTO hierarchy SELECT * FROM new_hierarchy_entries
I get error: Cannot add or update a child row: a foreign key constraint fails (my_db.hierarchy, CONSTRAINT hierarchy_ibfk_2 FOREIGN KEY (parent_id) REFERENCES hierarchy (id))
Of course, if the rows are inserted one by one, the first row (id=2, parent=1) cannot be inserted, because there is no row with id=1 in table hierarchy.
On the other hand, if all rows were added at once, then the constraints would be satisfied. So how can I copy the rows in such a way that I'm sure that constraints are satisfied after the copying, but they may not be satisfied while copying?
Sorting rows of new_hierarchy_entries by id will not help. I cannot assume that parent_id < id in the same row.
Sorting rows of new_hierarchy_entries by the hierarchy (using tree terminology, give me leaves first, then their parents etc.) would help, but I'm not sure how to do that in MySQL query.
I played with the idea of temporarily turning the FOREIGN_KEY_CHECKS off. But then I could insert inconsistent data and I wouldn't find out. Turning FOREIGN_KEY_CHECKS on doesn't make the database check consistency of all the data. It would take too much resources anyway.
This is tricky. I don't know any way to make MySQL re-check foreign key references after enabling FOREIGN_KEY_CHECKS.
You could check yourself for orphan rows, and if there are any, roll back.
BEGIN;
SET SESSION FOREIGN_KEY_CHECKS=0;
INSERT INTO hierarchy SELECT * FROM new_hierarchy_entries;
SET SESSION FOREIGN_KEY_CHECKS=1;
SELECT COUNT(*) FROM hierarchy AS c
LEFT OUTER JOIN hierarchy AS p ON p.id=c.parent_id
WHERE p.id IS NULL;
-- if count == 0 then...
COMMIT;
-- otherwise ROLLBACK and investigate the bad data
One other possibility is to use INSERT with the IGNORE option, which will skip failed rows. Then repeat the same statement in a loop, as long as you see "rows affected" more than 0.
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
...

MySQL bulk insertion: parent table autogenerated key into child table

We are doing migration from SQL Server 2012 to MySQL 5.6. One of the scenarios that came up is inserting bulk records in parent child table. An example:
create table parent (
parent_id int primary key auto_increment,
parent_name varchar(100) );
create table child (
child_id int primary key auto_increment,
child_name varchar(100) ,
foreign key (parent_id) references parent(parent_id));
Say I have two temp tables parent_temp and child_temp and I want to insert the records into parent and child tables. The problem is that I need to keep track of the auto-generated parent_id column with the parent_temp_id. In SQL Server, we used Output into statement to work around this problem. Since there is no direct method available here, here are some straight forward solutions that I could think of:
Do the insertion through entity framework.
Use a while loop to iterate the parent records and do an insertion in the parent table, get hold of the auto-generated key and insert into child table. And so on.
Add a spare column dummy_col in the parent table to hold the mapping. This will allow bulk inserts for parent table. The insert query looks like
insert into parent(parent_name,dummy_col )
select parent_temp_name, parent_temp_id from parent_temp
In this way we will have a 1-1 mapping between the rows of parent and parent_temp tables. The child table query looks like
insert into child(child_name,parent_id)
select child_temp_name, p.parent_id from child_temp ct
inner join parent p on p.dummy_col = ct.parent_temp_id
The problem with approaches 1 and 2 is that they are slow for bulk insertions. We could be inserting easily 15k rows at one time. Approach 3 will be problematic if two or more users are simultaneously running the same insertion query, and if their parent_temp_id's match since (we are using int, and they would be always be starting from 1,2,3,4...). If we use GUIDs instead of ints, we can probably avoid this duplicate issue. But we would always need to create extra columns in such tables and make sure that they are not used for some other purpose.
Based on the above scenario, are there any other solutions for MySQL? And which one would you prefer?

How to reset all ID AUTO Increment to sequencial value on each INSERT Or DELETE Query Execution..!

I have a table with name as listings and inside there I have a COLUMN namely as when some rows are deleted so the AUTO Incrmementation columns namely as "ID" goes into soemthing very bad values..Like missing values in between which I don't like and don't suit like a professional way..so therefore I want please if you people can guide me to how reset all ID columns values in rows on each INSERT or DELETE Query Exeution please..!
If you really want to find the lowest unused key value, don't use AUTO_INCREMENT at all, and manage your keys manually. However, this is NOT a recommended practice.
AS explained at Auto Increment after delete in MySQL
Primary autoincrement keys in database are used to uniquely identify a
given row and shouldn't be given any business meaning. So leave the
primary key as is and add another column called for example
courseOrder. Then when you delete a record from the database you may
want to send an additional UPDATE statement in order to decrement the
courseOrder column of all rows that have courseOrder greater than the
one you are currently deleting.
As a side note you should never modify the value of a primary key in a
relational database because there could be other tables that reference
it as a foreign key and modifying it might violate referential
constraints.
Well it is not recommended but you insisted , so use this to re order
By using something like:
ALTER TABLE table_name AUTO_INCREMENT = 1;

Delete all descendants of an entry (MySQL)

I have a table with, let's say, the following columns:
Name , Parent Name, ID
Let's also say that there are three entries where Parent Name is Null (meaning they are the top-most parent) - F_one, G_one, and H_one.
If I want to delete all the descendants of one of those parents (G_one, why not?) meaning all the children of G_one, all the children of those children, and the children of those, and so on all the way until the terminal level where, that row's Name does not exist as a Parent Name for any other entry.
Is that possible to be done easily, maybe with a single query?
Bonus, is there a way to select all of the G_one lineage so I can manipulate it to my whim and will?
Can assume:
-No Children are shared among parents
Cannot assume:
-A discrete or even consistent number of sub-levels.
As #Marc B's suggestion, a FORIEGN KEY with ON DELETE CASCADE would achieve this.
If you haven't one, you can add it now:
If there is a UNIQUE constraint on Name (I assume the PRIMARY key os ID), skip thi sstep. If there ism't one, create it:
ALTER TABLE tableX
ADD CONSTRAINT unique_Name
UNIQUE (Name) ;
If the previous step succeeded, add the FOREIGN KEY:
ALTER TABLE tableX
ADD CONSTRAINT fk_Name_ParentName
FOREIGN KEY (ParentName)
REFERENCES tableX (Name)
ON UPDATE CASCADE
ON DELETE CASCADE ;
If the previous step succeeded, you can now delete your rows with one statement:
DELETE
FROM tableX
WHERE ParentName = 'G_one' ;
This should result in: Y rows affected.
I can't test this, but I think something like this might work:
CREATE TRIGGER casc_del AFTER DELETE on tblName
FOR EACH ROW
DELETE FROM tblName
WHERE tblName.parent_name is not null
AND tblName.parent_name NOT IN (SELECT name FROM tblName)
More about triggers in MySQL can be found here. Note: this approach would only work in 5.02 or later.

mysql many to many relationship

Been reading the tutorial How to handle a Many-to-Many relationship with PHP and MySQL .
In this question I refer to the "Database schema" section which states the following rules:
This new table must be constructed to
allow the following:
* It must have a column which links back to table 'A'.
* It must have a column which links back to table 'B'.
* It must allow no more than one row to exist for any combination of rows from table 'A' and table 'B'.
* It must have a primary key.
Now it's crystal clear so far.
The only problem I'm having is with the 3rd rule ("It must allow no more than one row to exist for any combination").
I want this to be applied as well, but it doesn't seem to work this way.
On my test instance of mysql (5.XX) I'm able to add two rows which reflect the same relationship!
For example, if I make this relation (by adding a row):
A to B
It also allows me to make this relation as well:
B to A
So the question is two questions actually:
1) How do I enfore the 3rd rule which will not allow to do the above? Have only one unique relation regardless of the combination.
2) When I'll want to search for all the relations of 'A', how would the SQL query look like?
Note #1: Basically my final goal is to create a "friendship" system, and as far as I understand the solution is a many-to-many table. Suggest otherwise if possible.
Note #2: The users table is on a different database from the relations (call it friendships) table. Therefore I cannot use foreign keys.
For the first question:
Create a unique constraint on both
columns
Make sure you always sort the columns. So if your table has the
colummns a and b than make sure
that a is less than or equal to
b
For the second question:
SELECT
*
FROM
many_to_many_table
WHERE
a = A or b = A
It sounds like you want a composite primary key.
CREATE TABLE relationship (
A_id INTEGER UNSIGNED NOT NULL,
B_id INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (A_id, B_id)
);
This is how you setup a table so that there can only ever be one row that defines tables A and B as related. It works because a primary key has to be unique in a table so therefore the database will allow only one row with any specific pair of values. You can create composite keys that aren't a primary key and they don't have to be unique (but you can create a unique non-primary key, composite or not), but your specification requested a primary key, so that's what I suggested.
You can, of course, add other columns to store information about this specific relationship.
Ok WoLpH was faster, I basically agree (note that you have to create a single constraint on both columns at the same time!). And just to explain why you collide with the rules you mentioned: Typically, A and B are different tables. So the typical example for n:m relations would allow entries (1,0) and (0,1) because they'd be refering to different pairs. Having table A=table B is a different situation (you use A and B as users, but in the example they're tables).