Insert Procedure check if foreign key exists in another table - tsql

I am running into an issue where I am supposed to create a stored procedure. However my stored procedure needs to check if columns exists in a different table, this is my code at the moment:
CREATE PROCEDURE InsertTrail (
#Mount int,
#Skill varchar(20),
#Snow int,
#Lift int,
#Open char(3),
#AmountReal int,
#AmountFake int,
#Name varchar(50)
)
AS
BEGIN
If not exists (select mountainid from MOUNTAIN where #mount = mountainid)
and
If not exists (select skilllevel from SKILLLEVEL where #skill = SkillLevel)
and
If not exists (select snowmakerID from SNOWMAKER where #snow = SnowMakerID)
and
If not exists (select liftID from LIFT where #lift = LiftID)
BEGIN
INSERT INTO TRAIL (MountainID, SkillLevel, SnowMakerID, LiftID, TOpen, AmountRealSnow, AmountFakeSnow, TrailName)
values
(#Mount, #Skill, #Snow, #Lift, #Open, #AmountReal, #AmountFake, #Name)
Return ##Identity
END
END
The columns I need to make sure exist are the mountainid in the mountain table, the skilllevel in the SKILLLEVEL table, the snowmakerid in the snowmakertable and the liftID in the lift table.
If you need any more information please let me know! Please help if you can!

Currently you're inserting your value only if none of these values exist. If you want to check if all values exists before inserting you should use IF EXISTS instead of IF NOT EXISTS.
Still, I think the easiest way to achieve this is to make foreign keys and let the database administrator handle this validations.
I'm assuming SQL SERVER per your sample code's syntaxis. Correct me if I'm wrong to change the query:
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_MOUNTAIN] FOREING KEY [MountainID]
REFERENCES [MOUNTAIN] ([MountainID])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_SKILLLEVEL] FOREING KEY [SkillLevel]
REFERENCES [SKILLLEVEL] ([SkillLevel])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_SNOWMAKER] FOREING KEY [SnowMakerID]
REFERENCES [SNOWMAKER] ([SnowMakerID])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_LIFT] FOREING KEY [LiftID]
REFERENCES [LIFT] ([LiftID])
Also IMO you should also check these values on client-side, and just let your SP "crash" when there's a non-referenced value.

Related

tSQLt will not apply misnamed constraints (PK_dbo ....) to fake table

Adding tSQLt tests to an existing production product, so we're not able to alter tables, constraints, etc. Currently all the constraints are labeled like 'PK_dbo.ViolationCategory' when they should be like 'PK_ViolationCategory'
When I run:
EXEC tSQLt.NewTestClass 'AdHocReportFiltersTestConstraint';
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[Setup]
AS
BEGIN
EXEC tSQLt.FakeTable 'dbo.AdHocReports'
END
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint]
AS
BEGIN
DECLARE #Id uniqueidentifier = NEWID()
DECLARE #Name NVARCHAR(Max) = 'Test_Name'
DECLARE #Value NVARCHAR(Max) = 'Test_Value'
DECLARE #AdHocReport_ID uniqueidentifier = NEWID()
INSERT INTO dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id])
VALUES (#Id, #Name, #Value, #AdHocReport_ID)
exec tSQLt.ApplyConstraint 'dbo.AdHocReports', 'PK_dbo.AdHocReportFilters';
END
EXEC tSQLt.RunTestClass 'AdHocReportFiltersTestConstraint';
GO
I receive the error,
(1 row affected)
[AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint] failed:
(Error) The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id". The conflict occurred in
database "CR", table "dbo.tSQLt_tempobject_fbc9c8bf09e742929eccae914d5e440d",
column 'Id'.[16,0]{test_AdHocReportFilters_Constraint,11}
Any ideas of how to work around this?
Once I get this working, I will add a second record to violate the PK constraint and catch the error.
Just looking at your code, I see you are doing the following:
Fake the table dbo.AdHocReports
Insert a row into the table dbo.AdHocReportFilters
Apply a PK constraint called "PK_dbo.AdHocReportFilters" to the table dbo.AdHocReports
You will then try and add another row to validate the PK_dbo.AdHocReportFilters constraint
The error you are getting appears to to suggest that you are violating a foreign key on the AdHocReportFilters table - which is expected since that table hasn't been faked.
It is not clear from the test name whether you are trying to validate the behaviour of the primary key or foreign key.
Looking at the steps, I think you may be mixing up the two tables but without more detailed code (i.e. CREATE TABLE) statements, it is difficult for me to help you further.
There are two tables:
dbo.AdHocReports (Id,... (more columns))
dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id] )
AdHocReportFilters.AdHocReport_Id is foriegn key for table dbo.AdHocReportFilters referencing dbo.AdHocReports.Id
When you are inserting insert a row in table AdHocReportFilters, you should make sure AdHocReportFilters.AdHocReport_Id is an id column in table dbo.AdHocReports
But in your SP, #AdHocReport_ID is NEWID(), which is of course a unique value and hence not in table dbo.AdHocReports.Id
Work around,
Disable foreign key contraint
ALTER TABLE dbo.AdHocReportFilters NOCHECK CONSTRAINT FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id

