How to Create a real one-to-one relationship in SQL Server
Asked Answered
A

8

79

I have two tables Country and Capital, I set Capital's primary key as foreign key which references Country's primary. But when I use Entity Framework database-first, the model is 1 to 0..1.

How does one create a one-to-one relationship in SQL Server?

enter image description here

Annabell answered 24/4, 2012 at 5:46 Comment(7)
In practice. Who needs a "real" one-to-one relationship? a relationship of type 1 to 0..1 is Ok and more natural and practical. It's like adding a nullable column to an existing table. Just make a foreign key unique.Edgy
Ask yourself «which table do I want to add records to first?» and then add the FK to the other.Chemo
@ShadiNamrouti Outside of data-modelling contrivances, a common practical scenario is when a single table representing a single entity (with thousands of NOT NULL, absolutely-required) columns needs more columns added but it's too wide for the RDBMS (e.g. SQL Server's 8KB row length limit). If we have TABLE People (with PersonId PRIMARY KEY) then we can add a new table TABLE PeopleEx (with PK + FK over PeopleEx.PersonId) to hold the new columns, but there's no way to enforce/require every row in People to have a row in PeopleEx.Depravity
@Depravity to store 8K for a single entity I would use files and store paths only. Nevertheless, one to one can be used to extend a basic table if you want to think of inheritance between tablesEdgy
@ShadiNamrouti "to store 8K for a single entity I would use files and store paths only" Then you wouldn't be able to run any kind of SQL query on that data and it defeats the entire point of using an RDMBS in the first place: no data-integrity constraints, no joins, no automatic indexing, no high-performance aggregation, no columnar stores - instead you'd have to use something like Hadoop just to get a simple SUM and further schema changes would be impossible. Haven't you read Codd's paper on why RDMBS systems exist in the first place?Depravity
@Depravity what kind of 8K info would you store in a single field?Edgy
@ShadiNamrouti 8K is easy to fill with just a few nvarchar(n) columns with very common n values (e.g. ~200-1000). While you can use nvarchar(max) for off-table storage, that has a significant impact on performance and so is unsuitable for columns that will be frequently used in queries. It's also not uncommon to have thousands of columns. Or anything like 1,000 bigint columns, or 800 datetimeoffset columns, or just 285 decimal columns. Hitting the row-size limit happens a lot.Depravity
E
94

I'm pretty sure it is technically impossible in SQL Server to have a True 1 to 1 relationship, as that would mean you would have to insert both records at the same time (otherwise you'd get a constraint error on insert), in both tables, with both tables having a foreign key relationship to each other.

That being said, your database design described with a foreign key is a 1 to 0..1 relationship. There is no constraint possible that would require a record in tableB. You can have a pseudo-relationship with a trigger that creates the record in tableB.

So there are a few pseudo-solutions

First, store all the data in a single table. Then you'll have no issues in EF.

Or Secondly, your entity must be smart enough to not allow an insert unless it has an associated record.

Or thirdly, and most likely, you have a problem you are trying to solve, and you are asking us why your solution doesn't work instead of the actual problem you are trying to solve (an XY Problem).

UPDATE

To explain in REALITY how 1 to 1 relationships don't work, I'll use the analogy of the Chicken or the egg dilemma. I don't intend to solve this dilemma, but if you were to have a constraint that says in order to add a an Egg to the Egg table, the relationship of the Chicken must exist, and the chicken must exist in the table, then you couldn't add an Egg to the Egg table. The opposite is also true. You cannot add a Chicken to the Chicken table without both the relationship to the Egg and the Egg existing in the Egg table. Thus no records can be every made, in a database without breaking one of the rules/constraints.

Database nomenclature of a one-to-one relationship is misleading. All relationships I've seen (there-fore my experience) would be more descriptive as one-to-(zero or one) relationships.

UPDATE EF 5.0 - one-to-one Support

While SQL Server will still allow the dependent row to be null. Entity Framework Core 5.0 now allows you to configure dependent properties as required. EF 5 What's new

Excerpt:

