views:

47

answers:

2

Hi, I'm pretty new to databases and sql. I have a problem where I have two tables which both contain a foreign key to the primary key of the other. My problem is I have a large number of elements which can have multiple names in different languages, but MUST have a single primary name/language. alt text

Firstly, I want to know if this is possible, or should I just give up already? The obvious solution is to have an extra boolean field in the ElementName table to say IsDefaultName, but it adds some extra complexity for querying and updating. If this is the best solution, how do I constrain the ElementName table to not accept any submission if IsDefaultName is set and the table already has an entry with the same ElementId and IsDefaultName set (or would I need to query this manually)?

I'm attempting to use LINQ to SQL here. The code I'm using to attempt to insert new items throws an exception at SubmitChanges with The INSERT statement conflicted with the FOREIGN KEY constraint "FK_ElementName_Element". I can understand why this is, but wondering if there's a fix/better solution.

var db = new MyDBDataContext();
var element = new Element();
var elementName = new ElementName() {
    ElementName1 = "MyElement",
    Language = "English",
};
element.ElementName = elementName;

db.Elements.InsertOnSubmit(element);
db.ElementNames.InsertOnSubmit(elementName);
db.SubmitChanges();
+2  A: 

Why not simply use a self-join like so:

Create Table Elements( 
                        ElementId... Not Null Primary Key
                        , DefaultElementId ... Not Null 
                             References Elements( ElementId )
                        , Name ...
                        , Language ...
                        )

The default name is the one where ElementId = DefaultElementId.

Btw, this is a place where a guid PK is nicer than an identity column. With a guid PK, you could generate both the ElementId and DefaultElementId from the client. If you are using an identity column with this schema, you'll probably have to create a "Unknown" elementId with a known value like zero so that you can do the insert and then turn around and do an update all in a single transaction.

** ADDITION **

Given what you said in comments, it sounds like you are trying to localize the elements data. My inclination would be to recommend adding a non-nullable "Name" column to the Elements table which represents the language neutral or default language name. Your ElementNames table would have a foreign key to the Elements table and would only populate that table when you localized an element name. Your queries would then need to coalesce on the requested language name and the name in the elements table if the requested language did not have a localized name.

Thomas
I'm not sure I understand this solution. DefaultElementId is an attribute of ElementName not Element. Are you proposing merging the 2 tables into one? If so the PK won't work as is.
Martin Smith
@Martin Smith - Yes, I'm proposing that the OP merge them into a single table and yes it will work depending on how the PK is generated. If he uses guid PKs, it's a snap. If he uses an identity column, it is more challenging.
Thomas
@Thomas but he needs a way of grouping together the other elements that belong together. As I understood it there is a one-to-many relationship between Element and ElementName.
Martin Smith
@Thomas - Thanks for the feedback. Merging into one table isn't a viable solution. My real tables contain much more data than this. I just created a reduced version to demonstrate the problem. Each element contains a lot of misc data, to merge the names into the same table would mean those extra fields are duplicated for every new name entry.
Mark H
@Martin Smith - `Select .. From Elements Where DefaultElementId = <value>`
Thomas
@Thomas - Bingo! I've got it now. Thanks for the explanation! (+1)
Martin Smith
+2  A: 

Solution 1

element
------------------
element_id
~....

element_name
------------------
element_name_id
fk_element_id
name
language_id
is_default_name  Default ( 0 )

Trigger:
if ( ( select count ( 1 ) from element_name where is_default_name = 1 ) > 1 )
BEGIN
    raisError ( 'only 1 element_name may be marked is_default_name = true.', 16, 1 );
END

Solution 2

element
------------------
element_id ( pk )
~....

element_name
------------------
element_name_id ( pk )
fk_element_id
name
language_id

element_name_default
------------------
fk_element_id
fk_element_name_id
( pk - fk_element_id, fk_element_name_id )

Solution 3

element
------------------
element_id
fk_element_name_id_default NULL
~....

element_name
------------------
element_name_id
fk_element_id
name
language_id

order of code:
* Insert to element_name
* update of element

I would stick with what you had, cause it is just fine, just:

db.Elements.InsertOnSubmit(element);
db.ElementNames.InsertOnSubmit(elementName);
//I don't know this syntax to say
//  set the property of element.fk_element_name_id_default 
//  to the newly inserted elementName from above
db.Elements.?.?
MaasSql
Thanks. I think I'll probably adopt Solution 2 if nothing else comes along. It'll probably work OK for my problem. Solution 3 is too much mess - I can't insert an eElement without giving it a valid ElementName and vice versa - it'd mean turning off an FK constraint before updating.
Mark H
* It would not mean turning off a foreign key constraint. Foreign key constraints do not affect whether a column is nullable or not.* You really can insert an element w/o giving it a default element name. If you don't want an element to exist w/o a default element name, you would wrap the entire initial operation ( new element, new element name, set element.defaultname ) in a transaction.* Solution 2 is just as likely as Solution 3 to have you end up w/o a default name. In fact b/c it is removed from the element table, it is more likely.
MaasSql
For solution 3, I would make my 'select default name code' say - if fk_element_name_id_default is null, then my default element_name is the first element_name in the element_name table.
MaasSql
Ok. I've taken Solution 2, it's not exactly what I wanted, but good enough. After a little more searching, I've found that what I wanted is available in oracle by setting FKs to DEFERRABLE - that way, multiple inserts can be handled and the FKs are only checked on the commit. I've looked around, but can't find the equivalent in SQL Server though, and I'm doubtful LINQ to SQL will handle it anyway, so I'll take the easier solution.
Mark H