Is it possible to create a Foreign Key on 2 columns with differing Collations?

I've tried searching for about an hour through all the Foreign Key / Collation Questions but I can't find something even remotely close to my question.
I have 2 tables from two different software vendors sitting in the same database. One vendor hard codes their collations to Latin1_General_BIN while the other uses the Database Default (in this case Latin1_General_CI_AS). Is it possible, without altering any of the columns, to create Foreign Keys between these two Collation Types?
Typically you would simply change one but in this case the Tables are very sensitive and I'm not allowed to make such a change but I have to create a Foreign Key due to a piece of logic in a Trigger that reads data between these two tables only if it finds a Foreign Key:
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
WHERE CONSTRAINT_NAME =
(
SELECT name FROM sys.foreign_keys
WHERE parent_object_id = OBJECT_ID( 'Table1' )
AND referenced_object_id = OBJECT_ID( 'Table2' )
)
Any help would really be appreciate
P.S. I just can't seem to figure out how this code thing works if anyone would help me out, I put in the 4 required spaces but it's still just displaying my code as text :(
Adding a foreign key constraint from a field of one collation to a field of another collation can't be done. You will get error message 1757.
Either change the collation of one of the tables or create a work around with a new column that is used instead with the correct collation or create surrogate key columns with integers used for referencing.
If nothing else works and you really really need to fix this type of constraint and performance is not an issue, add a check constraints and/or triggers that will check the referential integrity of data put into the tables. These rules will have to cast all values in one table to the collation of the other in order to compare values so it will be slow and it will be really tricky for you to get use of indexes, proceed with caution.
For example you could have an insert trigger on the referencing table that check if a record with the inserted string exists in the referenced table. Then you would also have to add an update and delete trigger for the referenced table so that it doesn't fall out of range of values that are referenced by records in the referencing table or which cascades updates/deletes. Basically you replicate what foreign keys are and it gets really slow and scales horribly.
Short answer: don't do it, let the tables stay untied or fix the collation of one of them.
Sweet, I think the solution is very elegant. I'm writing it as an answer purely as it's the full alternative that closest resembles the required solution. But I'm going to mark your answer as the answer as it's the one that correctly answers my original question.
Right, so first what I did, was I got permission from the vendor who's trigger requires the foreign key, to create a new column in their table as a persisted computed column in the collation of the other vendors table:
DECLARE #Collation nvarchar(100)
DECLARE #SQL nvarchar(1000)
SET #Collation = ( SELECT collation_name FROM sys.columns WHERE OBJECT_ID IN ( SELECT OBJECT_ID FROM sys.objects WHERE type = 'U' AND name = 'Vendor1Table' ) AND name = 'Vendor1Column' )
SET #SQL = 'ALTER TABLE [Vendor2Table] ADD [Vendor2ComputedColumn] AS [Vendor2Column] COLLATE ' + #Collation + ' PERSISTED'
EXECUTE( #SQL )
GO
Next up, I added a candidate key to the computed column:
ALTER TABLE [Vendor2Table] ADD CONSTRAINT [CCUNQ1_x] UNIQUE NONCLUSTERED
(
[Vendor2ComputedColumn] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
Then, I simply created the foreign key to the computed column:
ALTER TABLE [dbo].[Vendor1Table] WITH CHECK ADD CONSTRAINT [CCFOK01_x] FOREIGN KEY ( [Vendor1Column] )
REFERENCES [dbo].[Vendor2Table] ( [Vendor2ComputedColumn] )
GO
ALTER TABLE [dbo].[Vendor1Table] CHECK CONSTRAINT [CCFOK01_x]
GO
and finally, the original SQL Script passes with flying colours:
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
WHERE CONSTRAINT_NAME =
(
SELECT name FROM sys.foreign_keys
WHERE parent_object_id = OBJECT_ID( 'Vendor1Table' )
AND referenced_object_id = OBJECT_ID( 'Vendor2Table' )
)
Hopefully this small walkthrough helps some other soul some day :)
Thanks for the assist David, appreciate it!

Set column as primary key if the table doesn't have a primary key

I have a column in db which has 5 columns but no primary key.
One of the columns is named myTable_id and is integer.
I want to check if the table has a primary key column. If it doesn't, then make myTable_id a primary key column and make it identity column. Is there a way to do this?
I tried with this:
ALTER TABLE Persons
DROP CONSTRAINT pk_PersonID
ALTER TABLE Persons
ADD PRIMARY KEY (P_Id)
and I get syntax error in Management studio.
This checks if primary key exists, if not it is created
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'Persons'
AND TABLE_SCHEMA ='dbo')
BEGIN
ALTER TABLE Persons ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
END
ELSE
BEGIN
-- Key exists
END
fiddle: http://sqlfiddle.com/#!6/e165d/2
ALTER TABLE Persons
ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
An IDENTITY constraint can't be added to an existing column, so how you add this needs to be your initial thought. There are two options:
Create a new table including a primary key with identity and drop the existing table
Create a new primary key column with identity and drop the existing 'P_ID' column
There is a third way, which is a better approach for very large tables via the ALTER TABLE...SWITCH statement. See Adding an IDENTITY to an existing column for an example of each. In answer to this question, if the table isn't too large, I recommend running the following:
-- Check that the table/column exist and no primary key is already on the table.
IF COL_LENGTH('PERSONS','P_ID') IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'PERSONS')
-- Add table schema to the WHERE clause above e.g. AND TABLE_SCHEMA ='dbo'
BEGIN
ALTER TABLE PERSONS
ADD P_ID_new int IDENTITY(1, 1)
GO
ALTER TABLE PERSONS
DROP COLUMN P_ID
GO
EXEC sp_rename 'PERSONS.P_ID_new', 'P_ID', 'Column'
GO
ALTER TABLE PERSONS
ADD CONSTRAINT PK_P_ID PRIMARY KEY CLUSTERED (P_ID)
GO
END
Notes:
By explicitly using the CONSTRAINT keyword the primary key constraint is given a particular name rather than depending on SQL Server to auto-assign a name.
Only include CLUSTERED on the PRIMARY KEY if the balance of searches for a particular P_ID and the amount of writing outweighs the benefits of clustering the table by some other index. See Create SQL IDENTITY as PRIMARY KEY.
You can check if primary key exists or not using OBJECTPROPERTY Transact SQL, use 'TableHasPrimaryKey' for the second arguments.
DECLARE #ISHASPRIMARYKEY INT;
SELECT #ISHASPRIMARYKEY = OBJECTPROPERTY(OBJECT_ID('PERSONS'), 'TABLEHASPRIMARYKEY');
IF #ISHASPRIMARYKEY IS NULL
BEGIN
-- generate identity column
ALTER TABLE PERSONS
DROP COLUMN P_ID;
ALTER TABLE PERSONS
ADD P_ID INT IDENTITY(1,1);
-- add primary key
ALTER TABLE PERSONS
ADD CONSTRAINT PK_PERSONID PRIMARY KEY (P_ID);
END;
I don't think you can do that. For making a column into an identity column I think you have to drop the table entirely.

