In a multitenant application that uses composite keys
(e.g. SiteId, $(Table)Id)
for everything, I have the following two tables:
Owner (
SiteId bigint NOT NULL,
OwnerId bigint NOT NULL,
ThingId bigint NULL
)
Thing (
SiteId bigint NOT NULL,
ThingId bigint NOT NULL
)
and a foreign key from Owner (SiteId, ThingId) to Thing (SiteId, ThingId).
If I write a method like this:
void RemoveThing(Owner owner)
{
owner.Thing = null;
db.SubmitChanges();
}
I get the exception
System.InvalidOperationException: An attempt was made to remove a relationship between a
Thing and a Owner. However, one of the relationship's foreign keys (Owner.MultiId,
Owner.ThingId) cannot be set to null.
at System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.SynchDependentData()
at System.Data.Linq.ChangeProcessor.ValidateAll(IEnumerable`1 list)
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges()
at proj.Program.RemoveThing() in C:\proj\Program.cs:line 115
To fix this, I could add a second copy of SiteId (perhaps ThingSiteId) to the Owner table. But it's only ever going to be equal to the SiteId, so I'd be spending database size and schema complexity to work around my ORM.
How can I tell LINQ to SQL that it doesn't need to worry about that half of the foreign key relationship, and only set Owner.ThingId=NULL?
Set ThingId = null instead of Thing = null. Don't reuse your DataContext, otherwise you might get a "Foreign key is already set" exception.
LINQ is actively preventing this from being any easier. In ChangeTracker.SynchDependentData(), every column is checked to see if it is part of any associations, and then an exception is thrown if the column was
set to null
the association has ANY non-nullable columns(!)
This is a bug in LINQ-to-SQL, in my opinion.
Related
I want to have an "Entity" and many versions of it, where one of those versions is the only one which is active/used. It is also possible that the Entity is entirely deactive. So I thought of using two tables with cyclic foreign keys like this:
CREATE TABLE entity (
id int NOT NULL AUTO_INCREMENT,
-- some extra irrelevant data commented out
active_version_id int DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE entityversion (
id int NOT NULL AUTO_INCREMENT,
-- some extra irrelevant data commented out
entity_id int NOT NULL,
PRIMARY KEY (id)
);
ALTER TABLE entity ADD FOREIGN KEY (active_version_id) REFERENCES entityversion(id) ON DELETE SET NULL;
ALTER TABLE entityversion ADD FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE;
I would like to, when creating a new active Entity, to create at the same time its first EntityVersion which will be its active_version. The problem is we don't have their ids yet. Currently, we're creating the Entity with "returning id" and using that to create the EntityVersion, also with "returning id", and then updating the active_version_id of that same Entity, so 3 separate commands like this for example:
INSERT INTO entity DEFAULT VALUES RETURNING id;
-- get the ID back and use it as a parameter to the next command
INSERT INTO entityversion (entity_id) VALUES (%s) RETURNING id;
-- again the same thing
UPDATE entity SET active_version_id = %s WHERE id = %s;
I would like to know if there is a shorter way to do this. I also accept as answer a different approach to the table schemas, if it happens to be the better choice. Thanks for the help!
Create both your rows in a stored procedure, or use a before insert trigger if there is no data that only goes in the entityversion version table. To deal with your cyclical id problem, in mariadb use a sequence instead of auto_increment. In mysql, emulate a sequence with an entity_sequence table that only contains the auto_increment id. In your stored procedure/trigger, get the sequence value (with insert..returning id if emulating a sequence), store entityversion using that value, then set the entityversion id to store in your entity row.
You are implying that the entities are 1:1, in which case they may as well be in the same table. (Make one of the NULLable if it is not to inserted until later.)
If it is 1:many (a 'latest' and many 'older' versions), then the FK only goes one way.
In either case, your "circular" FKs go away.
But to answer your question:
Turn off FK checks
CREATE both tables
Populate both tables
ALTER to add both FKs
Turn on FK checks.
More
Well, it seems that you have many:1, not 1:1. The "History" has a column that is the "id" into the "Current" ('active') table. No circular FKs. Index that column so you can go the other way efficiently. ON DELETE CASCADE is not practical in either direction.
The FK should go one direction, not both.
How can I insert new data in column after adding column without using update function. for example
"alter table Employee add column Gender varchar(1) after Birthdate then I get wrong when I used this statement insert into Employee(ENumber,EmpName,Birthdate,Address,Salary,DNumber,Gender)
-> values
-> ('E001','GSInocencio','1988-01-15','Munoz',18000,'D005','F'),
It gives me error Duplicate entry 'E001' for key 'PRIMARY'
MariaDB [Employees_Valdez]>
The messages is pretty clear: You already have an employee with that ENumber value.
You have a UNIQUE constraint on that column, it's a PRIMARY KEY, so either pick a different value, or use a different primary key.
One thing to note is MySQL doesn't use complex string primary keys very efficiently, they're also a real hassle for relating data since they're so big. It's usually better to include a standard id INT AUTO_INCREMENT PRIMARY KEY column and then have things like ENumber being a secondary UNIQUE constraint.
You can then relate data using the 4-byte id value, or 8-byte if BIGINT is a concern like you might have two billion employees.
I have a case that confused and annoyed me for a long time and currently I am struggling whether I should use trigger.
Here I have two table in MYSQL
Create table type(
id int primary key not null auto_increment,
class int not null default 0,
name string not null
)
Create table buddy (
id int primary key not null auto_increment,
value string not null)
Create table value (
id int primary key not null auto_increment,
type_id int not null,
buddy_id int default null,
value string not null,
relied_id int default null
foreign key type_id references type(id)
)
in this case, there are several rules,
Inside value table, some value may refer to the buddy table, while some are not.
if (the buddy_id inside value is not null){
row with this buddy_id should be unique
} else{
could have several instances with the nil buddy_id
}
If the buddy_id is nil, which means it does not refer to anything, it could be duplicate.
In my module, I will receive the message from my colleague's queue and then do the following things:
1), if I detect inside the message, there is a buddy_id, I need to use sql to check whether the value with this buddy_id is already in the database, if yes, just ignore, else should create one.
2), else, directly create a new one
in the first case, my colleague is keep sending me the duplicate message, as after I get "whether the instance with this buddy_id is already inside database" as result as false, it still needs to take sometime to do some logic and create a new instance, then insert into database. There maybe a gap between I got the result and insert.
During this period, another message may come with the same buddy_id and it will trigger another go routine or thread, but the first insert is still not done, in this case, it will also find "instance with this buddy_id is not inside databases, I should create one".
As a result, more than one instance with same buddy_id will be insert into database.
I think this pic could better illustrate what I am talking, two messages with same buddy_id come one by one, but when message 1 with buddy_id = 10 is processing, before inserting, message 2 comes and find there is no buddy_id = 10, so message 2 also creates an instance in database.
Previously, I tried to lock the table, but the project manager asked me to remove the lock as this value table is a main table and he does not want me to lock the database.
I also had a look of the unique constraint but the case for buddy_id is "if it is nil, no unique, if it is not nil, unique". I think this cannot be done just using the unique key constraint.
So anyone has idea whether I should use trigger to do the check before insert to make sure the instance is not duplicate? Or any other recommended methods?
Thanks.
I may be missing something simple here but I can't seem to find an answer.
I have two entities that are inherited from another and I want to enforce that the child entities cannot have the same key as each other in the database rather than with queries.
For example I want credit card or paypal to have the same primary key as payment, but I only want that to be in either credit card or paypal, not both. So a payment is a credit card or a paypal but not both.
I though it would be simple constraint, something like:
check (cc.transaction != pp.transaction)
But this doesn't seem to work in mysql workbench. Any ideas?
I have found out that in fact checks don't work at all in mySQL, and so I need to use a trigger, but there it no way to just cancel the insert so it has to throw an error to exit instead. I am using mySQL Workbench which supports triggers but it will not accept any I try, eg:
CREATE DEFINER = CURRENT_USER TRIGGER `ddi`.`tcredit_card_BEFORE_INSERT`
BEFORE INSERT ON `tcredit_card` FOR EACH ROW
BEGIN
IF EXISTS (SELECT Paypal_ID FROM tPaypal WHERE Paypal_ID = NEW.Card_ID) THEN
SIGNAL SQLSTATE '02000' SET MESSAGE_TEXT = 'Cannot have multiple payments.';
END IF;
END;
It always says there's and error no matter what I do, including attempting to change the delimiter which also come up as an error. Now short of smashing the spinning jenny and using paper records and a horse and cart surely there must be a way for this to work? (without changing the db schema)
You can have a super-type and sub-types arrangement in the database and it's really easy to keep them separate. Just declare the subtype as part of the super key then enforce type values in the sub-tables.
Whenever possible, let the constraints and checks built into the underlying system enforce whatever design you implement.
create table Super(
ID int not null auto_increment,
SubType char( 1 ) not null check( SubType in( 'A', 'B' ) ),
...
constraint PK_Super primary key( ID, SubType )
);
create table subA(
SubAID int not null,
SubType char( 1 ) not null default 'A' check( SubType = 'A' ),
... -- data specific to Type = A
constraint PK_SubA primary key( SubAID, SubType ),
constraint FK_SubA_Super foreign key( SubAID, SubType )
references Super( ID, SubType )
);
create table subB(
SubBID int not null,
SubType char( 1 ) not null default 'B' check( SubType = 'B' ),
... -- data specific to Type = B
constraint PK_SubB primary key( SubBID, SubType ),
constraint FK_SubB_Super foreign key( SubBID, SubType )
references Super( ID, SubType )
);
It may seem redundant to make Super.SubType part of the PK since Super.ID is a surrogate key all by itself, but look at what you get.
The Super table contains the data common to all subtypes (transaction date, type (credit/debit), amount, etc.).
With the proper constraint (you could use another table with defined subtypes) no entry is possible in Super that is not a properly defined subtype.
The subtype value tells you which sub-table contains the type-specific data.
No entry can be made in a sub-table that is not first made in the super table. And, once defined, the subtype cannot be changed -- entries in A can have nothing but 'A' in SubType field. Nor, once a FK connection is made, can an 'A' entry in Super be changed to 'B' or vice versa.
If you want to enforce this logic in the database, you might need another table.
How about this, if I've understood your problem:
payment(paymentID, date, amount)
paymentType(paymentTypeID,
paymentID)
creditCardPaymentType(creditCardPaymentTypeID, paymentTypeID, cc#,
CVV2, expiration)
paypalPaymentType(paypalPaymentTypeID,
paymentTypeID, paypalname, paypalotherinfowhatever)
And then make paymentType(paymentID) unique.
But it could be more performant to enforce the logic in the application.
I am using a MySQL InnoDB database and have many tables in it. What I want to be able to do is enforce (from within the database) a constraint such that a key may exist in one of two columns (in two separate tables) but not both. I'll try to make this more clear.
Say I have two tables, TableA and TableB. Both of these tables have many columns, but they have one column in common, called SpecialID (int 255).
Now, both of these tables have many rows, and from the PHP side of the web app, the SpecialID column in TableA should never contain an integer that is in the SpecialID column of TableB, and the same goes the other way around. In other words, an integer should never be able to be found in the SpecialID column of TableA and TableB at any one time.
I'm fairly confident that I've enforced this from the PHP side, however I want to be able to enforce this relationship from within the database, just to be extra careful, as if I ever ended up with the same value in both tables, it would be catastrophic.
This may not even be possible, but I thought I'd throw it out there cos it seems like it could be. It would be sort of like a "foreign uniqueness constraint". I have done a bit of research but haven't turned up anything at all, not even people asking for something like this, so perhaps I could just be searching for the wrong thing?
Here's a solution:
CREATE TABLE Specials (
specialid INT AUTO_INCREMENT PRIMARY KEY,
type CHAR(1) NOT NULL,
UNIQUE KEY (id, type)
);
CREATE TABLE TableA (
id INT AUTO_INCREMENT PRIMARY KEY,
specialid INT NOT NULL,
type CHAR(1) NOT NULL DEFAULT 'A',
FOREIGN KEY (specialid, type) REFRENCES Specials(specialid, type)
);
CREATE TABLE TableB (
id INT AUTO_INCREMENT PRIMARY KEY,
specialid INT NOT NULL,
type CHAR(1) NOT NULL DEFAULT 'B',
FOREIGN KEY (specialid, type) REFRENCES Specials(specialid, type)
);
Now you need to make sure TableA.type is always 'A' and TableB.type is always 'B'. You can do this with a trigger, or else a foreign key to a lookup table of one row for each case.
The result is that Specials.type can be any letter, but only one letter for a given specialid. The rows in TableA and TableB can reference only a specialid with a type that matches their own type. This means that any given specialid can be referenced by only one table or the other, but never both.
From what I've found, there is no easy way to do this. As I cannot find anything and no one has provided a solution to the issue, I will assume that it is in fact not possible (at least, not very easily).
For my own project, I ended up going along a different route to achieve my goal.
The discussion at this SO question may be of use for anyone searching for something along these lines:
Enforce unique values across two tables
I have not tried it myself, though.
If I ever come across anything better (or someone posts a better answer here) then I shall update my response and/or mark someone else's answer as correct as necessary.