In EF Core 5.0, a navigation to an owned entity can be configured as a required dependent. For example:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });
        b.Navigation(e => e.HomeAddress).IsRequired();
    });
}
Extrapolate answered 24/4, 2012 at 5:56 Comment(9)
"...as that would mean you would have to insert both records at the same time" so one to many relationship means that you need to insert all the records at the same time?Polio
No, because a one to many is actually 1 to 0..(x). There is still no requirement to have any records in the many relationship. :D (this is specifically regarding how EF describes relationships, not me)Extrapolate
As far as I remember from my university times (and it was long long ago :) there is such a thing as one-to-one relationship in the relation database theory. Still I may be wrong and still there is no explicit one-to-one relationships in sql-server. Sorry, what is EF?Polio
Entity FrameworkExtrapolate
"I'm pretty sure it is technically impossible in SQL Server" -- are you sure? Hint: the constraints could be enforced and the data maintained using procedural code (stored procs, triggers).Exhilaration
@Igor: "there is such a thing as one-to-one relationship in the relation database theory" -- if you accept that such relationships exist in reality then we need a way of modelling them (and if our database systems can't handle them well then we should be demanding new systems!) Chris Date wrote a short but thorough paper on the subject of the nature of relationships and justifies a classification of 10 relationship types: All For One, One For All.Exhilaration
I stated such 1 to 1 can exist using triggers (or any proc, that may even disable a constraint, and then enable it), but if you have to disable a constraint to add a row, you are circumventing the entire intention of the constraint in the first place. And PLEASE stop commenting on database theory, I'm trying to explain how it ACTUALLY works, especially in regards to Entity Framework.Extrapolate
Your answare is not totally true. In SQL-SERVER you can create a 1-to-1 relationship using only primary-key for FK and a Unique Index on same columns.Vasiliki
@GlaucoCucchiar That is a term called 1-to-1 however, it is technically 1-to-0..1 AND in EF it will generate a 1-to-0..1 relationship.Extrapolate
H
86

Set the foreign key as a primary key, and then set the relationship on both primary key fields. That's it! You should see a key sign on both ends of the relationship line. This represents a one to one.

enter image description here

Check this : SQL Server Database Design with a One To One Relationship

Houseless answered 24/4, 2012 at 5:56 Comment(12)
This will yield a 1 to 0..1 relationship in EF.Extrapolate
@ErikPhilips How can we achieve this by writing a query without using diagrams?Amazement
@Amazement What do you mean, by writing a query? Do you mean to say, how do I create two tables with the relation ship Pranay described in T-SQL?Extrapolate
@ErikPhilips yes I meant that.Amazement
Since you question isn't related to the OP question, I highly recommend you simply ask it as new question (if you haven't already). You can even include the same picture.Extrapolate
Could user_pers_id have a same name as pers_id? Is there a real need to differentiate column names?Cris
So the reason there is a one-to-one relationship is that the foreign key field is also the primary key field -- so it can only have distinct set of values (preventing one-to-many): constraint PK_user1 primary key (user_pers_id), constraint FK_user1 foreign key (user_pers_id) references person(pers_id)Ickes
Actually, there is no real need to use the Primary Key column in a Foreign Key constraint to achieve 1 to 0..1 relationship. The only thing that matters here is a UNIQUE constraint on the column used for Foreign Key in the second table. Btw, PK is always unique.Forever
@Forever to exemplify, yes, your suggestion should yield the same results. Thus CONSTRAINT UQ_user__user_pers_id UNIQUE (user_pers_id) & CONSTRAINT FK_user__user_pers_id REFERENCES person(pers_id) should be functionally equal to CONSTRAINT PK_user__user_pers_id PRIMARY KEY (user_pers_id) & CONSTRAINT FK_user__user_pers_id REFERENCES person(pers_id).Objective
Perfect! great Help, Was looking for something like that!Albuquerque
I don't understand why this answer is so highly upvoted when it simply isn't a true 1:1 relationship. In true 1:1 relationship there's a hard guarantee that for any PK in table A there will always have a corresponding row in table B for the same PK and vice-versa, otherwise it would be a 1:0-1 relationship. In this answer's case a program can INSERT into the person table and then never INSERT into user table. (This is why you need deferrable constraint support to implement 1:1 relationships otherwise you get an intractable chicken-or-the-egg problem).Depravity
@Forever The UNIQUE column should probably also be NOT NULL. But yeah, it doesn't need to be the primary key column.Catania
T
54

This can be done by creating a simple primary foreign key relationship and setting the foreign key column to unique in the following manner:

CREATE TABLE [Employee] (
    [ID]    INT PRIMARY KEY
,   [Name]  VARCHAR(50)
);

CREATE TABLE [Salary] (
    [EmployeeID]    INT UNIQUE NOT NULL
,   [SalaryAmount]  INT 
);

ALTER TABLE [Salary]
ADD CONSTRAINT FK_Salary_Employee FOREIGN KEY([EmployeeID]) 
    REFERENCES [Employee]([ID]);

Schema

INSERT INTO [Employee] (
    [ID]
,   [Name]
)
VALUES
    (1, 'Ram')
,   (2, 'Rahim')
,   (3, 'Pankaj')
,   (4, 'Mohan');

INSERT INTO [Salary] (
    [EmployeeID]
,   [SalaryAmount]
)
VALUES
    (1, 2000)
,   (2, 3000)
,   (3, 2500)
,   (4, 3000);

Check to see if everything is fine

SELECT * FROM [Employee];
SELECT * FROM [Salary];

Now Generally in Primary Foreign Relationship (One to many), you could enter multiple times EmployeeID, but here an error will be thrown

INSERT INTO [Salary] (
    [EmployeeID]
,   [SalaryAmount]
)
VALUES
    (1, 3000);

