-1

I'm getting this error:

There are no primary or candidate keys in referenced table 'User' that match.

I checked for other similar questions but different answers didn't help me.

Are my primary keys set wrong?
Must I reference only one primary key in ShoppingCart table ?

enter image description here

CREATE TABLE [User]
(
    EmailAddress NVARCHAR(320),
    UserId INT IDENTITY(1,1),
    UserPassword VARCHAR(16),
    FirstName VARCHAR(256) NOT NULL,
    LastName VARCHAR(256) NOT NULL,
    MobileNumber BIGINT,
    PRIMARY KEY (EmailAddress, UserId)
)

CREATE TABLE [ShoppingCart]
(
    OrderId INT PRIMARY KEY IDENTITY(1,1),
    UserId INT FOREIGN KEY REFERENCES [User](UserId), //-- error here
    CreatedDate NVARCHAR(40)
)

Thank you very much for all the users who answer me below.

ExtraSun
  • 528
  • 2
  • 11
  • 31
  • 5
    Your foreign key is only referencing the column `UserId`, but your Primary key is on `UserID` ***and*** `EmailAddress`. If I am honest, an Email Address is a poor choice for a primary key; people change their email addresses and updating primary keys, is a *real* pain. The Primary Key should ideally be static (and if clustered, always ascending). – Thom A Jul 27 '21 at 08:53
  • 1
    `EmailAddress` is for other table, I don't need `EmailAddress` in `[ShoppingCart]` table. But thanks for your email addresses advice. – ExtraSun Jul 27 '21 at 08:54
  • 1
    *"I don't need `EmailAddress`"* then why is it part of your primary key? Unless it *must* be part of the primary key, and then you **do** need said column in `ShoppingCart`. – Thom A Jul 27 '21 at 08:56
  • If your UserId is unique in [User] table and you can't change the Primary Key, make the UserId column as UNIQUE. You are allowed to make a Unique column as FK in another table. – PSK Jul 27 '21 at 08:56
  • 1
    @Larnu What do you mean by "then you do need said column in `ShoppingCart`" ? – ExtraSun Jul 27 '21 at 09:01
  • 1
    @PSK is the `INT IDENTITY(1,1)` not enough in UserId ? why to add `UNIQUE` ? – ExtraSun Jul 27 '21 at 09:05
  • 2
    You don't have **two primary keys** - any table in a relational database by definition has **at most** ***ONE*** primary key. What you have is a **compound** primary key - made up from more than one column - but it's still **ONE primary key** – marc_s Jul 27 '21 at 09:07
  • 2
    An identity column is not guaranteed to be unique. For more information, read [SQL Server’s identity column misconceptions](https://zoharpeled.wordpress.com/2019/10/06/sql-servers-identity-column-misconceptions/) over on my blog. – Zohar Peled Jul 27 '21 at 09:11
  • @ZoharPeled Hi Zohar I'm from Israel too, I read your article chapeau! Than what are you suggesting instead of `IDENTITY(1,1)` ? – ExtraSun Jul 27 '21 at 09:18
  • 1
    *"What do you mean by "then you do need said column in `ShoppingCart`" "* Exactly what I say; if `EmailAddress` *must* be part of your primary key, you *need* the `EmailAddress` in the table `ShoppingCard` to be able to reference your Foreign Key. – Thom A Jul 27 '21 at 09:21
  • An `IDENTITY` should be good enough, @ExtraSun . The problem is that people think that an `IDENTITY` on its own guarantees uniqueness; which is does not. It is only when it is combined with a Unique Constraint/Index that the column can be guaranteed to contain unique values. – Thom A Jul 27 '21 at 09:23
  • 1
    Very likely, here, you want your `ID` column defined as your Primary Key, and a separate unique constraint on your `EmailAddress` column. This means that the `ID` can be used as the foreign key, but you still guarantee that an email address cannot be repeated in the table. – Thom A Jul 27 '21 at 09:25
  • I'm not suggesting anything instead of `Identity(1,1)`, I'm just saying that it's a mistake to think about it as a unique column just because it's defined with the `identity` attribute. To make it unique you must either use a unique constraint on a unique index on it. Using it as the only column in your primary key seems like a good idea, but you must also add a unique index on the `email` column. Also, seems like you're storing plain text passwords in your database. Don't. Read [Salted Password Hashing - Doing it Right](https://crackstation.net/hashing-security.htm) and follow that protocol. – Zohar Peled Jul 27 '21 at 09:26
  • @Larnu I'm taking your adivce, users might change their email address. Than I'm making only `UserId ` as a primary key. – ExtraSun Jul 27 '21 at 09:27
  • Yes, that's my point, @ExtraSun ... I explicitly stated about people changing their email address in my [first comment](https://stackoverflow.com/questions/68541787/sql-primaty-key-issued-with-foreign-key?noredirect=1#comment121132980_68541787). – Thom A Jul 27 '21 at 09:29
  • 1
    @ZoharPeled do you mean to do unique constraint on a unique index on UserId column ? Because I found explanations about both of them by separately, didnt understand. – ExtraSun Jul 27 '21 at 09:40
  • I think @Larnu's answer should give you all the information you need. It's probably quite the same as I would write if I had the time and energy to invest in a proper answer. However, please do note the last two sentences of my previous comment, and learn how to properly store password, salted and hashed. – Zohar Peled Jul 27 '21 at 11:14
  • @ZoharPeled That was the OP's [next question](https://stackoverflow.com/questions/68543252/sql-server-how-to-insert-a-hashed-password-to-a-table). – Thom A Jul 27 '21 at 11:16

1 Answers1

2

As I have mentioned in the comments, the problem is that you are trying to create a FOREIGN KEY that references a PRIMARY KEY/UNIQUE CONSTRAINT in your table User which is made up of UserID and only UserID; no such constraint exists and as such the creation of the FOREIGN KEY fails.

To address this literally, you would need to add the EmailAddress column to the ShoppingCart table and the FOREIGN KEY's definition:

CREATE TABLE dbo.[User]
(
    EmailAddress NVARCHAR(320),
    UserId INT IDENTITY(1,1),
    UserPassword VARCHAR(16),
    FirstName VARCHAR(256) NOT NULL,
    LastName VARCHAR(256) NOT NULL,
    MobileNumber varchar(20), --Numerical data types are a poor choice for a Phone Number
    PRIMARY KEY (EmailAddress, UserId)
)

CREATE TABLE dbo.[ShoppingCart]
(
    OrderId INT PRIMARY KEY IDENTITY(1,1),
    UserId INT,
    EmailAddress nvarchar(320),
    CreatedDate datetime2(0), --(n)varchar is not a "one size fits all" data type.
    FOREIGN KEY (EmailAddress,UserID) REFERENCES [User](EmailAddress,UserId)
)

This, in truth, is a bad idea, as what do you think would happen if someone changed their email address? Well, let's try. Firstly, let's create some sample data:

INSERT INTO dbo.[User] (EmailAddress, FirstName, LastName)
VALUES(N'Jane@bloggs.com','Jane','Bloggs');

INSERT INTO dbo.ShoppingCart (UserId, EmailAddress, CreatedDate)
VALUES(1, N'Jane@bloggs', GETDATE());

And now Let's say Jane has changed her Email address, so we try to UPDATE the table:

UPDATE dbo.[User]
SET EmailAddress = N'Jane.Bloggs@email.com'
WHERE UserId = 1;

The INSERT statement conflicted with the FOREIGN KEY constraint "FK__ShoppingCart__36DDA17A". The conflict occurred in database "Sandbox", table "dbo.User".

This is because the value of EmailAddress in the table ShoppingCart is still N'Jane@bloggs.com', so the value can't be updated. Obviously, you can't also UPDATE the value in dbo.ShopppingCart because you'll get the same error:

UPDATE dbo.[ShoppingCart]
SET EmailAddress = N'Jane.Bloggs@email.com'
WHERE UserId = 1;

The INSERT statement conflicted with the FOREIGN KEY constraint "FK__ShoppingCart__415B2FED". The conflict occurred in database "Sandbox", table "dbo.User".

So what do you do instead? Well I assume that the email address is meant to be unique within the application, not by user. At the moment you could have 100 Users with a UserID of 1 and provided they all had different Email Addresses, they would be valid Primary Keys. Most likely what you really want, based on that you don't want EmailAddress in the table ShoppingCard, is for UserID to be the Primary Key, and the Email Address to have a separate Unique Constraint. This gives you the following instead:

CREATE TABLE dbo.[User] -- I recommend against the name USER, it's a reserved keyword
(
    EmailAddress NVARCHAR(320),
    UserId INT IDENTITY(1,1),
    UserPassword VARCHAR(16),
    FirstName VARCHAR(256) NOT NULL,
    LastName VARCHAR(256) NOT NULL,
    MobileNumber varchar(20), --Numerical data types are a poor choice for a Phone Number
);

ALTER TABLE dbo.[User] ADD CONSTRAINT PK_User PRIMARY KEY (UserId);
ALTER TABLE dbo.[User] ADD CONSTRAINT UQ_UserEmail UNIQUE (EmailAddress);
GO
CREATE TABLE dbo.[ShoppingCart]
(
    OrderId INT IDENTITY(1,1),
    UserId INT,
    CreatedDate datetime2(0), --(n)varchar is not a "one size fits all" data type.
)
ALTER TABLE dbo.[ShoppingCart] ADD CONSTRAINT PK_ShoppingCart PRIMARY KEY (OrderId);
ALTER TABLE dbo.[ShoppingCart] ADD CONSTRAINT FK_ShoppingCart_User
    FOREIGN KEY (UserId)
    REFERENCES dbo.[User] (UserID);

I also give your constraints explicit names, a habit you should really get into.

Now you can INSERT the sample data, and UPDATE the email address without issue:

INSERT INTO dbo.[User] (EmailAddress, FirstName, LastName)
VALUES(N'Jane@bloggs.com','Jane','Bloggs');

INSERT INTO dbo.ShoppingCart (UserId, CreatedDate)
VALUES(1,GETDATE());
GO

UPDATE dbo.[User]
SET EmailAddress = N'Jane.Bloggs@email.com'
WHERE UserId = 1;

And if you try to add another user with the same email address, you get an error:

INSERT INTO dbo.[User] (EmailAddress, FirstName, LastName)
VALUES(N'Jane.Bloggs@email.com','Jane','Bloggs');

Violation of UNIQUE KEY constraint 'UQ_UserEmail'. Cannot insert duplicate key in object 'dbo.User'. The duplicate key value is (Jane.Bloggs@email.com).

Thom A
  • 88,727
  • 11
  • 45
  • 75
  • 1
    Thank you so much for your explanation Larnu. Question - why to add at the next step of `ALTER TABLE` primary and unique keys BUT not at the initial table `CREATE` and same as `ADD CONSTRAINT` in `ShoppingCart` ? – ExtraSun Jul 27 '21 at 09:56
  • I just noticed, you should fix that column `CreatedDate` too, @ExtraSun . If the column is an `nvarchar` it is by definition **not** a date. Use a date and time datatype. Also, the `MobileNumber` should ironicially *not* be a numerical data type. – Thom A Jul 27 '21 at 10:18
  • Is better than giving the date as a string and passing it to the SQL at the client side ? In my case in the ReactJS code. – ExtraSun Jul 27 '21 at 10:26
  • ***NEVER*** store date and time values as an `(n)varchar`, @ExtraSun . Date and time data types exist for a reason; use them. – Thom A Jul 27 '21 at 10:27
  • I meant to create the dateime at the client side, why not ? it will be received as a string the SQL. – ExtraSun Jul 27 '21 at 10:46
  • Well, for starters, @ExtraSun, `'29 January 2017'` is not **after** `'27 July 2020'`; but it would be according to *your* data. Why are you so against using a proper data type? It an fatal flaw... Correct your design. Use a date and time data type. End of story. – Thom A Jul 27 '21 at 10:49
  • OK, `CreatedDate DATETIME` is how I fixed it. – ExtraSun Jul 27 '21 at 10:54
  • @ExtraSun Get help. Designing databases requires a very different skillset from writing application code and writing efficient tsql. You are making many mistakes that will continue to require more effort over time and expose you (and customers) to more risk. NB you should NEVER store plain-text passwords in a database. Do your research! – SMor Jul 27 '21 at 11:16
  • @SMor Sure hashing password is in my other question - https://stackoverflow.com/questions/68543252/sql-server-how-to-insert-a-hashed-password-to-a-table – ExtraSun Jul 27 '21 at 11:18