Is it fine to have one table with foreign keys to many other tables? - sql

Let's say that I have 30 tables in my database which all have a one-to-many relationship to a Notations table:
What is another way to set up the Notations table (other than containing a foreign key column for each of its "parents") that would be less taxing to the query's performance? The table is expected to grow to be very large.
(As each Notation contains a single parent_table_id with 29 null values for the remaining parent_tables - that is 29 nulls to track for every row, it seems like a lot)
ie.
SELECT text, table1_id, table2_id, ..., table30_id FROM Notations would show:
id text table1_id table2_id // table30_id
0 "buzz" 74 null // null
1 "foo" null 45 // null
2 "bar" 22 null // null
3 "fizz" 22 null // null
4 "hello" 28 null // null
5 "world" null null // 3
...etc

You are correct that storing 30 columns where one is not NULL is inefficient -- those NULL values typically occupy space in each row.
Assuming all the ids are the same type, you can simplify the structure to use:
table_name
table_id
Just two columns. Unfortunately, I am not aware of any database that allows "conditional" foreign key relationships. Although I advocate defining foreign key relationships this might be a case where you choose not to have them.
That is not fully satisfying. Although you can ensure some integrity using triggers, this lacks the "cascading" features of foreign keys. You can implement that with more triggers. Yuck!
One alternative is a separate notations table for each table:
notations_table1
text, id
notations_table2
text, id
. . .
You can then bring these together using union all:
create view notations as (
select text, id, 'table1' as table_name from notaions_table1 union all
select text, id, 'table2' as table_name from notaions_table2 union all
. . .
The underlying tables can then have properly formatted foreign key constraints -- and even cascading ones. Unfortunately, this lacks a single column as an id. The "real" id is a combination of table_name and id.
Some databases have other mechanisms that might be able to help. For instance, Postgres supports a form of inheritance that might be useful in this context.

You shouldn't have a notations table
The point of a primary-foreign key pair is that the value of the foreign key is the same as the value of the primary. A notations table does not make sense. Normalisation is an extremely important aspects of databases. We want to reduce redundant and replicated data. We don't want repetition! A notations table would introduce redundant data. In this case redundant data can refer to data we can calculate with existing data. All tables to be normalised must only contain data that relates to itself and not be calculable.
We can calculate each link due to check if foreign key field are filled and the key exists in the related table. Hence these links do not suit being in a separate table. As you mention this would be an enormous table and that's for the very reason it is only holding redundant data. There is no need for a notations table. Rather if you need to find links then just write a query to check for matches between primary and foreign keys. That is the reason why databases use foreign keys.
A notations table is only appropriate if we did not have any foreign keys. We would then require a table to tell us how each record relates to another record in a different table. But since you do have foreign keys then this table is causing repeated and redundant data.

Related

Using LIKE queries, Foreign Keys or jsonb arrays for filtering Postgres SELECT

I have a table, which suppose to contain relations to other tables. But I need these relations only for filtering, i.e. the relations in this case are not suppose to work as foreign key or some kind of references - I just need to search through them. It works like tags or something.
Here is the example:
CREATE TABLE "public"."table_name"
(
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"relations" text NOT NULL,
"some_column" text,
"some_another_column" int4,
"created" timestamp(6) WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "table_name_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "owner" FOREIGN KEY ("owner") REFERENCES "public"."user" ("id") ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE,
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."table_name" OWNER TO "postgres";
the relations column will contain multiple uuid keys. This column is not suppose to be SELECTed, I intend to use it only for filtering. In this case I intend to use this kind of queries to select rows from only this table:
SELECT
id, some_column, some_another_column
FROM
table_name
WHERE
relations LIKE '%c56c8a4f-765a-4e1c-9638-f3736a25da17%'
AND owner = 'badee659-1fca-412a-bcf6-c73ecf1e65aa';
Of course, I will create a multicolumn (owner,relations) index.
Is this a good approach to perform this kind of search queries? relations column will contain 1 to 10 uuids per each row on the average.
Or, maybe I should create additional table, which will contain, say, one uuid for each 'relation' and FK for referencing to the table_name table? In this case I will use JOIN queries.
Or may be there are better ways? May be I should store uuids as array within jsonb object? Or something else?
Index consideration
Using operator LIKE with leading % is not sargable. It won't use your index, because the optimizer doesn't know how to narrow first characters from relations column.
Design
It's almost always a bad idea to store different values in one column as a string with delimiter.
Remember that relational databases are designed for performing JOIN operations efficiently. In my opinion it would be better to separate that data into rows with atomic values in their columns.
Json consideration
json and jsonb datatypes should only be taken under consideration if your columns are unpredictably changing. By saying this I mean that whenever you can (without much overhead going on) fit your model into relational one, you should always go for it. The same goes for hstore.
You could read this blog post to grab some information to start with when considering using a mechanism for storing dynamic columns.
A little quote from that post:
How to decide when to use json
Use json if your data won’t fit in the database using a normal
relational modelling.
Credit for above blogpost goes to #Craig Ringer.

When should foreign keys be used? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I'm just starting to learn SQL (using SQLite) and I'm trying to figure out when foreign keys should be used. The way it was explained to me, was that foreign keys should be used any time repeated data comes up and just save IDs to save space. The database I'm making has a few thousand records in it, with categories and counties listed (probably a few dozen uniques in each column). So I can make a separate table for counties with county name and a primary key id, and do the same thing with categories. And I have no doubt that it would make the database about 5% smaller. But is that the only benefit? It seems like it's making everything else more complex. Adding in IDs for counties and categories which wouldn't otherwise be needed. When looking at the table in phpLiteAdmin, it just shows you a number instead of the category/county name, making it more difficult to visualize. What are the advantages of using foreign keys and making separate tables in this situation? Or should I just not do that and stick with all the data (repetition and all) in one table? Also- would it make sense at all to make the counties/categories tables just one column with no numeric primary key, since they'll all be uniques anyway? That would at least show the full names in phpLiteAdmin. Thanks in advance!
If you are using foreign key. its also called as referential integrity.
Suppose you have two table first table is account_user and second is account_user_detail.
so account_user table will have primary key of account_number of account_id. and account_user_detail table will have accounts holder address detail.
so if you are relating both the tables then account_number or account_id will be same.
so using value of primary key in second table we define foreign key.
foreign key identifies that value of account_number in second table is reference of Mr. Xyz in first table with same account number.
So Foreign key is used for joining two table with a column that is common to both tables and share same unique value.
You may check this:
SQL foreign key constraints are used to enforce "exists" relationships
between tables.
EDIT:-
The foreign key constraints exist is to guarantee that the referenced rows exist.
Also the wiki says:-
One important part of database design is making sure that
relationships between real-world entities are reflected in the
database by references, using foreign keys to refer from one table to
another.[9] Another important part of database design is database
normalization, in which tables are broken apart and foreign keys make
it possible for them to be reconstructed.
Also check this Thread.
Why are foreign keys more used in theory than in practice?
If your country name is "United States of America" this is 24bytes. If you use a foriegn key you will need only 2-4 bytes. Thats a huge difference.
When you are searching for a country name it is going to be very fast because you only have to match a number and not the whole string.
Also if you use an index on the country_id field it is going to much smaller.
I can understand you point about the added complexity. In your case you can get away with not using foreign keys but you shouldn't. You will eventually need them so better be prepared and experienced on the subject.
But is that the only benefit?
No.
Foreign keys are logically similar to pointers or references in most programming languages. Imagine trying to make some data structure by just copying data, without being able to reference anything. A database without foreign keys would be similarly problematic.
Without the ability to reference things, you'd have to make sure all the copies are kept up-to date. If there is a bug that leads to one copy being updated but not the other, that would effectively corrupt the data - you'd no longer know which copy is correct.
Avoiding redundancies is not primarily about space, it's about data integrity. The whole purpose of database normalization (which couldn't be done without foreign keys) is the avoidance of redundancies and therefore the protection the data integrity.
In your particular case...
Should a category (or country) be able to exist without being connected to any row from the main table?
Is there any data that should exist for a category, independently from which rows in the main table this category is connected to?
Is there any operation (like rename) that should be done independently?
If either answer is "yes", you should put categories into a separate lookup table. Whether this lookup table should use natural (name) or surrogate (ID) key is a different issue. Some pros and cons are listed here.
Foreign key constraints are used to restrict the values that are allowed to exist in a column or set of columns. For example, take marriages:
CREATE TABLE person
(person_id INTEGER NOT NULL PRIMARY KEY
, name varchar NOT NULL
);
CREATE TABLE marriage
( person1 INTEGER NOT NULL PRIMARY KEY
, person2 INTEGER NOT NULL UNIQUE
, comment varchar
, CONSTRAINT marriage_1 FOREIGN KEY (person1) REFERENCES person(person_id)
, CONSTRAINT marriage_2 FOREIGN KEY (person2) REFERENCES person(person_id)
, CONSTRAINT order_in_court CHECK (person1 < person2)
);
-- add some data ...
INSERT INTO person(person_id,name) values (1,'Bob'),(2,'Alice'),(3,'Charles');
INSERT INTO marriage(person1,person2, comment) VALUES(1,2, 'Crypto marriage!') ; -- Ok
INSERT INTO marriage(person1,person2, comment) VALUES(2,1, 'Not twice!' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(3,3, 'No you dont...' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(2,3, 'OMG she did it again.' ) ; -- Should fail (does not)
INSERT INTO marriage(person1,person2, comment) VALUES(3,4, 'Non existant persons are not allowed to marry !' ) ; -- Should fail
SELECT p1.name, p2.name, m.comment
FROM marriage m
JOIN person p1 ON m.person1 = p1.person_id
JOIN person p2 ON m.person2 = p2.person_id
;
The above DDL tries to model marriages (and partly fails) The constraints to be modelled are:
only existing persons can be married
marriages can only exist between two different persons
A person can only be married once
The output:
INSERT 0 3
INSERT 0 1
ERROR: new row for relation "marriage" violates check constraint "order_in_court"
ERROR: new row for relation "marriage" violates check constraint "order_in_court"
INSERT 0 1
ERROR: insert or update on table "marriage" violates foreign key constraint "marriage_2"
DETAIL: Key (person2)=(4) is not present in table "person".
name | name | comment
-------+---------+-----------------------
Bob | Alice | Crypto marriage!
Alice | Charles | OMG she did it again.
(2 rows)

Performance - Int vs Char(3)

I have a table and am debating between 2 different ways to store information. It has a structure like so
int id
int FK_id
varchar(50) info1
varchar(50) info2
varchar(50) info3
int forTable or char(3) forTable
The FK_id can be a foreign key to one of 6 tables so I need another field to determine which table it's for.
I see two solutions:
An integer that is a FK to a settings table which has its actual value.
A char(3) field with the a abbreviated version of the table.
I am wondering if anyone knows if one will be more beneficial speed wise over the other or if there will be any major problems using the char(3)
Note: I will be creating an indexed view on each of the 6 different values for this field. This table will contain ~30k rows and will need to be joined with much larger tables
In this case, it probably doesn't matter except for the collation overhead (A vs a vs ä va à)
I'd use char(3), say for currency code like CHF, GBP etc But if my natural key was "Swiss Franc", "British Pound" etc, I'd take the numeric.
3 bytes + collation vs 4 bytes numeric? You'd need a zillion rows or be running a medium sized country before it mattered...
Have you considered using a TinyInt. Only takes one byte to store it's value. TinyInt has a range of values between 0 and 255.
Is the reason you need a single table that you want to ensure that when the six parent tables reference a given instance of a child row that is guaranteed to be the same instance? This is the classic "multi-parent" problem. An example of where you might run into this is with addresses or phone numbers with multiple person/contact tables.
I can think of a couple of options:
Choice 1: A link table for each parent table. This would be the Hoyle architecture. So, something like:
Create Table MyTable(
id int not null Primary Key Clustered
, info1 varchar(50) null
, info2 varchar(50) null
, info3 varchar(50) null
)
Create Table LinkTable1(
MyTableId int not null
, ParentTable1Id int not null
, Constraint PK_LinkTable1 Primary Key Clustered( MyTableId, ParentTable1Id )
, Constraint FK_LinkTable1_ParentTable1
Foreign Key ( MyTableId )
References MyTable ( Id )
, Constraint FK_LinkTable1_ParentTable1
Foreign Key ( ParentTable1Id )
References ParentTable1 ( Id )
)
...
Create Table LinkTable2...LinkTable3
Choice 2. If you knew that you would never have more than say six tables and were willing to accept some denormalization and a fugly design, you could add six foreign keys to your main table. That avoids the problem of populating a bunch of link tables and ensures proper referential integrity. However, that design can quickly get out of hand if the number of parents grows.
If you are content with your existing design, then with respect to the field size, I would use the full table name. Frankly, the difference in performance between a char(3) and a varchar(50) or even varchar(128) will be negligible for the amount of data you are likely to put in the table. If you really thought you were going to have millions of rows, then I would strongly consider the option of linking tables.
If you wanted to stay with your design and wanted the maximum performance, then I would use a tinyint with a foreign key to a table that contained the list of the six tables with a tinyint primary key. That prevents the number from being "magic" and ensures that you narrow down the list of parent tables. Of course, it still does not prevent orphaned records. In this design, you have to use triggers to do that.
Because your FK cannot be enforced (since it is a variant depending upon type) by database constraint, I would strongly consider re-evaluating your design to use link tables, where each link table includes two FK columns, one to the PK of the entity and one to the PK of one of the 6 tables.
While this might seem to be overkill, it makes a lot of things simpler and adding new link tables is no more complex than accommodating new FK-types. In addition, it is more easily expandable to the case where an entity needs more than a 1-1 relationship to a single table, or needs multiple 1-1 relationships to the 6 other entities.
In a varying-FK scenario, you can lose database consistency, you can join to the wrong entity by neglecting to filter on type code, etc.
I should add that another huge benefit of link tables is that you can link to tables which have keys of varying data types (ints, natural keys, etc) without having to add surrograte keys or stored the key in a varchar or similar workarounds which are prone to problems.
I think a small integer (tinyint) is called for here. An "abbreviated version" looks too much like a magic number.
I also think performance wise the integer should beat the char(3).
First off, a 50 character Id that is not globally unique sounds a little scary. Do the IDs have some meaning? If not, you can easily get a GUID in less space. Personally, I am a big fan of making things human readable whenever possible. I would, and have, put the full name in graphs until I needed to do otherwise. My preference would be to have linking tables for each possible related table though.
Unless you are talking about really large scale, you are much better off decreasing the size of the IDs and taking a few more characters for the name of the table. For really large scale, I would decrease the size of the IDs and use an integer.
Jacob

Can I have 2 unique columns in the same table?

I have 2 tables:
roomtypes[id(PK),name,maxAdults...]
features(example: Internet in room, satelite tv)
Can both id and name field be unique in the same table in mysql MYISAM?
If the above is posible, I am thinking of changing the table to:
features[id(PK),name,roomtypeID] ==> features[id(PK),name,roomtypeNAME]
...because it is helping me not to do extra querying in presentation for features because the front end users can't handle with IDs.
Of course, you can make one of them PRIMARY and one UNIQUE. Or both UNIQUE. Or one PRIMARY and four UNIQUEs, if you like
Yes, you can define UNIQUE constraints to columns other than the primary key in order to ensure the data is unique between rows. This means that the value can only exist in that column once - any attempts to add duplicates will result in a unique constraint violation error.
I am thinking of changing the FEATURES table to features[id(PK), name, roomtypeNAME] because it is helping me not to do extra querying in presentation for features because the front end users can't handle with IDs.
There's two problems:
A unique constraint on the ROOM_TYPE_NAME wouldn't work - you'll have multiple instances of a given room type, and a unique constraint is designed to stop that.
Because of not using a foreign key to the ROOM_TYPES table, you risk getting values like "Double", "double", "dOUBle"
I recommend sticking with your original design for sake of your data; your application is what translates a room type into its respective ROOM_TYPE record while the UI makes it presentable.
I would hope so otherwise MySQL is not compliant with the SQL standard. You can only have one primary key but you can mark other columns as unique.
In SQL, this is achieved with:
create table tbl (
colpk char(10) primary key,
coluniq char(10) unique,
colother char(10)
);
There are other ways to do it (particularly with multi-part keys) but this is a simple solution.
Yes you can.
Also keep in mind that MySQL allow NULL values in unique columns, whereas a column that is a primary key cannot have a NULL value.
1 RoomType may have many Features
1 Feature may be assigned to many RoomTypes
So what type of relationship do i have? M:N ?
You have there a many-to-many relationship, which has to be represented by an extra table.
That relationship table will have 2 fields: the PK of RoomTypes and the PK of Features.
The PK of the relationship table will be made of those 2 fields.
If that's usefull, you can add extra fields like the Quantity.
I would like to encourage you to read about database Normalization, which is he process of creating a correct design for a relational database. You can Google for that, or look eventually here (there are plenty of books/web pages on this)
Thanks again for very helpful answers.
1 roomType may have many features
1 feature may be assigned to many roomTypes
So what type of relationship do i have? M:N ?
If yes the solution I see is changing table structure to
roomTypes[id,...,featuresIDs]
features[id(PK),name,roomtypeIDs] multiple roomTypesIDs separated with comma?

How do I implement this multi-table database design/constraint, normalized?

I have data that kinda looks like this...
Elements
Class | Synthetic ID (pk)
A | 2
A | 3
B | 4
B | 5
C | 6
C | 7
Elements_Xref
ID (pk) | Synthetic ID | Real ID (fk)
. | 2 | 77-8F <--- A class
. | 3 | 30-7D <--- A class
. | 6 | 21-2A <--- C class
. | 7 | 30-7D <--- C class
So I have these elements that are assigned synthetic IDs and are grouped into classes. But these synthetic IDs are then paired with Real IDs that we actually care about. There is also a constraint that a Real ID cannot recur in a single class. How can I capture all of this in one coherent design?
I don't want to jam the Real ID into the upper table because
It is nullable (there are periods where we don't know what the Real ID of something should be).
It's a foreign key to more data.
Obviously this could be done with triggers acting as constraints, but I'm wondering if this could be implemented with regular constraints/unique indexes. Using SQL Server 2005.
I've thought about having two main tables SyntheticByClass and RealByClass and then putting IDs of those tables into another xref/link table, but that still doesn't guarantee that the classes of both elements match. Also solvable via trigger.
Edit: This is keyword stuffing but I think it has to do with normalization.
Edit^2: As indicated in the comments below, I seem to have implied that foreign keys cannot be nullable. Which is false, they can! But what cannot be done is setting a unique index on fields where NULLs repeat. Although unique indexes support NULL values, they cannot constraint more than one NULL in a set. Since the Real ID assignment is initially sparse, multiple NULL Real IDs per class is more than likely.
Edit^3: Dropped the redundant Elements.ID column.
Edit^4: General observations. There seems to be three major approaches at work, one of which I already mentioned.
Triggers. Use a trigger as a constraint to break any data operations that would corrupt the integrity of the data.
Index a view that joins the tables. Fantastic, I had no idea you could do that with views and indexes.
Create a multi-column foreign key. Didn't think of doing this, didn't know it was possible. Add the Class field to the Xref table. Create a UNIQUE constraint on (Class + Real ID) and a foreign key constraint on (Class + Synthetic ID) back to the Elements table.
Comments from before the question was made into a 'bonus' question
What you'd like to be able to do is express that the join of Elements and Elements_Xref has a unique constraint on Class and Real ID. If you had a DBMS that supported SQL-92 ASSERTION constraints, you could do it.
AFAIK, no DBMS supports them, so you are stuck with using triggers.
It seems odd that the design does not constrain Real ID to be unique across classes; from the discussion, it seems that a given Real ID could be part of several different classes. Were the Real ID 'unique unless null', then you would be able to enforce the uniqueness more easily, if the DBMS supported the 'unique unless null' concept (most don't; I believe there is one that does, but I forget which it is).
Comments before edits made 2010-02-08
The question rules out 'jamming' the Real_ID in the upper table (Elements); it doesn't rule out including the Class in the lower table (Elements_Xref), which then allows you to create a unique index on Class and Real_ID in Elements_Xref, achieving (I believe) the required result.
It isn't clear from the sample data whether the synthetic ID in the Elements table is unique or whether it can repeat with different classes (or, indeed whether a synthetic ID can be repeated in a single class). Given that there seems to be an ID column (which presumably is unique) as well as the Synthetic ID column, it seems reasonable to suppose that sometimes the synthetic ID repeats - otherwise there are two unique columns in the table for no very good reason. For the most part, it doesn't matter - but it does affect the uniqueness constraint if the class is copied to the Elements_Xref table. One more possibility; maybe the Class is not needed in the Elements table at all; it should live only in the Elements_Xref table. We don't have enough information to tell whether this is a possibility.
Comments for changes made 2010-02-08
Now that the Elements table has the Synthetic ID as the primary key, things are somewhat easier. There's a comment that the 'Class' information actually is a 'month', but I'll try to ignore that.
In the Elements_Xref table, we have an unique ID column, and then a Synthetic ID (which is not marked as a foreign key to Elements, but presumably must actually be one), and the Real ID. We can see from the sample data that more than one Synthetic ID can map to a given Real ID. It is not clear why the Elements_Xref table has both the ID column and the Synthetic ID column.
We do not know whether a single Synthetic ID can only map to a single Real ID or whether it can map to several Real ID values.
Since the Synthetic ID is the primary key of Elements, we know that a single Synthetic ID corresponds to a single Class.
We don't know whether the mapping of Synthetic ID to Real ID varies over time (it might as Class is date-related), and whether the old state has to be remembered.
We can assume that the tables are reduced to the bare minimum and that there are other columns in each table, the contents of which are not directly material to the question.
The problem states that the Real ID is a foreign key to other data and can be NULL.
I can't see a perfectly non-redundant design that works.
I think that the Elements_Xref table should contain:
Synthetic ID
Class
Real ID
with (Synthetic ID, Class) as a 'foreign key' referencing Elements, and a NOT NULL constraint on Real ID, and a unique constraint on (Class, Real ID).
The Elements_Xref table only contains rows for which the Real ID is known - and correctly enforces the uniqueness constraint that is needed.
The weird bit is that the (Synthetic ID, Class) data in Elements_Xref must match the same columns in Elements, even though the Synthetic ID is the primary key of Elements.
In IBM Informix Dynamic Server, you can achieve this:
CREATE TABLE elements
(
class CHAR(1) NOT NULL,
synthetic_id SERIAL NOT NULL PRIMARY KEY,
UNIQUE(class, synthetic_id)
);
CREATE TABLE elements_xref
(
class CHAR(1) NOT NULL,
synthetic_id INTEGER NOT NULL REFERENCES elements(synthetic_id),
FOREIGN KEY (class, synthetic_id) REFERENCES elements(class, synthetic_id),
real_id CHAR(5) NOT NULL,
PRIMARY KEY (class, real_id)
);
I would:
Create a UNIQUE constraint on Elements(Synthetic ID, Class)
Add Class column to Elements_Xref
Add a FOREIGN KEY constraint on Elements_Xref table, referring to (Synthetic ID, Class)
At this point we know for sure that Elements_Xref.Class always matches Elements.Class.
Now we need to implement "unique when not null" logic. Follow the link and scroll to section "Use Computed Columns to Implement Complex Business Rules":
Indexes on Computed Columns: Speed Up Queries, Add Business Rules
Alternatively, you can create an indexed view on (Class, RealID) with WHERE RealID IS NOT NULL in its WHERE clause - that will also enforce "unique when not null" logic.
Create an indexed view for Elements_Xref with Where Real_Id Is Not Null and then create a unique index on that view
Create View Elements_Xref_View With SchemaBinding As
Select Elements.Class, Elements_Xref.Real_Id
From Elements_Xref
Inner Join Element On Elements.Synthetic_Id = Elements_Xref.Synthetic_Id
Where Real_Id Is Not Null
Go
Create Unique Clustered Index Elements_Xref_Unique_Index
On Elements_Xref_View (Class, Real_Id)
Go
This serves no other purpose other than simulating a unique index that treats nulls properly i.e. null != null
You can
Create a view from the the result set of joining Elements_Xref and Elements together on Synthetic ID
add a unique constraint on class, and [Real ID]. In other news, this is also how you do functional indexes in MSSQL, by indexing views.
Here is some sql:
CREATE VIEW unique_const_view AS
SELECT e.[Synthetic ID], e.Class, x.[Real ID]
FROM Elements AS e
JOIN [Elements_Xref] AS x
ON e.[Synthetic ID] = x.[Synthetic ID]
CREATE UNIQUE INDEX unique_const_view_index ON unique_const_view ( Class, [Real ID] );
Now, apparently, unbeknownst to myself this solution doesn't work in Microsoft-land-place because with MS SQL Server duplicate nulls will violate a UNIQUE constraint: this is against the SQL spec. This is where the problem is discussed about.
This is the Microsoft workaround:
create unique nonclustered index idx on dbo.DimCustomer(emailAddress)
where EmailAddress is not null;
Not sure if that is 2005, or just 2008.
I think a trigger is your best option. Constraints can't cross to other tables to get information. Same thing with a unique index (although I suppose a materialized view with an index might be possible), they are unique within the table. When you put the trigger together, remember to do it in a set-based fashion not row-by-row and test with a multi-row insert and multi-row update where the real key is repeated in the dataset.
I don't think either of your two reasons are an obstacle to putting Real ID in Elements. If a given element has 0 or 1 Real IDs (but never more than 1), it should absolutely be in the Elements table. This would then allow you to constrain uniqueness within Class (I think).
Could you expand on your two reasons not to do this?
Create a new table real_elements with fields Real ID, Class and Synthetic ID with a primary key of Class, RealId and add elements when you actually add a RealID
This constrains Real IDs to be unique for a class and gives you a way to match a class and real ID to the synthetic ID
As for Real ID being a foreign key do you mean that if it is in two classes then the data keyed off it will be the same. If so the add another table with key Real Id. This key is then a foreign key into real_elements and any other table needing real ID as foreign key