The above statement will show error as

Violation of UNIQUE KEY constraint 'UQ__Salary__7AD04FF0C044141D'. Cannot insert duplicate key in object 'dbo.Salary'. The duplicate key value is (1).

Trowbridge answered 14/7, 2015 at 10:55 Comment(6)
This will yield a 1 to 0..1 relationship in Entity Framework. You created an Employee without an existing Salary already present.Extrapolate
I'm a Little curious on this last example. If one just added the Salary object into the Employee class, will I, without further doing, in EF, just be able to say in code: var salary = Employee(Id).Salary???Brainy
Regardless of EF - can create an Employee all day without a salary. This is 1 to 0..1Clad
This suppose to be checked as the BEST answer! I have implement exactly what @Trowbridge has said, I have One to One relationship create on my SQL Server MS. Thank you Very much!!!!Albuquerque
@Albuquerque Well no, it is 1 to 0/1 as explained above. Erik's answer provides all the info. There are plenty of 1 to 0/1 answers on SO.Doran
The only way to do a "true" 1-2-1, that I know anyway, is to mess with disabling constraints which is IMHO pretty silly as a constraint is there to protect you when you are inserting which is what you do when you've disabled the thing that's there to protect you in the first place. I haven't ever tried because, well it's silly. I guess you'd probably get an error thrown when you re-enable it though. For me I do it as in this answer and it works fine and it is a "true" 1-2-1. Also, the main reason this seems to be an issue in this post is because of EF, sooo glad I learned Dapper :)Gough
R
6

There is one way I know how to achieve a strictly* one-to-one relationship without using triggers, computed columns, additional tables, or other 'exotic' tricks (only foreign keys and unique constraints), with one small caveat.

I will borrow the chicken-and-the-egg concept from the accepted answer to help me explain the caveat.

It is a fact that either a chicken or an egg must come first (in current DBs anyway). Luckily this solution does not get political and does not prescribe which has to come first - it leaves it up to the implementer.

The caveat is that the table which allows a record to 'come first' technically can have a record created without the corresponding record in the other table; however, in this solution, only one such record is allowed. When only one record is created (only chicken or egg), no more records can be added to any of the two tables until either the 'lonely' record is deleted or a matching record is created in the other table.

Solution:

Add foreign keys to each table, referencing the other, add unique constraints to each foreign key, and make one foreign key nullable, the other not nullable and also a primary key. For this to work, the unique constrain on the nullable column must only allow one null (this is the case in SQL Server, not sure about other databases).

