How to insert row in table with foreign key to itself? - sql

I have table that has foreign key for itself. Column parentid is foreign key and it cannot be NULL.
if I doINSERT INTO mytable(name) VALUES('name'), so it says that can't insert NULL to parentid. BUT, what value I can set to it if no row was inserted yet?!
How I can write script that will add row to this table?
Thank you

Remove the NOT NULL constraint, as it is an inappropriate constraint. If you do not have a ParentId then the value is NULL and should be allowed. Creating a dummy row just to have a dummy parentid creates unnecessary dependencies.

A trick: Have a dummy row with a dummy key, say 99999. Insert with this as the FK, and then change the FK to its real value. And do it in a transaction.

Disable the FK in charge.
Then do the insert
Then do an update with the new (generated?) PK-ID into the Self-FK-Field
Then Enable the FK back.
LIke so:
ALTER TABLE [Client] NOCHECK CONSTRAINT [FK_Client_MainClient]
INSERT INTO Client VALUES ...
#ClientID = SCOPE_IDENTITY()
IF #IsMainClient=1
BEGIN
UPDATE [Client]
SET MainClientID = #ClientID
WHERE ClientID = #ClientID
END
ALTER TABLE [Relatie] WITH CHECK CHECK CONSTRAINT [FK_Relatie_Relatie]

How to make a dummy row with both id and parentid equal to -1:
CREATE TABLE mytable(
id int NOT NULL IDENTITY,
parentid int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parentid) REFERENCES mytable(id)
) ;
SET IDENTITY_INSERT mytable ON ; <-- this allows you to insert the
INSERT INTO mytable(id, parentid) <-- auto incremented identity field
VALUES (-1, -1);
SET IDENTITY_INSERT mytable OFF ;
SELECT * FROM mytable ;
| id | parentid |
| -1 | -1 |
If you have many data from other tables that you want to transfer into this table, you can set the IDENTITY_INSERT variable to ON, insert the data and then set it to OFF again.
But as others said, it might be better to just remove the NOT NULL constraint from the parentid field.

You can alter the column to allow null then set the fk to the new identity and enable not null again.
This should work, though maybe there is a better way
CREATE TABLE mytable
(
id int IDENTITY(1,1) primary key,
name varchar(50) not null,
parentid int not null
)
go
alter table mytable
add constraint FK_mytable_parentid FOREIGN KEY ( parentid ) references mytable(id)
ALTER TABLE mytable alter column parentid int null;
insert into mytable(name)
select 'test'
update mytable
set parentid = SCOPE_IDENTITY()
where id = SCOPE_IDENTITY()
ALTER TABLE mytable alter column parentid int not null;
select * from mytable
drop table mytable

From what I understood you already have id before inserting and you can't insert it because identity field isn't letting you to.
Like you mentioned in your comment:
in 1 table I have the rows 34 'name1'
34, 35 'name2' 34 (id,name,parentid)
and I want to copy them to other table
First table
create table Table1
(
id int not null primary key,
name varchar(20) not null,
parentId int not null
)
insert Table1
values
(34, 'name1', 34),
(35, 'name2', 34)
Second table:
create table Table2
(
id int identity(1, 1) primary key,
name varchar(20) not null,
parentId int not null foreign key references Table2(id)
)
Copying data from the first table to the second one:
set identity_insert Table2 on
insert Table2(id, name, parentId)
select *
from Table1
set identity_insert Table2 on
[Update]
If the second table already has values then:
alter table Table2
add oldId int null
alter table Table2
alter column parentId int null
go
insert Table2(name, oldId)
select name, id
from Table1
update tt3
set parentId = tt2.id
from Table2 tt3
join Table1 tt1 on
tt1.id = tt3.oldId
join Table2 tt2 on
tt1.parentId = tt2.oldId
alter table Table2
drop column oldId
alter table Table2
alter column parentId int not null

Don't reference an IDENTITY column as a self-referencing foreign key. Use an alternative key of the table instead. I guess you are using IDENTITY as a surrogate key but every table ought to have a natural key as well, so the IDENTITY column shouldn't be the only key of your table.

Related

Check constraint with multiple conditions and other table column referring

