How to ensure my SQL varchar field is unique - sql

I'm sure this is really simple but I've been up through the night and am now getting stuck.
I have a piece of functionality that clones a record in a database however I need to ensure the new name field is unique in the database.
eg, the first record is
[ProjectName] [ResourceCount]
'My Project' 8
Then when I click the clone I want
'My Project Cloned', 8
But then if I hit the button again it should notice that the cloned name exists and rather spit out
'My Project Cloned 2', 8
Is that making sense?
I can do it with temp tables and cursors but there has to be a much nicer way to do this?
Using SQL Server 2008 R2
The solution needs to be entirely T-SQL based though, this occurs in a single stored procedure

So from my understanding of your problem, here's how I would approach it:
My table:
CREATE TABLE [dbo].[deal]
(
[dealName] varchar(100),
[resourceCount] int
)
Then create a unique index on the dealName column:
CREATE UNIQUE NONCLUSTERED INDEX [UQ_DealName] ON [dbo].[deal]
(
[dealName] ASC
)
Once you have the unique index, you can then just handle any exceptions such as a unique constraint violation (error 2601) directly in T-SQL using try/catch
SET NOCOUNT ON;
DECLARE #dealName VARCHAR(100) = 'deal'
DECLARE #resourceCount INT = 8
DECLARE #count INT
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO dbo.deal (dealName,resourceCount)
VALUES (#dealName, #resourceCount)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##ERROR = 2601
BEGIN
ROLLBACK TRANSACTION
SET #count = (SELECT COUNT(dealName) FROM dbo.deal WHERE resourceCount = #resourceCount)
SET #resourceCount = (SELECT resourceCount FROM dbo.deal WHERE dealName = #dealName)
SET #dealName = #dealName + ' Cloned ' + CAST(#count AS VARCHAR(100))
BEGIN TRANSACTION
INSERT INTO dbo.deal (dealName,resourceCount)
VALUES (#dealName,#resourceCount)
COMMIT TRANSACTION
END
END CATCH
SELECT * FROM dbo.deal
You can easily put this code into a procedure, all it does is try and insert a deal name with the resource count, if the unique constraint is violated, it enters the catch block, appends the information that you want onto the deal name after finding the resource count of the original deal and then inserts these values.
It's not bulletproof by any means, but I find this technique really useful, not just for enforcing uniqueness, but you can use a similar way of handling exception numbers to deal with deadlocking, primary key violations and loads of other errors, all in T-SQL.

Ensuring the value is unique is easy: Create a unique constraint. If a unique value is inserted MSSQL will throw an exception and you can recover in your application.
Creating a unique name based on a counter (Proj1, Proj2, etc.) is a bit more involved.
Note, this is best mitigated in the web layer, where you can perform an existence check and inform the user prior to attempting the insert that the project name "is already in use." And, if this isnt an option, there are far simpler methods of ensuring uniqueness than enumerating a count as you've described. Appending a datetime or guid would make things relatively easy and would greatly (if not completely) avoid race conditions.
If you absolutely must implement in t-sql as requested then incorporating a counter column somewhere (ie, my "sequence" table below) should help minimize race conditions. I suspect even with the below example you might see some contention under high frequency calls.
--setup
/*
--your existing table
create table dbo.Project
(
[ProjectName] varchar(100) primary key,
[ResourceCount] int
);
--a new table to transactionally constrain the increment
create table dbo.ProjectNameSequence (ProjectName varchar(100) primary key, Seq int);
--cleanup
--drop table dbo.ProjectNameSequence
--drop table dbo.Project
*/
declare #ProjectName varchar(100), #ResourceCount int;
set #ProjectName = 'Test Project XX';
set #ResourceCount = 9;
merge dbo.ProjectNameSequence [d]
using (values(#ProjectName)) [s] (ProjectName) on
d.ProjectName = s.ProjectName
when matched then update set Seq += 1
when not matched then insert values(#ProjectName, 1)
output #ProjectName + case inserted.Seq when 1 then '' else cast(inserted.Seq as varchar) end,
#ResourceCount
into dbo.Project;
select * from dbo.Project

I resolve this using an IF EXISTS inside a WHILE loop..
Personally I can't see what's wrong with this method but will obviously take any comments into account
DECLARE #NameInvalid varchar(100)
DECLARE #DealName varchar(100)
DECLARE #Count int
SET #Count = 1
SET #NameInvalid = 'true'
SELECT #DealName = DealName FROM Deal WHERE DealId = #DealId
--Ensure we get a unique deal name
WHILE( #NameInvalid = 'true')
BEGIN
IF NOT EXISTS(SELECT DealName FROM Deal where DealName = #DealName + ' Cloned ' + cast(#Count as varchar(10)))
BEGIN
INSERT INTO Deal
(DealName)
SELECT #DealName + ' Cloned ' + cast(#Count as varchar(10))
FROM Deal
WHERE DealID = #DealId
SET #NewDealId = ##IDENTITY
SET #NameInvalid = 'false'
END
ELSE
BEGIN
SET #NameInvalid = 'true'
SET #Count = #Count + 1
END
END

Related

Generate a unique column sequence value based on a query handling concurrency

I have a requirement to automatically generate a column's value based on another query's result. Because this column value must be unique, I need to take into consideration concurrent requests. This query needs to generate a unique value for a support ticket generator.
The template for the unique value is CustomerName-Month-Year-SupportTicketForThisMonthCount.
So the script should automatically generate:
AcmeCo-10-2019-1
AcmeCo-10-2019-2
AcmeCo-10-2019-3
and so on as support tickets are created. How can ensure that AcmeCo-10-2019-1 is not generated twice if two support tickets are created at the same time for AcmeCo?
insert into SupportTickets (name)
select concat_ws('-', #CustomerName, #Month, #Year, COUNT())
from SupportTickets
where customerName = #CustomerName
and CreatedDate between #MonthStart and #MonthEnd;
One possibility:
Create a counter table:
create table Counter (
Id int identify(1,1),
Name varchar(64)
Count1 int
)
Name is a unique identifier for the sequence, and in your case name would be CustomerName-Month-Year i.e. you would end up with a row in this table for every Customer/Year/Month combination.
Then write a stored procedure similar to the following to allocate a new sequence number:
create procedure [dbo].[Counter_Next]
(
#Name varchar(64)
, #Value int out -- Value to be used
)
as
begin
set nocount, xact_abort on;
declare #Temp int;
begin tran;
-- Ensure we have an exclusive lock before changing variables
select top 1 1 from dbo.Counter with (tablockx);
set #Value = null; -- if a value is passed in it stuffs us up, so null it
-- Attempt an update and assignment in a single statement
update dbo.[Counter] set
#Value = Count1 = Count1 + 1
where [Name] = #Name;
if ##rowcount = 0 begin
set #Value = 10001; -- Some starting value
-- Create a new record if none exists
insert into dbo.[Counter] ([Name], Count1)
select #Name, #Value;
end;
commit tran;
return 0;
end;
You could look into using a TIME type instead of COUNT() to create unique values. That way it is much less likely to have duplicates. Hope that helps

Prevent circular reference in MS-SQL table

I have a Account table with ID and ParentAccountID. Here is the scripts to reproduce the steps.
If the ParentAccountID is NULL then that is considered as Top level account.
Every account should finally ends with top level account i.e ParentAccountID is NULL
Declare #Accounts table (ID INT, ParentAccountID INT )
INSERT INTO #Accounts values (1,NULL), (2,1), (3,2) ,(4,3), (5,4), (6,5)
select * from #Accounts
-- Request to update ParentAccountID to 6 for the ID 3
update #Accounts
set ParentAccountID = 6
where ID = 3
-- Now the above update will cause circular reference
select * from #Accounts
When request comes like to update ParentAccountID of an account, if that cause circular reference then before update its need to identified.
Any idea folks!!
It seems you've got some business rules defined for your table:
All chain must end with a top-level account
A chain may not have a circular reference
You have two ways to enforce this.
You can create a trigger in your database, and check the logic in the trigger. This has the benefit of running inside the database, so it applies to every transaction, regardless of the client. However, database triggers are not always popular. I see them as a side effect, and they can be hard to debug. Triggers run as part of your SQL, so if they are slow, your SQL will be slow.
The alternative is to enforce this logic in the application layer - whatever is talking to your database. This is easier to debug, and makes your business logic explicit to new developers - but it doesn't run inside the database, so you could end up replicating the logic if you have multiple client applications.
Here is an example that you could use as a basis to implement a database constraint that should prevent circular references in singular row updates; I don't believe this will work to prevent a circular reference if multiple rows are updated.
/*
ALTER TABLE dbo.Test DROP CONSTRAINT chkTest_PreventCircularRef
GO
DROP FUNCTION dbo.Test_PreventCircularRef
GO
DROP TABLE dbo.Test
GO
*/
CREATE TABLE dbo.Test (TestID INT PRIMARY KEY,TestID_Parent INT)
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 1 AS TestID,NULL AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 2 AS TestID,1 AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 3 AS TestID,2 AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 4 AS TestID,3 AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 5 AS TestID,4 AS TestID_Parent
GO
GO
CREATE FUNCTION dbo.Test_PreventCircularRef (#TestID INT,#TestID_Parent INT)
RETURNS INT
BEGIN
--FOR TESTING:
--SELECT * FROM dbo.Test;DECLARE #TestID INT=3,#TestID_Parent INT=4
DECLARE #ParentID INT=#TestID
DECLARE #ChildID INT=NULL
DECLARE #RetVal INT=0
DECLARE #Ancestors TABLE(TestID INT)
DECLARE #Descendants TABLE(TestID INT)
--Get all descendants
INSERT INTO #Descendants(TestID) SELECT TestID FROM dbo.Test WHERE TestID_Parent=#TestID
WHILE (##ROWCOUNT>0)
BEGIN
INSERT INTO #Descendants(TestID)
SELECT t1.TestID
FROM dbo.Test t1
LEFT JOIN #Descendants relID ON relID.TestID=t1.TestID
WHERE relID.TestID IS NULL
AND t1.TestID_Parent IN (SELECT TestID FROM #Descendants)
END
--Get all ancestors
--INSERT INTO #Ancestors(TestID) SELECT TestID_Parent FROM dbo.Test WHERE TestID=#TestID
--WHILE (##ROWCOUNT>0)
--BEGIN
-- INSERT INTO #Ancestors(TestID)
-- SELECT t1.TestID_Parent
-- FROM dbo.Test t1
-- LEFT JOIN #Ancestors relID ON relID.TestID=t1.TestID_Parent
-- WHERE relID.TestID IS NULL
-- AND t1.TestID_Parent IS NOT NULL
-- AND t1.TestID IN (SELECT TestID FROM #Ancestors)
--END
--FOR TESTING:
--SELECT TestID AS [Ancestors] FROM #Ancestors;SELECT TestID AS [Descendants] FROM #Descendants;
IF EXISTS (
SELECT *
FROM #Descendants
WHERE TestID=#TestID_Parent
)
BEGIN
SET #RetVal=1
END
RETURN #RetVal
END
GO
ALTER TABLE dbo.Test
ADD CONSTRAINT chkTest_PreventCircularRef
CHECK (dbo.Test_PreventCircularRef(TestID,TestID_Parent) = 0);
GO
SELECT * FROM dbo.Test
--This is problematic as it creates a circular reference between TestID 3 and 4; it is now prevented
UPDATE dbo.Test SET TestID_Parent=4 WHERE TestID=3
Dealing with self-referencing tables / recursive relationships in SQL is not simple. I suppose this is evidenced by the fact that multiple people can't get their heads around the problem with just checking for single-depth cycles.
To enforce this with table constraints, you would need a check constraint based on a recursive query. At best that's DBMS-specific support, and it may not perform well if it has to be run on every update.
My advice is to have the code containing the UPDATE statement enforce this. That could take a couple of forms. In any case if it needs to be strictly enforced it may require limiting UPDATE access into the table to a service account used by a stored proc or external service.
Using a stored procedure would be vary similar to a CHECK constraint, except that you could use procedural (iterative) logic to look for cycles before doing the update. It has become unpopular to put too much logic in stored procs, though, and whether this type of check should be done is a judgement call from team to team / organization to organization.
Likewise using a service-based approach would let you use procedural logic to look for cycles, and you could write it in a language better suited to such logic. The issue here is, if services aren't part of your architecture then it's a bit heavy-weight to introduce a whole new layer. But, a service layer is probably considered more modern/popular (at the moment at least) than funneling updates through stored procs.
With those approaches in mind - and understanding that both procedural and recursive syntax in databases is DBMS-specific - there are too many possible syntax options to really go into. But the idea is:
Examine the proposed parent.
Check it's parent recursively
Do you ever reach the proposed child before reaching a top-level account? IF not, allow the update
Finally, I have created the scripts after some failures, its working fine for me.
-- To hold the Account table data
Declare #Accounts table (ID INT, ParentAccountID INT)
-- To be updated
Declare #AccountID int = 4;
Declare #ParentAccountID int = 7;
Declare #NextParentAccountID INT = #ParentAccountID
Declare #IsCircular int = 0
INSERT INTO #Accounts values (1, NULL), (2,1), (3,1) ,(4,3), (5,4), (6,5), (7,6), (8,7)
-- No circular reference value
--Select * from #Accounts
-- Request to update ParentAccountID to 7 for the Account ID 4
update #Accounts
set ParentAccountID = #ParentAccountID
where ID = #AccountID
Select * from #Accounts
WHILE(1=1)
BEGIN
-- Take the ParentAccountID for #NextParentAccountID
SELECT #NextParentAccountID = ParentAccountID from #Accounts WHERE ID = #NextParentAccountID
-- If the #NextParentAccountID is NULL, then it reaches the top level account, no circular reference hence break the loop
IF (#NextParentAccountID IS NULL)
BEGIN
BREAK;
END
-- If the #NextParentAccountID is equal to #AccountID (to which the update was done) then its creating circular reference
-- Then set the #IsCircular to 1 and break the loop
IF (#NextParentAccountID = #AccountID )
BEGIN
SET #IsCircular = 1
BREAK
END
END
IF #IsCircular = 1
BEGIN
select 'CircularReference' as 'ResponseCode'
END

SQL stored procedure inserting duplicate OrderNumber

I searched the Internet for days, no effort, maybe I cant ask in a right way.
I have a sql table like this:
create table Items
(
Id int identity(1,1),
OrderNumber varchar(7),
ItemName varchar(255),
Count int
)
Then I have a stored procedure inserting items, on demand creating new OrderNumber:
create procedure spx_insertItems
#insertNewOrderNr bit,
#orderNumber varchar(7),
#itemName varchar(255),
#count int
as
begin
set nocount on;
if (#insertNewOrderNr = 1)
begin
declare #newNr = (select dbo.fun_getNewOrderNr())
INSERT INTO Items (OrderNumber, ItemName, Count) values (#newNr, #itemName, #count)
select #newNr
end
else
begin
INSERT INTO Items (OrderNumber, ItemName, Count) values (#orderNumber, #itemName, #count)
select scope_identity()
end
end
Finally there is a user defined function returning new OrderNumber:
create function dbo.fun_getNewOrderNr
()
return varchar(7)
as
begin
/* this func works well */
declare #output varchar(7)
declare #currentMaxNr varchar(7)
set #currentMaxNr = (isnull((select max(OrderNumber) from Items), 'some_default_value_here')
/* lets assume the #currentMaxNr is '01/2014', here comes logic that increments to #newValue='02/2014' and sets to #output, so: */
set #output = #newValue
return #output
end
Into Items can be inserted items that do as well that do not belong to any OrderNumber.
Whether an Item should become new OrderNumber, the procedure is called with #insertNewOrderNr=1, returns the new order number, that can be used to insert next items with that OrderNumber while #insertNewOrderNr=0.
Occasionally there happens that there come simultaneously 2 requests to #insertNewOrderNr and THERE IS THE PROBLEM - Items, that should correspond with different OrderNumbers get the same OrderNumber.
I tried to use transaction with no success.
The table structure cant be modified by me.
What would be the right way to ensure, that there won't be used the same newOrderNumber when simultaneous requests to the procedure come?
I am stuck here for a long time till now. Please, help.
You will have that problem as long as you use MAX(OrderNumber).
You might consider using sequences:
Create sequence
CREATE SEQUENCE dbo.OrderNumbers
AS int
START WITH 1
INCREMENT BY 1
NO CACHE;
GO
CREATE SEQUENCE dbo.OrderNumberYear
AS int
START WITH 2014
INCREMENT BY 1
NO CACHE;
SELECT NEXT VALUE FOR dbo.OrderNumberYear; --Important, run this ONE time after creation, this years value must be returned one initial time to work correctly.
Insert code
DECLARE #orderNumberYear INT = (SELECT CONVERT(INT, current_value) FROM sys.sequences WHERE name = 'OrderNumberYear');
IF(#orderNumberYear < YEAR(GETDATE()))
BEGIN
SELECT #orderNumberYear = NEXT VALUE FOR dbo.OrderNumberYear;
ALTER SEQUENCE dbo.OrderNumbers RESTART WITH 1 ;
END
IF(#orderNumberYear != YEAR(GETDATE()))
RAISERROR(N'Order year sequence is out of sync.', 16, 1);
DECLARE #newNr VARCHAR(15) = CONCAT(FORMAT(NEXT VALUE FOR dbo.OrderNumbers, '00/', 'en-US'), #orderNumberYear);
INSERT INTO Items (OrderNumber, ItemName, Count) values (#newNr, #itemName, #count)
SELECT #newNr
The duplicity still occured, not so often, but did.
The trick I finally used to get around didn't find itself inside SQL. Since the DB is always used by ONLY one web app that is used by several users, this is the solution:
in all (about 5) places in my VB.NET code I surrounded the myCommand.ExecuteScalar() with SyncLock (read more) statement that DID the trick :)

Generating the Next Id when Id is non-AutoNumber

I have a table called Employee. The EmpId column serves as the primary key. In my scenario, I cannot make it AutoNumber.
What would be the best way of generating the the next EmpId for the new row that I want to insert in the table?
I am using SQL Server 2008 with C#.
Here is the code that i am currently getting, but to enter Id's in key value pair tables or link tables (m*n relations)
Create PROCEDURE [dbo].[mSP_GetNEXTID]
#NEXTID int out,
#TABLENAME varchar(100),
#UPDATE CHAR(1) = NULL
AS
BEGIN
DECLARE #QUERY VARCHAR(500)
BEGIN
IF EXISTS (SELECT LASTID FROM LASTIDS WHERE TABLENAME = #TABLENAME and active=1)
BEGIN
SELECT #NEXTID = LASTID FROM LASTIDS WHERE TABLENAME = #TABLENAME and active=1
IF(#UPDATE IS NULL OR #UPDATE = '')
BEGIN
UPDATE LASTIDS
SET LASTID = LASTID + 1
WHERE TABLENAME = #TABLENAME
and active=1
END
END
ELSE
BEGIN
SET #NEXTID = 1
INSERT INTO LASTIDS(LASTID,TABLENAME, ACTIVE)
VALUES(#NEXTID+1,#TABLENAME, 1)
END
END
END
Using MAX(id) + 1 is a bad idea both performance and concurrency wise.
Instead you should resort to sequences which were design specifically for this kind of problem.
CREATE SEQUENCE EmpIdSeq AS bigint
START WITH 1
INCREMENT BY 1;
And to generate the next id use:
SELECT NEXT VALUE FOR EmpIdSeq;
You can use the generated value in a insert statement:
INSERT Emp (EmpId, X, Y)
VALUES (NEXT VALUE FOR EmpIdSeq, 'x', 'y');
And even use it as default for your column:
CREATE TABLE Emp
(
EmpId bigint PRIMARY KEY CLUSTERED
DEFAULT (NEXT VALUE FOR EmpIdSeq),
X nvarchar(255) NULL,
Y nvarchar(255) NULL
);
Update: The above solution is only applicable to SQL Server 2012+. For older versions you can simulate the sequence behavior using dummy tables with identity fields:
CREATE TABLE EmpIdSeq (
SeqID bigint IDENTITY PRIMARY KEY CLUSTERED
);
And procedures that emulates NEXT VALUE:
CREATE PROCEDURE GetNewSeqVal_Emp
#NewSeqVal bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON
INSERT EmpIdSeq DEFAULT VALUES
SET #NewSeqVal = scope_identity()
DELETE FROM EmpIdSeq WITH (READPAST)
END;
Usage exemple:
DECLARE #NewSeqVal bigint
EXEC GetNewSeqVal_Emp #NewSeqVal OUTPUT
The performance overhead of deleting the last inserted element will be minimal; still, as pointed out by the original author, you can optionally remove the delete statement and schedule a maintenance job to delete the table contents off-hour (trading space for performance).
Adapted from SQL Server Customer Advisory Team Blog.
Working SQL Fiddle
The above
select max(empid) + 1 from employee
is the way to get the next number, but if there are multiple user inserting into the database, then context switching might cause two users to get the same value for empid and then add 1 to each and then end up with repeat ids. If you do have multiple users, you may have to lock the table while inserting. This is not the best practice and that is why the auto increment exists for database tables.
I hope this works for you. Considering that your ID field is an integer
INSERT INTO Table WITH (TABLOCK)
(SELECT CASE WHEN MAX(ID) IS NULL
THEN 1 ELSE MAX(ID)+1 END FROM Table), VALUE_1, VALUE_2....
Try following query
INSERT INTO Table VALUES
((SELECT isnull(MAX(ID),0)+1 FROM Table), VALUE_1, VALUE_2....)
you have to check isnull in on max values otherwise it will return null in final result when table contain no rows .

How to check if a column value is referred in some other table as that column is a foreign key in other table (sql server)?

I have a table that its primary key "ID" field is used in many other table as foreign key.
How can I check that a particular record from this table (for example first record "ID = 1") is used in other table?
If a particular record is used in some other table I don't want to do any operations on that row.
Very blunt solution:
try to delete the record.
If you get an integrity contraint violation, this means it's referenced by another record, catch this exception
If the delete worked, rollback your delete
I said it was blunt :)
On the surface, your question doesn't make sense. Let's look at some data.
users
user_id user_email
--
1 abc#def.com
2 def#hij.com
user_downloads
user_id filename downloaded_starting
1 123.pdf 2013-05-29 08:00:13
1 234.pdf 2013-05-29 08:05:27
1 345.pdf 2013-05-29 08:10:33
There's a foreign key on user_downloads: foreign key (user_id) references users (user_id).
As long as you don't also declare that foreign key as ON DELETE CASCADE, then you can't delete the corresponding row in users. You don't have to check for the presence of rows in other tables, and you shouldn't. In a big system, that might mean checking hundreds of tables.
If you don't declare the foreign key as ON UPDATE CASCADE, you can't update the user_id if it's referenced by any other table. So, again, you don't have to check.
If you use the email address as the target for a foreign key reference, then, once again, don't use ON DELETE CASCADE and don't use ON UPDATE CASCADE. Don't use those declarations, and you don't have to check. If you don't use the email address as the target for a foreign key reference, it doesn't make sense to prevent updates to it.
So if you build your tables right, you don't have to check any of that stuff.
You could use a trigger to roll back any transaction that gives a true for
"where exists( select * from otherTable Where fk = id union select * from anotherTable Where fk = id union etc)
It wont be too heavy if you have any index on each of the tables which starts with fk, (which you should have for general speed anyway), SQL will just check the index for the id. ie a single read for each table checked.
Use the following if you do not wish to use a trial and error method:
DECLARE #schema NVARCHAR(20)
DECLARE #table NVARCHAR(50)
DECLARE #column NVARCHAR(50)
DECLARE #SQL NVARCHAR(1000)
DECLARE #ID INT
DECLARE #exists INT
DECLARE #x NVARCHAR(100)
SELECT #x = '#exists int output', #ID = 1, #schema = 'dbo', #table = 'Gebruiker', #column = 'GebruikerHasGebruiker_id'
SELECT #SQL = 'SELECT #exists = 1 WHERE EXISTS( ' + STUFF((
SELECT ' UNION ALL SELECT ' + U2.COLUMN_NAME + ' AS ID FROM ' + U2.TABLE_SCHEMA + '.' + U2.TABLE_NAME + ' WHERE ' + U2.COLUMN_NAME + ' = ' + cast(#id as VARCHAR(10))
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE U ON R.UNIQUE_CONSTRAINT_NAME = U.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE U2 ON R.CONSTRAINT_NAME = U2.CONSTRAINT_NAME
WHERE U.TABLE_SCHEMA = #schema
AND U.TABLE_NAME = #table
AND U.COLUMN_NAME = #column
FOR XML PATH('')
),1,11, '') + ')'
EXEC sp_executesql #SQL, #x, #exists = #exists OUTPUT
IF 1 <> #exists
BEGIN
-- do you stuff here
END
But in 99% of the cases you could do this, it's overkill. It is faster if you already know the FKs and just create a query.
Edit:
A little explanation. This dynamic SQL looks in the INFORMATION SCHEMA to see all relations with other tables. It uses that information to create a query to check if your ID exists in that table. With a UNION it adds all results and returns 1 if any results are found. This can be used for any database, for any column, as long as you don't check for a FK over multiple columns.
Using this solution you don't need to hard code all referenced tables.
use tempdb
go
/* provide test data*/
if OBJECT_ID(N't2') is not null
drop table t2
if OBJECT_ID(N't1') is not null
drop table t1
create table t1(i int not null primary key)
create table t2(i int not null, constraint fk_t1_t2 foreign key (i) references t1(i))
go
insert into t1 values(1),(2)
insert into t2 values(1)
/* checking if the primary key value referenced in other tables */
declare #forCheck int=1 /* id to be checked if it referenced in other tables */
declare #isReferenced bit=0
begin tran
begin try
delete from t1 where i=#forCheck
end try
begin catch
set #isReferenced=1
end catch
rollback
select #isReferenced
The Approach should be to collect all the dependent objects and query them to check if the parent tables records exists.
i use a Procedure which returns the dependent objects.
The Reason i can not post that procedure is exceeding the limited number 30000 characters to post it is 48237 characters. let me know your mail-id i will send you the procedure.
Iterate through the result of the procedure to check if any dependent column holds your primary tables data.