Adding Foreign Key, SQL SERVER 2008

I am trying to add a foreign key to a table, and it give me the following error:
There are no primary or candidate keys in the referenced table 'tbl_Person' that match the referencing column list in the foreign key 'P_ID'.
I have a tbl_Person, which is defined as:
P_ID INT (Primary Key)
f_Name,
l_Name
the other table is a comments table which is defined as:
C_ID INT,
Comments,
P_ID (should be the foreign key)
Trying to make a one to many relationship table, so when the user add a comment, it is referenced back to him, also, he can add onto the comments without initializing a new comment. Hopefully that makes a little sense.
Ex: Randy Bing enter "I love SQL", his ID is 1, f_Name is Randy, l_Name is Bing, his comments are "I love Sql". His comments should store a unique ID, as well as import his P_ID.
Later on when Randy wants to add onto the comment with the same C_ID where P_ID matches him without creating a new C_ID.
Here is the Code:
ALTER TABLE tbl_Comments
ADD CONSTRAINT P_ID
FOREIGN KEY (P_ID)
REFERENCES tbl_Person(P_ID)
Am I close to being on the right track?
This error usually means the datatypes are different between "Comments" and "Person", assuming this is the actual message
The SQL should be this
ALTER TABLE tbl_Comments WITH CHECK ADD
CONSTRAINT FK_Comments_Person FOREIGN KEY (P_ID) REFERENCES tbl_Person (P_ID)
This matches what you added. So:
check datatypes are both int
ensure P_ID is primary key on tbl_Person
(Edit, Dec 2011) collation and length must be the same for varchar columns too
In Object Explorer, connect to an instance of Database Engine.
On the Standard bar, click New Query.
The example creates a foreign key on the column TempID and references the column SalesReasonID in the Sales.SalesReason table.
USE AdventureWorks2012;
GO
ALTER TABLE Sales.TempSalesReason
ADD CONSTRAINT FK_TempSales_SalesReason FOREIGN KEY (TempID)
REFERENCES Sales.SalesReason (SalesReasonID)
ON DELETE CASCADE
ON UPDATE CASCADE
;
GO
the name of your constraint, p_id,
clashes with the name of the p_id column