I have two table called tbl_1 and tbl_2 with below schema
CREATE TABLE tbl_1(id int, disabled bit, qtype int)
and
CREATE TABLE tbl_2 (qtype int, qname nvarchar(MAX))
i want to add a constraint to the tbl_1 so that if (disabled=0 or disabled is null) qtype must be a number that exists in the tbl_2.qtype column?
I tried creating a function and added a check constraint
CREATE FUNCTION fn_Check_qtype(#qtype INT)
RETURNS int
AS
BEGIN
IF EXISTS(SELECT qtype from tbl_1 where (disabled=0 or disabled is null) and qtype=#qtype)
IF EXISTS (SELECT qtype FROM tbl_2 WHERE qtype is not null and qtype = #qtype)
return 1
return 0
END
Constraint
alter table tbl_1
add constraint ck_qtyppe
check (dbo.fn_Check_qtype(qtype) =1)
But even no non-matching records it is throwing error
The ALTER TABLE statement conflicted with the CHECK constraint
"ck_qtyppe". The conflict occurred in database "TestDB", table
"dbo.tbl_1", column 'qtype'.
But if i am deleting qtype null value from tbl_1 it is working no matter disabled=0, disabled=, disabled = null.
First, a foreign key references should be to a primary key, so let me assume tbl_2 is defined as:
CREATE TABLE tbl_2 (
qtype int primary key,
qname nvarchar(MAX)
);
Then, you can do this without a user-defined function. All you need is a persisted computed column:
CREATE TABLE tbl_1 (
id int,
disabled bit,
qtype int,
qtype_enabled as (case when disabled = 1 then qtype end) persisted,
foreign key (qtype_enabled) references tbl_2 (qtype)
);
Here is a db<>fiddle.

How to prevent the auto increment values if the conflict error is occured?

I had created two tables
Create Table Tbl_Class
(
Id int Identity,
ClassId int Primary Key,
Class Varhar(20)
)
Create Table Tbl_Subjects
(
Id Int Identity,
SubjectId int Primary Key,
Subject Varchar(20),
ClassId int Foreign key (ClassId) REFERENCE Tbl_Class(ClassId)
)
Insert into Tbl_Class values (7,'Seventh');
Insert into Tbl_Class values (8,'Eight');
Insert Into Tbl_Subject values(1,'Science',7);
If I run the above insert statement then it will insert into the table because I had passed the date which exists in the primary table and the identity column value is 1 now but if I fire this query
Insert Into Tbl_Subject values(2,'Science',9);
It gives the conflicted error but here the identity column value increased to 2
I need to prevent auto increment of column value if values not inserted into the database

Changing datatype of a column, which is referenced by other tables

I want to change the datatype of a primary key column in Table A, which is referenced by Table B. The schema is something like-
Table A: (col1A number, col2A...)
Table B: (col1B number, col2B...)
col2B -> col1A
I want to change datatype of col1A from number to varchar. I want that to reflect in Table B also. Is there any simple way to do that?
--
Thanks.
No, there is no simple way to do this. Assuming that both tables have data in them, you'd need to
Add a new VARCHAR2 column to table A
Update A to set the new column equal to TO_CHAR( col1A )
Add a new 'VARCHAR2` column to table B
Update B to set the column equal to TO_CHAR( col2B )
Drop the existing foreign key constraint
Drop the existing primary key constraint
Drop col1A from A
Drop col2B from B
Rename the new columns (if desired) in A & B to col1A and col2B
Create the new primary key constraint
Create the new foreign key constraint
Obviously, that's going to be a rather expensive operation.
This is a proof of concept in T-SQL (SQL Server). Basically, we're changing the primary key's data type from INT to UNIQUEIDENTIFIER (GUID) in table A, which is referenced by a foreign key in table B.
-- ARRANGE
CREATE TABLE A(
[Id] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT PK_A PRIMARY KEY (Id)
)
CREATE TABLE B(
[Id] INT IDENTITY(1,1) NOT NULL,
[A_Id] INT NOT NULL,
CONSTRAINT PK_B PRIMARY KEY (Id),
CONSTRAINT FK_B_A FOREIGN KEY (A_Id) REFERENCES A(Id)
)
INSERT A DEFAULT VALUES
DECLARE #A_Id INT
SELECT #A_Id = SCOPE_IDENTITY()
INSERT INTO B VALUES (#A_Id)
INSERT INTO B VALUES (#A_Id)
INSERT INTO B VALUES (#A_Id)
INSERT INTO B VALUES (#A_Id)
INSERT INTO B VALUES (#A_Id)
-- ACT
ALTER TABLE A ADD New_Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID()
ALTER TABLE B ADD New_A_Id UNIQUEIDENTIFIER NULL
UPDATE B SET B.New_A_Id = (SELECT A.New_Id FROM A WHERE A.Id = B.A_Id)
ALTER TABLE B DROP FK_B_A
ALTER TABLE B DROP COLUMN A_Id
EXEC sp_RENAME 'B.New_A_Id', 'A_Id', 'COLUMN'
ALTER TABLE A DROP PK_A
ALTER TABLE A DROP COLUMN Id
ALTER TABLE A ADD CONSTRAINT PK_A PRIMARY KEY (New_Id)
EXEC sp_RENAME 'A.New_Id', 'Id', 'COLUMN'
ALTER TABLE B ADD CONSTRAINT FK_B_A FOREIGN KEY (A_Id) REFERENCES A(Id)
-- ASSERT
SELECT * FROM A
SELECt * FROM B

Adding a nullable foreign key

I have two tables built like this (this is just a simplified and non-proprietary example):
Person Table
-----------
p_Id, f_name, l_name
Job Table
----------
job_Id, job_desc
I want to add a foreign key column, Persons.job_Id, that can be nullable that references Job.job_Id (the PK) The reason is, the job may not be known in advance, so it could be null. Having an "Other" is not an option.
I had this so far but I'm getting "could not create constraint".
ALTER TABLE dbo.Person
ADD job_Id INT FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id)
Try it in two steps:
ALTER TABLE dbo.Person ADD job_Id INT NULL;
ALTER TABLE dbo.Person ADD CONSTRAINT FL_JOB
FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id);
Try it like this, WITH NOCHECK:
ALTER TABLE dbo.Person ADD job_Id INT NULL;
ALTER TABLE dbo.Person WITH NOCHECK ADD CONSTRAINT FL_JOB
FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id);
Below is my solution with creating foreign key programmatically.
TestTable1 has substitute of FK that is either NULL or matches record in TestTable2.
TestTable2 has standard FK in TestTable1.
CREATE Table TestTable1 (ID1 int IDENTITY UNIQUE, ID2 int NULL);
GO
CREATE Table TestTable2 (ID2 int IDENTITY UNIQUE, ID1 int NOT NULL foreign key references TestTable1(ID1));
GO
CREATE procedure CreateTestRecord1 #ID2 int null AS
begin
if #iD2 IS NOT NULL AND NOT EXISTS(SELECT * from TestTable2 where ID2 = #ID2)
begin
RAISERROR('Cannot insert TestTable1 record. TestTable2 record with ID %d doesnt exist', 16, 1, #ID2);
return;
end
Insert into TestTable1(ID2) OUTPUT Inserted.ID1 Values(#ID2);
end
GO
CREATE procedure LinkTable1toTable2 #ID1 int, #ID2 int NULL as
begin
if #iD2 IS NOT NULL AND NOT EXISTS(SELECT * from TestTable2 where ID2 = #ID2)
begin
RAISERROR('Cannot update ID2 in TestTable1 record. TestTable2 record with ID %d doesnt exist', 16, 1, #ID2);
return;
end
update TestTable1 Set ID2=#ID2 where ID1=#ID1;
select ##ROWCOUNT;
endGO

with 2 field that allow null how to make a constrain that at only one must be filled?

to simplify this let take that table:
table1
-------------
id unique primary int
myVal1 int null (fk)
myVal2 int null (fk)
myData int not null
what would be the best way to create a constrain on this table so only one value can be filled?
these would work:
insert into table1 (myval1,myData) values (1,234)
insert into table1 (myval2,myData) values (1,123)
these would not work:
insert into table1 (myData) values (234)
insert into table1 (myVal1,myval2,myData) values (1,2,123)
try using a check constraint:
CREATE TABLE dbo.Table1
(
rowID int NOT NULL primary key identity(1,1),
myVal1 int NULL,
myVal2 int NULL,
myData int NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Table1 ADD CONSTRAINT
CK_Table1_myVal1_or_myVal2 CHECK ((myVal2 IS NOT NULL AND myVal1 IS NULL) OR (myVal2 IS NULL AND myVal1 IS NOT NULL))
GO