Check constraint with multiple conditions and other table column referring - sql

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.

Related

my function returns null results and I'm not sure what is wrong (MySQL)

My function to find the cheapest property with n bedrooms is always returning null. I'm not sure what the issue is right now.
DELIMITER $$
DROP FUNCTION IF EXISTS cheapest_property_with_n_bedrooms;
CREATE FUNCTION cheapest_property_with_n_bedrooms (numBedrooms INT)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE PropertyID INT;
DECLARE bedrooms INT;
SET PropertyID = (SELECT Property.property_id
FROM Property
JOIN Listing ON Listing.property_FK = Property.property_id
JOIN Amenities ON Amenities.property_FK = Property.property_id
GROUP BY Amenities.num_bedrooms
HAVING Amenities.num_bedrooms = numBedrooms
);
RETURN PropertyID;
END$$
DELIMITER ;
CREATE TABLE IF NOT EXISTS Amenities (
amenities_id INT PRIMARY KEY AUTO_INCREMENT,
property_FK INT,
num_bedrooms INT,
num_bathrooms INT,
num_garage INT,
num_kitchen INT,
num_parking INT,
CONSTRAINT FK_AMENITIES_PROPERTY FOREIGN KEY (property_FK) REFERENCES Property(property_id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS Property (
property_id INT PRIMARY KEY AUTO_INCREMENT,
property_name VARCHAR(50),
address_FK INT,
CONSTRAINT FK_PROPERTY_ADDRESS FOREIGN KEY (address_FK) REFERENCES Address(address_id) ON DELETE CASCADE ON UPDATE CASCADE
);
My query to call the function is this, where you can replace it with any number of bedrooms desired.
SELECT cheapest_property_with_n_bedrooms (1);

How to create a Conditional foreign key in PostgreSQL?

I have the following table
create table if not exists pgroup (
id uuid primary key default gen_random_uuid(),
label varchar not null,
is_role boolean default false
);
I want to create a table like the following :
create table if not exists grouprole (
groupId uuid not null references pgroup(id) `where is_role = false`,
roleId uuid not null references pgroup(id) `where is_role = true`,
primary key (groupId, roleId)
);
the idea is that two pgroup can be in a grouprole relationship if one is a role and the other is not.
My goal is that at INSERT time a check will be performed to ensure that.
EDIT:
I cannot split pgroup in two different tables because other tables references it and don't care for the is_role flag.
Try to use an auxiliary function in CHECK constraint:
create table if not exists pgroup (
id int primary key,
label varchar not null,
is_role boolean default false
);
create table if not exists grouprole (
groupId int not null references pgroup(id),
roleId int not null references pgroup(id),
primary key (groupId, roleId)
);
CREATE FUNCTION check_pgroup(p_id int,p_is_role boolean) RETURNS int AS $$
SELECT id
FROM pgroup
WHERE id=p_id
AND is_role=p_is_role
$$ LANGUAGE SQL;
alter table grouprole add check(check_pgroup(groupId,false) is not null);
alter table grouprole add check(check_pgroup(roleId,true) is not null);
Test:
INSERT INTO pgroup(id,label,is_role)VALUES(1,'1',true);
INSERT INTO pgroup(id,label,is_role)VALUES(2,'2',false);
INSERT INTO grouprole(groupId,roleId)VALUES(1,2); -- Error
INSERT INTO grouprole(groupId,roleId)VALUES(2,1); -- OK
You also can create a cross-check into pggroup to prevent to set a bad value into is_role:
CREATE FUNCTION check_pgroup_is_role(p_id int,p_is_role boolean) RETURNS boolean AS $$
SELECT true is_exists
FROM grouprole
WHERE ((p_is_role=true AND groupId=p_id) OR (p_is_role=false AND roleId=p_id))
$$ LANGUAGE SQL;
ALTER TABLE pgroup ADD CHECK(check_pgroup_is_role(id,is_role) IS NULL);
Test:
UPDATE pgroup SET is_role=false; -- Error
INSERT INTO pgroup(id,label,is_role)VALUES(3,'3',true); -- OK
UPDATE pgroup SET is_role=false WHERE id=3; -- OK

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

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

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.