Foreign keys in postgresql can be violated by trigger

I've created some tables in postgres, added a foreign key from one table to another and set ON DELETE to CASCADE. Strangely enough, I have some fields that appear to be violating this constraint.
Is this normal behaviour? And if so, is there a way to get the behaviour I want (no violations possible)?
Edit:
I orginaly created the foreign key as part of CREATE TABLE, just using
... REFERENCES product (id) ON UPDATE CASCADE ON DELETE CASCADE
The current code pgAdmin3 gives is
ALTER TABLE cultivar
ADD CONSTRAINT cultivar_id_fkey FOREIGN KEY (id)
REFERENCES product (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
Edit 2:
To Clarify, I have a sneaking suspicion that the constraints are only checked when updates/inserts happen but are then never looked at again. Unfortunately I don't know enough about postgres to find out if this is true or how fields could end up in the database without those checks being run.
If this is the case, is there some way to check all the foreign keys and fix those problems?
Edit 3:
A constraint violation can be caused by a faulty trigger, see below
I tried to create a simple example that shows foreign key constraint being enforced. With this example I prove I'm not allowed to enter data that violates the fk and I prove that if the fk is not in place during insert, and I enable the fk, the fk constraint throws an error telling me data violates the fk. So I'm not seeing how you have data in the table that violates a fk that is in place. I'm on 9.0, but this should not be different on 8.3. If you can show a working example that proves your issue that might help.
--CREATE TABLES--
CREATE TABLE parent
(
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_parent PRIMARY KEY (parent_id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE parent OWNER TO postgres;
CREATE TABLE child
(
child_id integer NOT NULL,
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_child PRIMARY KEY (child_id),
CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
OIDS=FALSE
);
ALTER TABLE child OWNER TO postgres;
--CREATE TABLES--
--INSERT TEST DATA--
INSERT INTO parent(parent_id,first_name)
SELECT 1,'Daddy'
UNION
SELECT 2,'Mommy';
INSERT INTO child(child_id,parent_id,first_name)
SELECT 1,1,'Billy'
UNION
SELECT 2,1,'Jenny'
UNION
SELECT 3,1,'Kimmy'
UNION
SELECT 4,2,'Billy'
UNION
SELECT 5,2,'Jenny'
UNION
SELECT 6,2,'Kimmy';
--INSERT TEST DATA--
--SHOW THE DATA WE HAVE--
select parent.first_name,
child.first_name
from parent
inner join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--SHOW THE DATA WE HAVE--
--DELETE PARENT WHO HAS CHILDREN--
BEGIN TRANSACTION;
delete from parent
where parent_id = 1;
--Check to see if any children that were linked to Daddy are still there?
--None there so the cascade delete worked.
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
BEGIN TRANSACTION;
--Get rid of fk constraint so we can insert red headed step child
ALTER TABLE child DROP CONSTRAINT fk1_child;
INSERT INTO child(child_id,parent_id,first_name)
SELECT 7,99999,'Red Headed Step Child';
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--Will throw FK check violation because parent 99999 doesn't exist in parent table
ALTER TABLE child
ADD CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
--DROP TABLE parent;
--DROP TABLE child;
Everything I've read so far seems to suggest that constraints are only checked when the data is inserted. (Or when the constraint is created) For example the manual on set constraints.
This makes sense and - if the database works properly - should be good enough. I'm still curious how I managed to circumvent this or if I just read the situation wrong and there was never a real constraint violation to begin with.
Either way, case closed :-/
------- UPDATE --------
There was definitely a constraint violation, caused by a faulty trigger. Here's a script to replicate:
-- Create master table
CREATE TABLE product
(
id INT NOT NULL PRIMARY KEY
);
-- Create second table, referencing the first
CREATE TABLE example
(
id int PRIMARY KEY REFERENCES product (id) ON DELETE CASCADE
);
-- Create a (broken) trigger function
--CREATE LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_product()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM product WHERE product.id = OLD.id;
-- This is an error!
RETURN null;
END;
$BODY$
LANGUAGE plpgsql;
-- Add it to the second table
CREATE TRIGGER example_delete
BEFORE DELETE
ON example
FOR EACH ROW
EXECUTE PROCEDURE delete_product();
-- Now lets add a row
INSERT INTO product (id) VALUES (1);
INSERT INTO example (id) VALUES (1);
-- And now lets delete the row
DELETE FROM example WHERE id = 1;
/*
Now if everything is working, this should return two columns:
(pid,eid)=(1,1). However, it returns only the example id, so
(pid,eid)=(0,1). This means the foreign key constraint on the
example table is violated.
*/
SELECT product.id AS pid, example.id AS eid FROM product FULL JOIN example ON product.id = example.id;