CREATE TABLE dbo.Egg (
    ID int identity(1,1) not null,
    Chicken int null,
    CONSTRAINT [PK_Egg] PRIMARY KEY CLUSTERED ([ID] ASC) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE dbo.Chicken (
    Egg int not null,
    CONSTRAINT [PK_Chicken] PRIMARY KEY CLUSTERED ([Egg] ASC) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE dbo.Egg  WITH NOCHECK ADD  CONSTRAINT [FK_Egg_Chicken] FOREIGN KEY([Chicken]) REFERENCES [dbo].[Chicken] ([Egg])
GO
ALTER TABLE dbo.Chicken  WITH NOCHECK ADD  CONSTRAINT [FK_Chicken_Egg] FOREIGN KEY([Egg]) REFERENCES [dbo].[Egg] ([ID])
GO
ALTER TABLE dbo.Egg WITH NOCHECK ADD CONSTRAINT [UQ_Egg_Chicken] UNIQUE([Chicken])
GO
ALTER TABLE dbo.Chicken WITH NOCHECK ADD CONSTRAINT [UQ_Chicken_Egg] UNIQUE([Egg])
GO

To insert, first an egg must be inserted (with null for Chicken). Now, only a chicken can be inserted and it must reference the 'unclaimed' egg. Finally, the added egg can be updated and it must reference the 'unclaimed' chicken. At no point can two chickens be made to reference the same egg or vice-versa.

To delete, the same logic can be followed: update egg's Chicken to null, delete the newly 'unclaimed' chicken, delete the egg.

This solution also allows swapping easily. Interestingly, swapping might be the strongest argument for using such a solution, because it has a potential practical use. Normally, in most cases, a one-to-one relationship of two tables is better implemented by simply refactoring the two tables into one; however, in a potential scenario, the two tables may represent truly distinct entities, which require a strict one-to-one relationship, but need to frequently swap 'partners' or be re-arranged in general, while still maintaining the one-to-one relationship after re-arrangement. If the more common solution were used, all data columns of one of the entities would have to be updated/overwritten for all pairs being re-arranged, as opposed to this solution, where only one column of foreign keys need to be re-arranged (the nullable foreign key column).

Well, this is the best I could do using standard constraints (don't judge :) Maybe someone will find it useful.

Reenter answered 14/7, 2016 at 6:11 Comment(4)
This will yield a 1 to 0..1 relationship in Entity Framework.Extrapolate
Of course in other SQL database Servers like Oracle or PostgreSQL, this can by correctly done with deferrable constraints that are checked when the transaction is committed, not when the row is inserted, thus avoiding the chicken-and-egg problem. Surprisingly, MS has ignored this part of the SQL-92 standard, which is now 25 years old.Remunerative
Another more long-term way to get true 1:1 relationships in MS SQL Server is by voting up this connect item: connect.microsoft.com/SQLServer/feedback/details/124728/…Remunerative
Anyways - thank you for this answer - it seems like the best work-around for my database. AppreciatedRemunerative
D
5

How does one create a one-to-one relationship in SQL Server?

Short answer: You can't.

Long answer: You can, if you dare to read on...


I understand there are two main approaches for "implementing" 1:1 relationships when deferrable constraints are not supported by a DBMS (*cough* MS SQL Server *cough*). This post discusses those 2 main approaches.

Both of these approaches have some degree of compatibility with EF by tricking EF into treating a VIEW as a TABLE. If you aren't using EF then you probably don't need the VIEW objects, but they're still handy for convenience queries and for quickly querying a product type view of your 1:1 entities in separate tables.

Both of these approaches are built around using another table (ValidCountries) which contains only PK values, and exists for 2 reasons:

  1. To have FK constraints to both of the 1:1 member tables (don't forget you can also have three or more 1:1 tables too!): so a row in ValidCountries cannot exist unless all required related data exists in their respective tables.
  2. To provide a target for any incoming FOREIGN KEY constraints from other entities. This is explained in more detail and demonstrated below.

The two approaches differ in their constraints on the 1:1 member tables, their use of TRIGGER objects, and their compatibility with EF. I'm sure more variations on these 2 approaches are possible - it really depends on how you modelled your data and your business requirements.

Neither of these approaches use CHECK CONSTRAINT rules with UDFs to validate data in other tables, which is currently the predominant way to implement 1:1 constraints, but that approach has a poor reputation for performance.


Approach 1: Use two more TABLE objects (one for forward-declarations, the other as proof-of-validity), and a read/write VIEW to expose only valid 1:1 data from a JOIN:

  • This approach uses a third table to "forward-declare" only the (shared) PK values, while other tables that want a 1:1 relationship with each other reference only the forward-declaration table.

  • Another "final" TABLE is used to prove (via FK constraints) that for any given PK, that valid definitely exists.

  • This complexity is then hidden behind an (technically optional) VIEW object which exposes only valid data and performs an INNER JOIN of the 3 (or more) backing tables, while also supporting INSERT/UPDATE/DELETE/MERGE DML operations.

    • This works great with Entity Framework as EF is perfectly happy to pretend that a VIEW is a TABLE. A caveat is that all these approaches are strictly database-first because all these approaches outsmart EF to bend it to our will (so be sure to disable migrations!)
    • While the "final" table might seem superfluous as the VIEW won't ever expose invalid data, it's actually quite necessary to serve as a target for incoming foreign-key references from other separate entity tables (which must never reference the forward-declarations table).
  • The three tables are:

    1. Table 1: The "forward-declaration table" with only the PK value.
    • In the OP's example (of Countries and Capitals), this would be a table named like CountryDeclarations (or CountryDecl for short) and stores only CountryName values, which is the shared PK for both the Countries and Capitals tables).
    1. Table 2: One (or more!) dependent tables with FKs to the forward-declaration table.
      • In the OP's example this would be 2 tables:
        • TABLE Countries with CountryName as the table's PK and its FK to only the forward-declaration table.
        • TABLE Capitals with CountryName as the table's PK and its FK to only the forward-declaration table.
    2. Table 3: The publicly-visible principal table, which has FKs to the forward-declaration table and all dependent tables.
    • In the OP's example this would be TABLE ValidCountries with a PK + FK to CountryDecl and separate FK columns to Countries and Capitals.

Here's a database-diagram of this approach:

enter image description here

  • When querying data from the Countries and/or Capitals tables, provided provided you always INNER JOIN with ValidCountries then you get hard guarantees that you're always querying valid data.

    • Or just use the VIEW to get the JOIN already-done for you.
  • Remember that the 1:1 relationship is not enforced between the constituent Countries and Capitals tables: this is necessary otherwise there would be a chicken vs. egg problem on INSERT.

    • Though if you're sure you'll always INSERT into Countries before Capitals (and DELETE in the reverse order) you could add an FK constraint from Capitals directly to Countries, but this doesn't really add any benefits because the Countries table cannot provide guarantees that a corresponding Capitals row will exist.
  • This design is compatible with IDENTITY PKs too, just remember that only the forward-declaration table will have the IDENTITY column, all other tables will have normal int PK+FK columns.

Here's the SQL for this approach:

CREATE SCHEMA app1; /* The `app1` schema contains the individual objects to avoid namespace pollution in `dbo`. */
GO

CREATE TABLE app1.CountryDecl (
    CountryName nvarchar(100) NOT NULL,
    CONSTRAINT PK_CountryDecl PRIMARY KEY ( CountryName )
);
GO

CREATE TABLE app1.Countries (
    CountryName nvarchar(100) NOT NULL,
    CapitalName nvarchar(255) NOT NULL,
    Inhabitants bigint        NOT NULL,
    AreaKM2     bigint        NOT NULL,

    CONSTRAINT PK_Countries PRIMARY KEY ( CountryName ),

    CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES app1.CountryDecl ( CountryName ),
--  CONSTRAINT FK_Countries_Capitals FOREIGN KEY ( CountryName ) REFERENCES app1.Capitals ( CountryName ) -- This FK is entirely optional and adds no value, imo.
);
GO

CREATE TABLE app1.Capitals (
    CountryName nvarchar(100) NOT NULL,
    CapitalName nvarchar(255) NOT NULL,
    Inhabitants bigint        NOT NULL,
    AreaKM2     int           NOT NULL,

    CONSTRAINT PK_Capitals PRIMARY KEY ( CountryName ),

    CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES app1.CountryDecl ( CountryName )
);
GO

CREATE TABLE app1.ValidCountries (

    CountryName nvarchar(100) NOT NULL,

    CONSTRAINT PK_ValidCountries PRIMARY KEY ( CountryName ),

    CONSTRAINT FK_ValidCountries_to_Capitals FOREIGN KEY ( CountryName ) REFERENCES app1.Capitals ( CountryName ),
    CONSTRAINT FK_ValidCountries_to_Countries FOREIGN KEY ( CountryName ) REFERENCES app1.Countries ( CountryName ).
    CONSTRAINT FK_ValidCountries_to_Decl FOREIGN KEY( CountryName ) REFERENCES app1.CountriesDecl ( CountryName )
);
GO

CREATE VIEW dbo.Countries AS
SELECT
    -- ValidCountries:
    v.CountryName,

    -- Countries
    cun.Inhabitants     AS CountryInhabitants,
    cun.Area            AS CountryArea,

    -- Capitals
    cap.Capital         AS CapitalCityName,
    cap.CityArea        AS CapitalCityArea,
    cap.CityInhabitants AS CapitalCityInhabitants

FROM
    app1.ValidCountries AS v
    INNER JOIN app1.Countries AS cun ON v.CountryName = cun.CountryName
    INNER JOIN app1.Capitals  AS cap ON v.CountryName = cap.CountryName;

GO

CREATE TRIGGER Countries_Insert ON dbo.Countries
INSTEAD OF INSERT 
AS
BEGIN

    SET NOCOUNT ON;
    
    INSERT INTO app1.CountriesDecl (
        CountryName
    )
    SELECT
        CountryName
    FROM
        inserted;

    -------

    INSERT INTO app1.Capitals (
        CountryName,
        Capital,
        CityInhabitants,
        CityArea
    )
    SELECT
        CountryName,
        CapitalCityName,
        CapitalCityInhabitants,
        CapitalCityArea
    FROM
        inserted;

    -------

    INSERT INTO app1.Countries (
        CountryName,
        Capital,
        Inhabitants,
        Area
    )
    SELECT
        CountryName,
        CapitalCityName,
        CountryInhabitants,
        CountryArea
    FROM
        inserted;

    ----

    INSERT INTO app1.ValidCountries (
        CountryName
    )
    SELECT
        CountryName
    FROM
        inserted;

    -------

END;

/* NOTE: Defining UPDATE and DELETE triggers for the VIEW is an exercise for the reader. */
  • When using Entity Framework and Entity Framework Core, remember that approaches like these are ultimately about outsmarting Entity Framework (if not outright hacks), so it's important that you don't ever let EF perform any migrations or generate and run any DDL (CREATE TABLE...) statements based on your Code-First entity model classes.
    • While EF no-longer supports "Database-first" models, you can still use "Code-first from Database" with code-first code-gen like https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator (disclaimer: this is my personal favourite code-gen and I'm a contributor to that project).

    • If you run default scaffolding or code-first-codegen on a database using this approach dthen you'll end up with a model containing separate entities for app1.Countries, app1.Capitals, app1.CountriesDecl and app1.ValidCountries - so you should configure your code-gen to filter-out those objects you don't want in your EF model.

      • In this case, I'd exclude all app1.* tables from EF, and instead instruct EF to treat VIEW dbo.Countries as a single entity (which makes sense, as mathematically every 1:1 relationship between 2 entities is the same thing as a single entity defined as a Product Type of those 2 other entities).
      • Because a VIEW does not have a PRIMARY KEY nor any FOREIGN KEY constraints, EF (by default) cannot correctly codegen an entity class from a VIEW, but the aforementioned code-gen tool makes it easy to nudge EF in the right ways (look for the ViewProcessing method, and AddForeignKeys method below it).
    • If you do retain the app1.Countries and app1.Capitals tables as entity types in EF, be aware that having EF perform an INSERT into those two tables will fail unless your code first does an INSERT into app1.CountriesDecl.

    • Or you could add a CREATE TRIGGER Countries/Capitals_Insert ON app1.Countries/app1.Capitals INSTEAD OF INSERT which will perform the IF NOT EXIST ... INSERT INTO app1.CountriesDecl.

    • However EF won't have any problems with UPDATE and DELETE on those 2 tables, at least.


Approach 2: Only a single extra TABLE object, but the FK columns are NULL-able - and a VIEW is used as a curtain to hide invalid/incomplete rows.

  • If Approach 1 can be summarized as borrowing ideas from the "objects must always be immutable" school-of-thought, then Approach 2 is inspired by languages that allow you to mutate an existing object in-place such that the compiler can verify that each mutation step alters the effective type of the object such that it satisfies some type-constraint.

    • For example, consider this pseudo-TypeScript (because as of 2022, TypeScript still doesn't seem to support/detect when adding properties to a POJsO (thus extending its structural type) is valid and provably extends a variable's static type):

      interface MyResult { readonly name: string; readonly year: number; };
      
      function doSomething() : MyResult {
          let result = {};
      //  return result;                 // Error: Cannot return `result` yet: it doesn't conform to `MyResult` (there's no `name` nor `year` value)
          result.name = "NameGoesHere";  // So let's define `name`.
      //  return result;                 // ERROR: Still cannot return `result` yet: it still doesn't yet have a `year` property.
          result.year = 2022;            // So let's add `year`.
          return result;                 // No error, `result` can now be returned OK because it conforms to `interface MyResult`.
      }
      
  • With that concept in-mind, we can have TABLE objects that holds partial/incomplete Country and Capital data which we can freely insert/update/delete because their mutual FOREIGN KEY constraints are NULL-able, see below.

    • The tables are named dbo.CountriesData and dbo.CapitalsData instead of dbo.Countries and dbo.Capitals respectively to indicate that the tables only contain arbitrary "data" rather than valid and correct entities. This is a personal naming-convention of mine. YMMV.
    • As with Approach 1, the VIEW dbo.Countries exists which exposes only valid entities as a single product type.
      • Optionally you could also define additional VIEW objects for Countries and Capitals separately and do the work to make EF treat those as Entities too (though you'll need to do loads more legwork to make INSERT work for each view individually).
  • But unlike with Approach 1, the dbo.CapitalsData table now has a composite primary-key, which is a consequence of the OP's specific database design objectives - this might not apply to your database.

    • The composite-PK is necessary to allow dbo.Countries to have a non-NULL CountryName value while not having the FK_CountriesData_to_Capitals constraint enforced. This is necessary because CountryName is also the PK of dbo.CountriesData, so it cannot be NULL. This works because SQL Server only enforces FK constraints when all columns in an FK are non-NULL. If you have a different PK design then this will be different for you.
CREATE TABLE dbo.CountriesData (
   CountryName nvarchar(100) NOT NULL,
   CapitalName nvarchar(255)     NULL,
   Inhabitants bigint        NOT NULL,
   Area        geography     NOT NULL,

   CONSTRAINT PK_CountriesData PRIMARY KEY ( CountryName ),

   CONSTRAINT FK_CountriesData_to_Capitals FOREIGN KEY ( CountryName, CapitalName ) REFERENCES dbo.CapitalsData ( CapitalName )
);

CREATE TABLE dbo.CapitalsData (
    CountryName nvarchar(100) NOT NULL,
    CapitalName nvarchar(255) NOT NULL,
    Inhabitants bigint        NOT NULL,
    Area        geography     NOT NULL,

    CONSTRAINT PK_CapitalsData PRIMARY KEY ( CountryName, CountryName ),

    CONSTRAINT FK_CapitalssData_to_Countries FOREIGN KEY ( CapitalName ) REFERENCES dbo.CountriesData ( CountryName )
);

CREATE VIEW dbo.Countries AS
SELECT
    -- Countries
    cun.Inhabitants     AS CountryInhabitants,
    cun.Area            AS CountryArea,

    -- Capitals
    cap.Capital         AS CapitalCityName,
    cap.CityArea        AS CapitalCityArea,
    cap.CityInhabitants AS CapitalCityInhabitants

FROM
    dbo.CountriesData AS cd
    INNER JOIN dbo.CapitalsData AS cad ON cd.CountryName = cad.CountryName;


CREATE TABLE dbo.ValidCountries (

   -- This TABLE is largely the as in Approach 1. Ensure that all incoming FKs only reference this table and not dbo.CountriesData or dbo.CapitalsData.
   -- NOTE: When using EF, provided to trick EF into treating `VIEW dbo.Countries` as a TABLE then you don't need to include this table in your EF model at all (just be sure to massage all of EF's FK relationships from other entities that initially point to `ValidCountries` to point to the `VIEW dbo.Countries` entity instead.

    CountryName nvarchar(100) NOT NULL,
    CapitalName nvarchar(255) NOT NULL,

    CONSTRAINT PK_ValidCountries PRIMARY KEY ( CountryName ),

    CONSTRAINT FK_ValidCountries_to_Capitals FOREIGN KEY ( CountryName ) REFERENCES dbo.CapitalsData ( CountryName, CapitalName ),
    CONSTRAINT FK_ValidCountries_to_Countries FOREIGN KEY ( CountryName ) REFERENCES dbo.CountriesData ( CountryName )
);

CREATE TRIGGER After_UPDATE_in_CountriesData_then_INSERT_into_ValidCountries_if_valid ON dbo.CountriesData
AFTER UPDATE 
AS
BEGIN
    INSERT INTO dbo.ValidCountries ( CountryName, CapitalName )
    SELECT
        i.CountryName,
        i.CapitalName
    FROM
        inserted.CountryName AS i
        INNER JOIN dbo.CapitalsData AS capd ON -- The JOINs prevents inserting CountryNames for countries that are either invalid or already exist in dbo.ValidCountries.
            capd.CountryName = i.CountryName
            AND
            capd.CapitalName = i.CapitalName
        LEFT OUTER JOIN dbo.ValidCountries AS v ON -- This is a "LEFT ANTI JOIN" due to the WHERE condition below.
            v.CountryName = i.CountryName
    WHERE 
        v.CountryName IS NULL
        AND
        i.CapitalName IS NOT NULL;
END;

CREATE TRIGGER After_INSERT_in_CapitalsData_then_SET_C ON dbo.CapitalsData
AFTER INSERT 
AS
BEGIN
    
    -- Due to the specific design of dbo.CapitalsData, any INSERT will necessarily complete a valid product-type entity, so we can UPDATE dbo.CountriesData to set CapitalName to the correct value.
    UPDATE
        cd
    SET
        cd.CapitalName = inserted.CapitalName
    FROM
        dbo.CountriesData AS cd
        INNER JOIN inserted AS i ON
            cd.CountryName = i.CountryName
            AND
            cd.CapitalName IS NULL
    WHERE
        i.CountryName IS NOT NULL;
 
END;
  • For manual DML:
    • To INSERT a new Country...
      1. First INSERT INTO dbo.CountriesData with an initially NULL CapitalName value.
      • This is okay because SQL Server ignores FK constraints when its value (or when at least 1 value in a composite FK) is NULL.
      1. Then INSERT INTO dbo.CapitalsData (or vice-versa, provided CountryName is conversely NULL).
      2. Only after both rows are inserted do you then run UPDATE dbo.CountriesData SET CapitalName = inserted.CapitalName WHERE CountryName = inserted.CountryName.
      3. Whereupon your VIEW dbo.Countries will now expose the now-valid 1:1-related data.
    • DELETE operations must be performed in reverse-order (i.e. first UPDATE to clear the FKs, then DELETE from each table, in any order).
    • UPDATE operations require no special handling.
  • I note that you could actually move all the above INSERT logic into an AFTER INSERT trigger on both CountriesData and CapitalsData tables, as this means:
    • That UPDATE into an AFTER INSERT trigger on dbo.CapitalsData! (and vice-versa) - but be sure to also add the check that WHERE inserted.CountryName IS NOT NULL - but if you do that then your client's SQL code only needs to do two INSERT statements and one of the two AFTER INSERT triggers will handle the rest automatically, but only if the data is finally valid - whereupon it will be visible in VIEW dbo.Countries.
    • This approach plays nicer with EF, as you don't need to faff around with the CountriesDecl table, so doing individual INSERT ops into dbo.CountriesData and dbo.CapitalsData won't fail - but remember that there's no 1:1 relationship between those two tables/entities.
Depravity answered 23/8, 2021 at 23:9 Comment(3)
Option 1; This will yield a 1 to 0..1 relationship in Entity Framework. Option 2, depending on how the modelbuilder and entities are configured, may yield a 1 to 0..1 or no relationship. EF only supports 1 to 0..1 or 1 to 0..∞ relationships. For SQL Server this is a Novel solution and I'm only mentioning the outcome in EF since the OP specifically mentions it.Extrapolate
@ErikPhilips Right, but EF6 nor EF Core 6 supports true 1:1 relationships anyway (I understand that EFC6 supports 1:1 internally, but does not enforce it when validating in-memory entity objects). I only see this as a problem if you're using EF6/EFC6 to do migrations or handle your DDL for you, which is moot in this case as the fact we're defining views and tables ourselves means all EF6/EFC6 use is implicitly always database-first (or code-first-from-codegen which is my preference) and never truly code-first.Depravity
@ErikPhilips Besides, even if EF did support 1:1 relationships between two entities, it should always be possible for applications to specifically load and save individual entities of that relationship instead of having EF always require both entities always be loaded and saved together - that would just be silly and wasteful.Depravity
I
2

1 To 1 Relationships in SQL are made by merging the field of both table in one !

I know you can split a Table in two entity with a 1 to 1 relation. Most of time you use this because you want to use lazy loading on "heavy field of binary data in a table".

Exemple: You have a table containing pictures with a name column (string), maybe some metadata column, a thumbnail column and the picture itself varbinary(max). In your application, you will certainly display first only the name and the thumbnail in a collection control and then load the "full picture data" only if needed.

If it is what your are looking for. It is something called "table splitting" or "horizontal splitting".

https://visualstudiomagazine.com/articles/2014/09/01/splitting-tables.aspx

Infinitude answered 28/11, 2016 at 10:23 Comment(3)
This will yield a 1 to 0..1 relationship in Entity Framework.Extrapolate
"1 To 1 Relationships in SQL are made by merging the field of both table in one !" - while this is true as far as database-theory is concerned, it's impractical because SQL Server disallows tables with rows wider than 8060 bytes (excepting specific column-types that support off-table storage for large-values, namely varchar(max), nvarchar(max), varbinary(max), ntext, text, and image). Also, sticking all related data in a single table also restricts your ability to use per-table security and optimize storage scenarios.Depravity
For security, you use view and store proc to interact with the data instead of querying directly the table.Infinitude
Q
0

The easiest way to achieve this is to create only 1 table with both Table A and B fields NOT NULL. This way it is impossible to have one without the other.

Quarterly answered 5/1, 2018 at 13:52 Comment(3)
This will yield a 1 to 0..1 relationship in Entity Framework.Extrapolate
@ErikPhilips, it's OK. Who told you that EF is 100% correct?Edgy
@ShadiNamrouti When did I say EF was correct? I only stated what the result will be, not an opinion of that result.Extrapolate
M
-1

What about this ?

create table dbo.[Address]
(
Id int identity not null,
City nvarchar(255) not null,
Street nvarchar(255) not null,
CONSTRAINT PK_Address PRIMARY KEY (Id)
)

create table dbo.[Person]
(
Id int identity not null,
AddressId int not null,
FirstName nvarchar(255) not null,
LastName nvarchar(255) not null,
CONSTRAINT PK_Person PRIMARY KEY (Id),
CONSTRAINT FK_Person_Address FOREIGN KEY (AddressId) REFERENCES dbo.[Address] (Id)
)
Misunderstanding answered 18/5, 2019 at 21:4 Comment(10)
Is this asking a question or proposing an answer? This is clearly a case described in other answers as 1:0-or-1. In fact it is the same inadequate code as described in the question. Why is that not apparent anyway? You can have an address row with no person row.Pitterpatter
@Pitterpatter you are right, I did not realize that creating additional address is an issue.Misunderstanding
If you didn't realize that, I wonder what you thought the question was about? "1:1" (Have you read all the answers & comments?) PS You might want to delete this before it gets downvoted. That can affect your allowed posting rate, whether later deleted or not. This merits downvoting but I didn't because I came here via your profile to investigate question quality.Pitterpatter
In SQL i always thought about it as 1:1 but I did not try this in EF where this relationship is treated differently. I will try better next time.Misunderstanding
It's not 1:1 in SQL, the referenced table can have unreferenced rows. The EF is reflecting the SQL. And the question is about SQL. Read the question, answers & comments.Pitterpatter
In this code there can be addresses that no person has. You are describing 1:0-or-1. I have no idea what you are trying to say by the "does not matter" sentence starting "Perspective". 1:1 in the question means contrained like SQL PK FKs to each other. Yet again: Read the question, answers & comments. Etc.Pitterpatter
I don't know how it can be 1-0 (even with unreferenced addresses records) but I will leave it as that. Thank you for your comments.Misunderstanding
If an address has one or no person & a person has one address then address:person is a 1:(0-or-1) relationship. Bye.Pitterpatter
Seems strange to me to define a relation between address and person when the address has no reference record to person. This is probably source of confusion, I get your point now.Misunderstanding
Approaches to cardinality can be divided into two approaches: describing an n-ary relation/table vs describing one of those plus single-column tables from which values are drawn. (Also diagram line labels come in 2 camps, look-here & look-away.) This is similar to the 2 approaches to function cardinality--describing a set of key-value pairs vs describing one of those plus sets from which keys & values are drawn & with respect to which a function can be total or partial. (Terms "domain", "range" & "co-domain" don't even have standard meanings & every presentation has to clarify its usage.)Pitterpatter

© 2022 - 2024 — McMap. All rights reserved.