How can I use single quote inside sql command? [duplicate] - sql

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I escape a single quote in sqlserver?
I got a script below that drop everything on the database from this link. It does error when I execute on this line.
SET #statement = '
IF(#type = 'F') or (#type = 'C') or (#type = 'D') or (#type='F') or (#type='K')
The reason is because the single quote. I want to know how can I fix this error?
/*** drop (pretty much) everything before rebuilding the database ***/
DECLARE
OBJECTS CURSOR FOR SELECT
so.name,
so.type,
so.type_desc,
p.name AS parentName
FROM
sys.objects AS so
LEFT JOIN sys.objects AS p ON so.parent_object_id = p.object_id
WHERE
so.schema_id = 1
ORDER BY
CASE
WHEN so.type = 'F' THEN
0
WHEN so.type = 'TR' THEN
1
WHEN so.type = 'U' THEN
2
WHEN so.type = 'F' THEN
3
ELSE
4
END OPEN OBJECTS DECLARE
#name AS nvarchar (MAX) DECLARE
#type AS nvarchar (2) DECLARE
#type_desc AS nvarchar DECLARE
#parentName AS nvarchar (MAX) DECLARE
#statement AS nvarchar (MAX) FETCH NEXT
FROM
OBJECTS INTO #name,
#type,
#type_desc,
#parentName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #statement = ' IF(#type = ' F ')
BEGIN
PRINT ' DROPING FK : ' + #name + ' OF type ' + #type + ' (' + #type_desc + ') '
SET #statement = ' ALTER TABLE ' + #parentName + ' DROP CONSTRAINT ' + #name
EXECUTE(#statement)
END
ELSE IF (#type = ' TR ')
BEGIN
PRINT ' DROPING TRIGGER : ' + #name + ' OF type ' + #type + ' (' + #type_desc + ') '
SET #statement = ' DROP TRIGGER ' + #name
EXECUTE(#statement)
END
ELSE IF (#type = ' U ')
BEGIN
PRINT ' DROPING TABLE : ' + #name + ' OF type ' + #type + ' (' + #type_desc + ') '
SET #statement = ' DROP TABLE ' + #name
EXECUTE(#statement)
END
ELSE IF (#type = ' FN ')
BEGIN
PRINT ' DROPING FUNCTION : ' + #name + ' OF type ' + #type + ' (' + #type_desc + ') '
SET #statement = ' DROP FUNCTION ' + #name
EXECUTE(#statement)
END
ELSE
PRINT ' Didn 't drop object ' + #name + ' of type ' + #type + ' (' + #type_desc + ')' FETCH NEXT
FROM
OBJECTS INTO #name,
#type,
#type_desc,
#parentName
END CLOSE OBJECTS DEALLOCATE OBJECTS

if you want to use single quote inside a prepared statement, escape it with another single quote, example,
SET #statement = 'world''s view';
SET #statement2 = 'world''s view';
from your example above
SET #statement = '
IF(#type = ''F'') or (#type = ''C'') or
(#type = ''D'') or (#type=''F'') or
(#type=''K'')'
-- the strings are all red.

Single quote is used to represent a string literal in SQL.
If you need to explicitly insert a single quote , you should use double single quotes ('')

It should be like this:
SET #statement = 'IF(#type = ''F'') or (#type = ''C'') or (#type = ''D'') or (#type=''F'') or (#type=''K'')'
Raj

Related

Update column by looping through rows

I have four columns in a table ID, Longitude, Latitude, and SpatialData. I have the first three columns filled out for every row, but I need to enter in the SpatialData for each row. I can currently manually update the SpatialData column by using the below query:
update GioMap set SpatialData = 'Point(-74.009506 40.70602)' Where ID =1
From here I have to keep manually updating the Longitude, Latitude and ID for every row. I am using this code to try to loop through all of the rows and update the table that way:
DECLARE #LoopC INT = 1, #MaxOID INT,
#Long nVarchar(32), #Lat nVarchar(32),#Col1 nVarchar(11)
SET #MaxOID = (select count(*) from GioMap)
Set #Col1 = 'SpatialData'
WHILE(#LoopC <= #MaxOID)
BEGIN
SET #Long = (Select Longitude FROM GioMap where ID = #LoopC)
SET #Lat = (Select Latitude FROM GioMap where ID = #LoopC)
DECLARE #sql VARCHAR(MAX) = ('update GioMap set ' + #Col1 +' = ' + '''' + 'Point(' + #Long + ' ' + #Lat + ')' + '''' + ' Where ID = ' + #LoopC)
EXEC sp_executesql #sql
SET #LoopC = #LoopC + 1
END
When I run this code I keep getting this error message:
Msg 245, Level 16, State 1, Line 13
Conversion failed when converting the nvarchar value 'update [ISSHXI1].[dbo].[GioMap] set SpatialDat = 'Point(-74.0095 40.706)' Where ID = ' to data type int.
I don't understand why it would be trying to convert it to an int?
You could do something like this:
UPDATE GioMap SET SpatialData = 'Point(' + cast(Longitude as varchar) + ' ' + cast(Latitude as varchar) + ')'
I think the way you are doing it is bad, but that's not technically what you asked.
It is trying to convert it to an int because you are adding a varchar to an int. You need to change this:
DECLARE #sql VARCHAR(MAX) = ('update GioMap set ' + #Col1 +' = ' +
'''' + 'Point(' + #Long + ' ' + #Lat + ')' + '''' + ' Where ID = ' +
#LoopC)
to this
DECLARE #sql VARCHAR(MAX) = ('update GioMap set ' + #Col1 +' = ' +
'''' + 'Point(' + #Long + ' ' + #Lat + ')' + '''' + ' Where ID = ' +
Cast(#LoopC as varchar))
The point statement paramaters need to be seperated by a comma.
DECLARE #sql VARCHAR(MAX) = ('update GioMap set ' + #Col1 +' = ' + '''' + 'Point(' + #Long + ' ' + #Lat + ')' + '''' + ' Where ID = ' + #LoopC)
Instead of:
#Long + ' ' + #Lat + ')
try
#Long + ',' + #Lat + ')
To see what is being executed you can try adding a print statement:
DECLARE #sql VARCHAR(MAX) = ('update GioMap set ' + #Col1 +' = ' + '''' + 'Point(' + #Long + ' ' + #Lat + ')' + '''' + ' Where ID = ' + #LoopC)
print #sql
EXEC sp_executesql #sql
Also why do you parans around strings you are assigning? Its confusing in TSQL. While it works it is jarring and unusual.
Instead of:
SET #MaxOID = (select count(*) from GioMap)
try
SET #MaxOID = 'select count(*) from GioMap'
Later in the code you do both parens and quotes. The great, great majority of TSQL developers just use single quotes.
Ben

update special character columns dynamically for all columns of a table?

I want to replace special characters by normal characters in all columns dynamically for all columns of a table.But it works only for a column which is hardcoded
alter proc dbo.specialcharacterreplacer
#tblname varchar(1000),
#column_name varchar(1000)
as
begin
declare #Sql VARCHAR(MAX)
set #Sql = '
UPDATE ' + #tblname + ' SET ' + #column_name+ ' = REPLACE('+#column_name + ', ' + '''ó'''+ ', '+'''o'''+')
UPDATE ' + #tblname + ' SET ' + #column_name+ ' = REPLACE('+#column_name + ', ' + '''ò'''+ ', '+'''o'''+')
UPDATE ' + #tblname + ' SET ' + #column_name+ ' = REPLACE('+#column_name + ', ' + '''ö'''+ ', '+'''o'''+')
UPDATE ' + #tblname + ' SET ' + #column_name+ ' = REPLACE('+#column_name + ', ' + '''ð'''+ ', '+'''o'''+')
exec (#sql)
end
go
EXEC dbo.specialcharacterreplacer #tblname = 'dirtyyyysource', #column_name ='select *from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = '#tblname''
how to make columns dynamic?
This was the central code to get your update statement for all columns of a given table dynamically. Be aware of TABLE_SCHEMA and the column's type. You might use some additions in the WHERE part... (in my example you'd try to replace the INT column as well...)
And you might have a look here: https://stackoverflow.com/a/32048968/5089204
There you'll find one of my former answers to a similar question and shows an approach how to create a function which will replace several special characters in one go.
CREATE TABLE dbo.TestTable(ID INT,Test1 VARCHAR(100), Test2 VARCHAR(100));
GO
declare #tblname varchar(1000)='TestTable';
declare #tblschema varchar(1000)='dbo';
DECLARE #SqlCmd VARCHAR(MAX)= 'UPDATE ' + #tblname + ' SET ' +
(
STUFF(
(
SELECT ',' + COLUMN_NAME + ' = REPLACE(' + COLUMN_NAME + ', ' + '''ó'', ''o'''+')'+ CHAR(10) --might need to use CHAR(13)+CHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA=#tblschema AND TABLE_NAME = #tblname
FOR XML PATH('')
),1,1,'') + ';'
);
SELECT #SqlCmd;
GO
DROP TABLE dbo.TestTable;
GO
The result:
UPDATE TestTable SET ID = REPLACE(ID, 'ó', 'o')
,Test1 = REPLACE(Test1, 'ó', 'o')
,Test2 = REPLACE(Test2, 'ó', 'o')
;

Population of Visual Studio Database Project data-loading scripts from existing data

So, I've been hunting for a solution for this for awhile and have come up with what sort of works ... but I can't help feeling that there must be something more elegant.
What I'm looking for is to be able to extract existing data from a populated database & incorporate that data into loading scripts. The database schema & configuration data will be rolled out multiple times, and will change as development continues, so it's important to be able to rebuild the configuration data from existing data, rather than from static data kept in scripts.
Here's what I've cobbled together:
create procedure #dump (
#TableName varchar(128)
)
as
set nocount on
set rowcount 0
declare #template varchar(max)
set #template = 'SET IDENTITY_INSERT [dbo].[' + #TableName + '] ON
MERGE INTO [dbo].[' + #TableName + '] AS [Target]
USING
(
VALUES
{vals}
)
AS [Source] ({fields})
ON [Target].[{pk}] = [Source].[{pk}]
WHEN MATCHED THEN UPDATE SET
{upds}
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
{fields}
)
VALUES
(
{fields}
);
SET IDENTITY_INSERT [dbo].[' + #TableName + '] OFF
--------------------------------------------------
'
declare #pk varchar(max) = ''
declare #vals varchar(max) = '/*
set concat_null_yields_null off
select ''' + '(' + ''' + replace(replace({casts} + '') ,'', '',,'', '', null,''), ''' + ',)' + ''', ''' + ',null)' + ''') from [' + #TableName + ']
*/'
declare #casts varchar(max) = ''
declare #fields varchar(max) = ''
declare #upds varchar(max) = ''
declare #inserts varchar(max) = ''
set #pk = SUBSTRING(#TableName, 1, len(#TableName) - 1) + 'ID'
declare cur_flds
cursor for select c.name, c.type
from syscolumns c
where c.id = object_id(#TableName)
order by c.colid
declare #fn varchar(max)
declare #ft int
open cur_flds
fetch next from cur_flds into #fn, #ft
while ##FETCH_STATUS = 0
begin
if len(#fields) > 0
set #fields = #fields + ', '
set #fields = #fields + '[' + #fn + ']'
if len(#casts) > 0
set #casts = #casts + ' + ' + ''','' + '
if #ft in(56,55,50,38,48)
set #casts = #casts + 'cast([' + #fn + '] as varchar)'
else if #ft = 111
set #casts = #casts + ''''''''' + ' + 'cast([' + #fn + '] as varchar) + ' + ''''''''''
else
set #casts = #casts + ''''''''' + ' + 'replace([' + #fn + '], ''''''''' + ', ' + ''''''''''''') + '''''''''
if #fn != #pk
begin
if len(#upds) > 0
set #upds = #upds + ', '
set #upds = #upds + '[Target].[' + #fn + '] = [Source].[' + #fn + ']'
end
fetch next from cur_flds into #fn, #ft
end
close cur_flds
deallocate cur_flds
set #vals = REPLACE(#vals, '{casts}', #casts)
set #template = REPLACE(#template, '{pk}', #pk)
set #template = REPLACE(#template, '{vals}', #vals)
set #template = REPLACE(#template, '{fields}', #fields)
set #template = REPLACE(#template, '{upds}', #upds)
set #template = REPLACE(#template, '{inserts}', #inserts)
print #template
go
exec #dump 'ActionItemSystems'
drop proc #dump
That ends up giving me output of:
SET IDENTITY_INSERT [dbo].[ActionItemSystems] ON
MERGE INTO [dbo].[ActionItemSystems] AS [Target]
USING
(
VALUES
/*
set concat_null_yields_null off
select '(' + replace(replace(cast([ActionItemSystemID] as varchar) + ',' + '''' + replace([ActionItemSystemName], '''', '''''') + '''' + ') ,', ',,', ', null,'), ',)', ',null)') from [ActionItemSystems]
*/
)
AS [Source] ([ActionItemSystemID], [ActionItemSystemName])
ON [Target].[ActionItemSystemID] = [Source].[ActionItemSystemID]
WHEN MATCHED THEN UPDATE SET
[Target].[ActionItemSystemName] = [Source].[ActionItemSystemName]
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
[ActionItemSystemID], [ActionItemSystemName]
)
VALUES
(
[ActionItemSystemID], [ActionItemSystemName]
);
SET IDENTITY_INSERT [dbo].[ActionItemSystems] OFF
From this point, I can take the commented-out bit
set concat_null_yields_null off
select '(' + replace(replace(cast([ActionItemSystemID] as varchar) + ',' + '''' + replace([ActionItemSystemName], '''', '''''') + '''' + ') ,', ',,', ', null,'), ',)', ',null)') from [ActionItemSystems]
execute that, and get output like:
(33,'7111-5 -Upstream/Seed Lab') ,
(32,'7301-Seed Lab') ,
(30,'7807 UFDF') ,
(14,'BAS Panel Upgrade') ,
(1,'Clean Steam') ,
(13,'DCS') ,
(2,'HWFI') ,
(3,'MCS') ,
(12,'MES') ,
(31,'Seed Lab') ,
(18,'UCS WRO') ,
(34,'Upstream Seed Lab') ,
(29,'Viral Filtration') ,
which can then be incorporated (sans the final comma) into the script.
Now, this solution functions, but it's fragile. It depends on various assumptions (e.g., that the Table Name will have a Primary Key of Table Name - trailing 's' and plus ID) that may not hold true for every solution. It also requires cutting & pasting, and rerunning from the start when the table structures change.
This is probably quite a lot of background ... which I'm partly sharing because I couldn't find anything similar out there & thought that somebody might benefit from this. However, I still come back to my real question, which is to say: where's the tool to generate this kind of script for VS Database Projects? There really should be something - something that would take into account whatever the primary key is, that would generate the thing entire, etc.
You can try with this procedure for generating MERGE statements:
https://github.com/readyroll/generate-sql-merge
It is more advanced version of what you already have.

SQL Query within VS TableAdapter Query Configuration Wizard

I am trying to write an SQL query within Visual Studio TableAdapter Query Wizard
My SQL query is:
DECLARE #SQL varchar(255);
SET #SQL = ' SELECT * FROM dbAddress WHERE 1 = 1'
IF #ApexLine1 = ''
BEGIN
SET #SQL = #SQL + ' AND addLine1 IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addLine1 = ''' + #ApexLine1 + ''''
END
IF #ApexLine2 = ''
BEGIN
SET #SQL = #SQL + ' AND addLine2 IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addLine2 = ''' + #ApexLine2 + ''''
END
IF #ApexLine3 = ''
BEGIN
SET #SQL = #SQL + ' AND addLine3 IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addLine3 = ''' + #ApexLine3 + ''''
END
IF #ApexZip = ''
BEGIN
SET #SQL = #SQL + ' AND addPostCode IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addPostCode = ''' + #ApexZip + ''''
END
IF #ApexCity = ''
BEGIN
SET #SQL = #SQL + ' AND addLine4 IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addLine4 = ''' + #ApexCity + ''''
END
IF #ApexProv = ''
BEGIN
SET #SQL = #SQL + ' AND addLine5 IS NULL '
END
ELSE
BEGIN
SET #SQL = #SQL + ' AND addLine5 = ''' + #ApexProv + ''''
END
EXEC(#SQL)
I get the error:
'The Declare SQL contruct or statement is not supported'
If I remove the Declare statement I get error:
'The Set SQL construct or statement is not supported'
Is there a work around for this?
Thanks.
Anything like this:
SET #SQL = #SQL + ' AND addLine1 = ''' + #ApexLine1 + ''''
is EVIL. Don't do it. Variables like #ApexLine1 could contain anything, even something like this:
';DROP TABLE dbAddress--
Think very carefully about what would happen if someone entered something like that in your Address Line 1 field. The only correct solution here is to use the built-in sp_executesql stored procedure. Learn it, use it.
Aside from that, I think at least part of your problem might be that your #SQL variable is only 255 characters. It's easily possible your query is running out of space.

Generate Delete Statement From Foreign Key Relationships in SQL 2008?

Is it possible via script/tool to generate a delete statement based on the tables fk relations.
i.e. I have the table: DelMe(ID) and there are 30 tables with fk references to its ID that I need to delete first, is there some tool/script that I can run that will generate the 30 delete statements based on the FK relations for me ?
(btw I know about cascade delete on the relations, I can't use it in this existing db)
I'm using Microsoft SQL Server 2008
Here is a script for cascading delete by Aasim Abdullah, works for me on MS SQL Server 2008:
IF OBJECT_ID('dbo.udfGetFullQualName') IS NOT NULL
DROP FUNCTION dbo.udfGetFullQualName;
GO
CREATE FUNCTION dbo.udfGetFullQualName
(#ObjectId INT)
RETURNS VARCHAR (300)
AS
BEGIN
DECLARE #schema_id AS BIGINT;
SELECT #schema_id = schema_id
FROM sys.tables
WHERE object_id = #ObjectId;
RETURN '[' + SCHEMA_NAME(#schema_id) + '].[' + OBJECT_NAME(#ObjectId) + ']';
END
GO
--============ Supporting Function dbo.udfGetOnJoinClause
IF OBJECT_ID('dbo.udfGetOnJoinClause') IS NOT NULL
DROP FUNCTION dbo.udfGetOnJoinClause;
GO
CREATE FUNCTION dbo.udfGetOnJoinClause
(#fkNameId INT)
RETURNS VARCHAR (1000)
AS
BEGIN
DECLARE #OnClauseTemplate AS VARCHAR (1000);
SET #OnClauseTemplate = '[<#pTable>].[<#pCol>] = [<#cTable>].[<#cCol>] AND ';
DECLARE #str AS VARCHAR (1000);
SET #str = '';
SELECT #str = #str + REPLACE(REPLACE(REPLACE(REPLACE(#OnClauseTemplate, '<#pTable>', OBJECT_NAME(rkeyid)), '<#pCol>', COL_NAME(rkeyid, rkey)), '<#cTable>', OBJECT_NAME(fkeyid)), '<#cCol>', COL_NAME(fkeyid, fkey))
FROM dbo.sysforeignkeys AS fk
WHERE fk.constid = #fkNameId; --OBJECT_ID('FK_ProductArrearsMe_ProductArrears')
RETURN LEFT(#str, LEN(#str) - LEN(' AND '));
END
GO
--=========== CASECADE DELETE STORED PROCEDURE dbo.uspCascadeDelete
IF OBJECT_ID('dbo.uspCascadeDelete') IS NOT NULL
DROP PROCEDURE dbo.uspCascadeDelete;
GO
CREATE PROCEDURE dbo.uspCascadeDelete
#ParentTableId VARCHAR (300), #WhereClause VARCHAR (2000), #ExecuteDelete CHAR (1)='N', --'N' IF YOU NEED DELETE SCRIPT
#FromClause VARCHAR (8000)='', #Level INT=0 -- TABLE NAME OR OBJECT (TABLE) ID (Production.Location) WHERE CLAUSE (Location.LocationID = 7) 'Y' IF WANT TO DELETE DIRECTLY FROM SP, IF LEVEL 0, THEN KEEP DEFAULT
AS -- writen by Daniel Crowther 16 Dec 2004 - handles composite primary keys
SET NOCOUNT ON;
/* Set up debug */
DECLARE #DebugMsg AS VARCHAR (4000),
#DebugIndent AS VARCHAR (50);
SET #DebugIndent = REPLICATE('---', ##NESTLEVEL) + '> ';
IF ISNUMERIC(#ParentTableId) = 0
BEGIN -- assume owner is dbo and calculate id
IF CHARINDEX('.', #ParentTableId) = 0
SET #ParentTableId = OBJECT_ID('[dbo].[' + #ParentTableId + ']');
ELSE
SET #ParentTableId = OBJECT_ID(#ParentTableId);
END
IF #Level = 0
BEGIN
PRINT #DebugIndent + ' **************************************************************************';
PRINT #DebugIndent + ' *** Cascade delete ALL data from ' + dbo.udfGetFullQualName(#ParentTableId);
IF #ExecuteDelete = 'Y'
PRINT #DebugIndent + ' *** #ExecuteDelete = Y *** deleting data...';
ELSE
PRINT #DebugIndent + ' *** Cut and paste output into another window and execute ***';
END
DECLARE #CRLF AS CHAR (2);
SET #CRLF = CHAR(13) + CHAR(10);
DECLARE #strSQL AS VARCHAR (4000);
IF #Level = 0
SET #strSQL = 'SET NOCOUNT ON' + #CRLF;
ELSE
SET #strSQL = '';
SET #strSQL = #strSQL + 'PRINT ''' + #DebugIndent + dbo.udfGetFullQualName(#ParentTableId) + ' Level=' + CAST (##NESTLEVEL AS VARCHAR) + '''';
IF #ExecuteDelete = 'Y'
EXECUTE (#strSQL);
ELSE
PRINT #strSQL;
DECLARE curs_children CURSOR LOCAL FORWARD_ONLY
FOR SELECT DISTINCT constid AS fkNameId, -- constraint name
fkeyid AS cTableId
FROM dbo.sysforeignkeys AS fk
WHERE fk.rkeyid <> fk.fkeyid -- WE DO NOT HANDLE self referencing tables!!!
AND fk.rkeyid = #ParentTableId;
OPEN curs_children;
DECLARE #fkNameId AS INT,
#cTableId AS INT,
#cColId AS INT,
#pTableId AS INT,
#pColId AS INT;
FETCH NEXT FROM curs_children INTO #fkNameId, #cTableId; --, #cColId, #pTableId, #pColId
DECLARE #strFromClause AS VARCHAR (1000);
DECLARE #nLevel AS INT;
IF #Level = 0
BEGIN
SET #FromClause = 'FROM ' + dbo.udfGetFullQualName(#ParentTableId);
END
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #strFromClause = #FromClause + #CRLF + ' INNER JOIN ' + dbo.udfGetFullQualName(#cTableId) + #CRLF + ' ON ' + dbo.udfGetOnJoinClause(#fkNameId);
SET #nLevel = #Level + 1;
EXECUTE dbo.uspCascadeDelete #ParentTableId = #cTableId, #WhereClause = #WhereClause, #ExecuteDelete = #ExecuteDelete, #FromClause = #strFromClause, #Level = #nLevel;
SET #strSQL = 'DELETE FROM ' + dbo.udfGetFullQualName(#cTableId) + #CRLF + #strFromClause + #CRLF + 'WHERE ' + #WhereClause + #CRLF;
SET #strSQL = #strSQL + 'PRINT ''---' + #DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(#cTableId) + ' Rows Deleted: '' + CAST(##ROWCOUNT AS VARCHAR)' + #CRLF + #CRLF;
IF #ExecuteDelete = 'Y'
EXECUTE (#strSQL);
ELSE
PRINT #strSQL;
FETCH NEXT FROM curs_children INTO #fkNameId, #cTableId;
--, #cColId, #pTableId, #pColId
END
IF #Level = 0
BEGIN
SET #strSQL = #CRLF + 'PRINT ''' + #DebugIndent + dbo.udfGetFullQualName(#ParentTableId) + ' Level=' + CAST (##NESTLEVEL AS VARCHAR) + ' TOP LEVEL PARENT TABLE''' + #CRLF;
SET #strSQL = #strSQL + 'DELETE FROM ' + dbo.udfGetFullQualName(#ParentTableId) + ' WHERE ' + #WhereClause + #CRLF;
SET #strSQL = #strSQL + 'PRINT ''' + #DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(#ParentTableId) + ' Rows Deleted: '' + CAST(##ROWCOUNT AS VARCHAR)' + #CRLF;
IF #ExecuteDelete = 'Y'
EXECUTE (#strSQL);
ELSE
PRINT #strSQL;
END
CLOSE curs_children;
DEALLOCATE curs_children;
Usage example 1
Note the use of the fully qualified column name in the example. It's subtle but you must specify the table name for the generated SQL to execute properly.
EXEC uspCascadeDelete
#ParentTableId = 'Production.Location',
#WhereClause = 'Location.LocationID = 2'
Usage example 2
EXEC uspCascadeDelete
#ParentTableId = 'dbo.brand',
#WhereClause = 'brand.brand_name <> ''Apple'''
Usage example 3
exec uspCascadeDelete
#ParentTableId = 'dbo.product_type',
#WhereClause = 'product_type.product_type_id NOT IN
(SELECT bpt.product_type_id FROM dbo.brand_product_type bpt)'
DELETE statements generated for use in SP with parameter, and as ON DELETE triggers:
(this variant supports single column FKs only)
SELECT 'DELETE '+detail.name+' WHERE '+dcolumn.name+' = #'+mcolumn.name AS stmt,
'DELETE ' + detail.name + ' FROM ' + detail.name + ' INNER JOIN deleted ON ' +
detail.name + '.' + dcolumn.name + ' = deleted.' + mcolumn.name AS trg
FROM sys.columns AS mcolumn
INNER JOIN sys.foreign_key_columns ON mcolumn.object_id =
sys.foreign_key_columns.referenced_object_id
AND mcolumn.column_id = sys.foreign_key_columns.referenced_column_id
INNER JOIN sys.tables AS master ON mcolumn.object_id = master.object_id
INNER JOIN sys.columns AS dcolumn
ON sys.foreign_key_columns.parent_object_id = dcolumn.object_id
AND sys.foreign_key_columns.parent_column_id = dcolumn.column_id
INNER JOIN sys.tables AS detail ON dcolumn.object_id = detail.object_id
WHERE (master.name = N'MyTableName')
I'm pretty sure I posted code here on Stack Overflow which does this automatically using INFORMATION_SCHEMA to generate dynamic SQL, but I can't find it. Let me see if I can regenerate it.
You might need to check this out a bit, I couldn't find my original code, so I modified some code I had which builds flattend views for star-schemas automatically.
DECLARE #COLUMN_NAME AS sysname
DECLARE #TABLE_NAME AS sysname
DECLARE #IDValue AS int
SET #COLUMN_NAME = '<Your COLUMN_NAME here>'
SET #TABLE_NAME = '<Your TABLE_NAME here>'
SET #IDValue = 123456789
DECLARE #sql AS varchar(max) ;
WITH RELATED_COLUMNS
AS (
SELECT QUOTENAME(c.TABLE_SCHEMA) + '.'
+ QUOTENAME(c.TABLE_NAME) AS [OBJECT_NAME]
,c.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS AS c WITH (NOLOCK)
INNER JOIN INFORMATION_SCHEMA.TABLES AS t WITH (NOLOCK)
ON c.TABLE_CATALOG = t.TABLE_CATALOG
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_TYPE = 'BASE TABLE'
INNER JOIN (
SELECT rc.CONSTRAINT_CATALOG
,rc.CONSTRAINT_SCHEMA
,lkc.TABLE_NAME
,lkc.COLUMN_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
WITH (NOLOCK)
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE lkc
WITH (NOLOCK)
ON lkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
AND lkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
AND lkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WITH (NOLOCK)
ON rc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rc.UNIQUE_CONSTRAINT_NAME = tc.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE rkc
WITH (NOLOCK)
ON rkc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rkc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rkc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE rkc.COLUMN_NAME = #COLUMN_NAME
AND rkc.TABLE_NAME = #TABLE_NAME
) AS j
ON j.CONSTRAINT_CATALOG = c.TABLE_CATALOG
AND j.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
AND j.TABLE_NAME = c.TABLE_NAME
AND j.COLUMN_NAME = c.COLUMN_NAME
)
SELECT #sql = COALESCE(#sql, '') + 'DELETE FROM ' + [OBJECT_NAME]
+ ' WHERE ' + [COLUMN_NAME] + ' = ' + CONVERT(varchar, #IDValue)
+ CHAR(13) + CHAR(10)
FROM RELATED_COLUMNS
PRINT #sql
Another technique is to use a code generator to create the Sql. I'm pretty sure the MyGeneration (no connection) has existing templates to do this. Using that tool and the right template you can create a sql script that deletes the relevant stuff with no pain.
Unfortunately, I think cascading is the tool you're asking for. I understand not being able to use it, but that fact that it exists as a built-in part of the db has pretty much killed the need for an alternative.
You can create all fk columns with a same name like 'row_id'
Then write the code below:
create procedure dbo.deleteRow
#row_id int
as
begin
set nocount on
begin transaction delete_row
declare #mainTableName varchar(50) = 'MyMainTableName'
begin try
declare #OBJECT_ID_mainTable int
select #OBJECT_ID_mainTable = OBJECT_ID from sys.tables where name = #mainTableName
create table #ids ( object_id int , table_name varchar (50) , referenced_object_id int , r_index int )
--1) select all tables are has fk to main table
insert into #ids select t.object id , t.name , fk.referenced object id ,
row_number () over ( order by
--how many tables are depends on me
(select COUNT ( * ) from sys . foreign_key_columns a fk where a_fk.referenced_object_id = fk.parent_object_id ) desc ) r_index
from sys.foreign_key_columns fk
join sys.tables t on t.object_id- fk.parent_object_id
where fk.referenced_object_id = #OBJECT_ID_mainTable
declare #i int = ( select max ( r_index ) from #ids )
declare #sqlBuilder nvarchar ( max )
--2) delete from fk tables in dependet order
while #i > 0
begin
--all fk column are called 'row_id'
set #sqlBuilder = concat ('delete from dbo.[' + ( select table_name from #ids where r_index = #i ) + ']' +
'where row_id = ', #row_id )
exec(#sqlBuilder)
set #i=#i-1
end
--3) delete from main table
delete from <MyMainTableName> where id = #row_id
commit transaction delete_row
end try
begin catch
rollback transaction delete_row
throw
end catch
end