Remove ON DELETE CASCADE on foreign keys for several tables - sql-server

I have several tables with a foreign key constraint that has the option ON DELETE CASCADE. Every table belongs to the same schema called datasets.
I'm able to retrieve the complete list of tables using :
SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA ='datasets'
For each table I would like to remove the ON DELETE CASCADE option on the foreign key constraint named FK_[TABLENAME]_SerieID where [TABLENAME] corresponds to the name of the table (and SerieId is the same foreign key across tables).
I am able to perform the operation for a particular table, for instance the table called Table1 using :
ALTER TABLE datasets.Table1
DROP CONSTRAINT FK_Table1_SerieID
ALTER TABLE datasets.Table1
ADD CONSTRAINT FK_Table1_SerieID
FOREIGN KEY (Serie_Id) REFERENCES[dbo].[Serie](SerieID)
ON DELETE NO ACTION
GO
I would like to perform the above operation for each table that belong to the schema datasets . I'm new to T-SQL and I don't know how to do it.
Should I use a cursor? Can you help me with this?
I'm using SQL Server 2016.

I would not reinvent the wheel. There is excellent script written by Aaron Bertrand: Drop and Re-Create All Foreign Key Constraints in SQL Server.
You could easily extend it to handle NO ACTION case and specific schema by adding simple WHERE restriction:
DECLARE #drop NVARCHAR(MAX) = N'',
#create NVARCHAR(MAX) = N'';
-- drop is easy,just build a simple concatenated list from sys.foreign_keys:
SELECT #drop += N'
ALTER TABLE ' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
+ ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS ct
ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
ON ct.[schema_id] = cs.[schema_id]
WHERE delete_referential_action_desc <> 'NO_ACTION' -- here
AND cs.name = 'datasets';
-- create is a little more complex. We need to generate the list of
-- columns on both sides of the constraint, even though in most cases
-- there is only one column.
SELECT #create += N'
ALTER TABLE '
+ QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
+ ' ADD CONSTRAINT ' + QUOTENAME(fk.name)
+ ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the columns in the constraint table
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.parent_column_id = c.column_id
AND fkc.parent_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'),1,1,N'')
+ ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
+ '(' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the referenced columns
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.referenced_column_id = c.column_id
AND fkc.referenced_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'),1,1,N'') + ');'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs
ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0
AND delete_referential_action_desc <> 'NO_ACTION' -- here
AND cs.name = 'datasets';
print(#drop);
print(#create);
-- ...
DBFiddle Demo
One warning! Please avoid adding ORDER BY.
This script uses
SELECT #drop += N'...'
<=>
SELECT #drop = #drop + N'...'
and it may start producing incorrect results. More nvarchar concatenation / index / nvarchar(max) inexplicable behavior

Related

Subquery table based on sys.tables

I'm trying to retrieve all primary keys iin a fact table and then count the number of records in that fact table grouped by that id, however so far i can only seem to get all the primary keys and its talbe. i guess i need to make some kind of subquery
SELECT
tab1.name AS [FactTable],
col1.name AS [PrimaryKey]
FROM sys.indexes ind1
INNER JOIN sys.tables tab1
ON tab1.object_id = ind1.object_id
INNER JOIN sys.schemas sch1
ON tab1.schema_id = sch1.schema_id
INNER JOIN sys.columns col1
ON col1.object_id = tab1.object_id AND col1.name like '%Id'
WHERE tab1.name like 'Fact%' AND ind1.is_primary_key = 1
sample output
primaryKey countRecordsGroupedByPrimaryKey
2 4000
3 8343
4 203
1 4023
I going out on a limb and guessing that you want to count the number of each dimensionPK used in your fact table. If a fact table has references to two different dimensions, you need two different statements to count the usage by that dimension.
For the query below, provide your fact table name and schema and it should generate a count statement joining your fact and dim and grouping by the join keys.
- If a fact table has two different FK relationships, you'll get two different statements.
- If a table uses a composite PK, both key columns will be included in the join
This is complicated and I don't have any tables with multiple FKs to test it on, so please let me know if it does what you want.
DECLARE #NameOfTableWithFKs sysname = 'your fact table name',
#SchemaOfTableWithFKs sysname = 'dbo';
WITH JoinColumns
AS (SELECT QUOTENAME(OBJECT_SCHEMA_NAME(parent.object_id)) + '.' + QUOTENAME(OBJECT_NAME(parent.object_id)) AS ParentTableName,
QUOTENAME(OBJECT_SCHEMA_NAME(referenced.object_id)) + '.' + QUOTENAME(OBJECT_NAME(referenced.object_id)) AS ReferencedTableName,
QUOTENAME(OBJECT_NAME(parent.object_id)) + '.' + QUOTENAME(parent.name) + ' = ' + QUOTENAME(OBJECT_NAME(referenced.object_id)) + '.'
+ QUOTENAME(referenced.name) AS JoinColumn,
QUOTENAME(OBJECT_NAME(referenced.object_id)) + '.' + QUOTENAME(referenced.name) AS GroupingColumn
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.columns AS parent
ON parent.object_id = fkc.parent_object_id
AND parent.column_id = fkc.parent_column_id
INNER JOIN sys.columns AS referenced
ON referenced.object_id = fkc.referenced_object_id
AND referenced.column_id = fkc.referenced_column_id
WHERE OBJECT_NAME(parent.object_id) = #NameOfTableWithFKs
AND OBJECT_SCHEMA_NAME(parent.object_id) = #SchemaOfTableWithFKs
),
JoinTables
AS (SELECT QUOTENAME(OBJECT_SCHEMA_NAME(tbl.object_id)) + '.' + QUOTENAME(OBJECT_NAME(tbl.object_id)) AS ParentTableName,
QUOTENAME(OBJECT_SCHEMA_NAME(rtbl.object_id)) + '.' + QUOTENAME(OBJECT_NAME(rtbl.object_id)) AS ReferencedTableName
FROM sys.tables AS tbl
INNER JOIN sys.foreign_keys AS cstr
ON cstr.parent_object_id = tbl.object_id
INNER JOIN sys.tables AS rtbl
ON rtbl.object_id = cstr.referenced_object_id
WHERE OBJECT_NAME(tbl.object_id) = #NameOfTableWithFKs
AND OBJECT_SCHEMA_NAME(tbl.object_id) = #SchemaOfTableWithFKs
)
SELECT 'SELECT Count(*)' + ( SELECT ', ' + JC.GroupingColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = jt.ParentTableName
AND JC.ReferencedTableName = jt.ReferencedTableName
FOR XML PATH('')
) + ' FROM ' + JT.ParentTableName + ' INNER JOIN ' + JT.ReferencedTableName + ' ON'
+ SUBSTRING(( SELECT ' AND ' + JC.JoinColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = JT.ParentTableName
AND JC.ReferencedTableName = JT.ReferencedTableName
FOR XML PATH('')
), 5, 8000
) + ' GROUP BY ' + SUBSTRING(( SELECT ', ' + JC.GroupingColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = JT.ParentTableName
AND JC.ReferencedTableName = JT.ReferencedTableName
FOR XML PATH('')
), 2, 8000
)
FROM JoinTables AS JT;
If I understand the question correctly you are wanting to count all the rows in some tables based on some criteria. Not really sure why you care about the primary key portion since by definition a primary key must be unique so this could still be simplified to not check for primary key but whatever.
I did remove the join to sys.columns because why does it matter the name of the column unless you want only those table named Fact% and has a column named %Id.
This should get you pretty close as I understand it.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select TableName = ''' + tab1.name + ''', NumRows = count(*) from ' + QUOTENAME(sch1.name) + '.' + QUOTENAME(tab1.name) + ' UNION ALL '
FROM sys.indexes ind1
INNER JOIN sys.tables tab1
ON tab1.object_id = ind1.object_id
INNER JOIN sys.schemas sch1
ON tab1.schema_id = sch1.schema_id
--INNER JOIN sys.columns col1
-- ON col1.object_id = tab1.object_id AND col1.name like '%Id'
WHERE tab1.name like 'Fact%'
AND ind1.is_primary_key = 1
select #SQL = LEFT(#SQL, LEN(#SQL) - 10) + ' ORDER BY TableName'
select #SQL --uncomment the exec line below once you are comfortable that the dynamic sql is what you want.
--exec sp_executesql #SQL

How to drop table PK if I do not know its name

I try to drop primary key:
alter table mytable DROP (
SELECT CONSTRAINT_NAME FROM information_schema.table_constraints
where table_name='mytable' and constraint_type = 'PRIMARY KEY')
How to do it correctly?
First, don't use INFORMATION_SCHEMA views. Second, always use the schema prefix when referencing objects in DDL or DML. Third, you need to use dynamic SQL, because you can't parameterize DDL that way.
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'ALTER TABLE dbo.mytable DROP CONSTRAINT '
+ QUOTENAME(k.name) + ';'
FROM sys.key_constraints k
INNER JOIN sys.objects AS o
ON k.parent_object_id = o.[object_id]
INNER JOIN sys.schemas AS s
ON o.[schema_id] = s.[schema_id]
WHERE o.name = N'mytable'
AND s.name = N'dbo';
PRINT #sql;
-- EXEC sp_executesql #sql;
Now, this won't necessarily work, for example if there are other tables with foreign keys referencing this primary key, you will need to seek those out and fix them first.

updateing all Foreign key constraint in sql server?

I have a database in sql server 2008 R2 that have many table (over 200) and have many relation betweens tables.
Delete rule in most of relations is No Action
I need to Update all relation delete rule to Cascade at once
Because of too many relation in my database I dont want do this one by one
Is there any way ?
Typically, when you ALTER a Foreign key constraint, using the SSMS GUI, SQL Server on the background actually drops and recreates the same.
Here is a script, that will generate the SQL for dropping all FKeys and recreating them with ON UPDATE CASCADE ON DELETE CASCADE options
Assumption is, all your FKeys are name "FK....."
SET NOCOUNT ON;
DECLARE #Objects TABLE
(
ID int identity(1,1),
TableName sysname,
SchemaName sysname
)
INSERT INTO #Objects (TableName, SchemaName)
SELECT
TABLE_NAME,
CONSTRAINT_SCHEMA
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME LIKE 'FK%'
DECLARE #min int, #max int,#table sysname,#schema sysname
SELECT #min = 1, #max = MAX(ID) FROM #Objects
WHILE #min <=#max
BEGIN
SELECT
#table = TableName,#schema = SchemaName FROM #Objects WHERE ID = #min
print '/*Drop Foreign Key Statements for [' + #schema + '].[' + #table + ']*/'
SELECT
'ALTER TABLE [' + SCHEMA_NAME(o.schema_id) + '].[' + o.name + ']
DROP CONSTRAINT [' + fk.name + ']'
FROM sys.foreign_keys fk
INNER JOIN sys.objects o
ON fk.parent_object_id = o.object_id
WHERE o.name = #table AND
SCHEMA_NAME(o.schema_id) = #schema
print '/*Create Foreign Key Statements for ['
+ #schema + '].[' + #table + ']*/'
SELECT
'ALTER TABLE [' + SCHEMA_NAME(o.schema_id) + '].[' + o.name + ']
ADD CONSTRAINT [' + fk.name + '] FOREIGN KEY ([' + c.name + '])
REFERENCES [' + SCHEMA_NAME(refob.schema_id) + '].[' + refob.name + ']
([' + refcol.name + '])ON UPDATE CASCADE ON DELETE CASCADE'
FROM sys.foreign_key_columns fkc
INNER JOIN sys.foreign_keys fk
ON fkc.constraint_object_id = fk.object_id
INNER JOIN sys.objects o
ON fk.parent_object_id = o.object_id
INNER JOIN sys.columns c
ON fkc.parent_column_id = c.column_id AND
o.object_id = c.object_id
INNER JOIN sys.objects refob
ON fkc.referenced_object_id = refob.object_id
INNER JOIN sys.columns refcol
ON fkc.referenced_column_id = refcol.column_id AND
fkc.referenced_object_id = refcol.object_id
WHERE o.name = #table AND
SCHEMA_NAME(o.schema_id) = #schema
SET #min = #min+1
END
Hope this helps.
Raj
PS: Setting query output to text helps. Also please read the comment posted on your question. Arbitrarily setting CASCADE may not be the right thing to do

copy FOREIGN KEY constraints from one DB to other

I have a local db which has FOREIGN KEY constraints.
The live version of this websites DB, does not have any of these FOREIGN KEY constraints.
How can I "copy/paste", import/export ONLY the FOREIGN KEY constraints from one db to the other?
I do NOT want to copy any data, only the constraints.
Thanks
You could use this script I found at http://www.siusic.com/wphchen/how-to-script-out-all-the-foreign-keys-of-a-table-106.html. Replace tablename1 and tablename2 with the list of tables you wish to get the foreign keys for.
select 'ALTER TABLE '+object_name(a.parent_object_id)+
' ADD CONSTRAINT '+ a.name +
' FOREIGN KEY (' + c.name + ') REFERENCES ' +
object_name(b.referenced_object_id) +
' (' + d.name + ')'
from sys.foreign_keys a
join sys.foreign_key_columns b
on a.object_id=b.constraint_object_id
join sys.columns c
on b.parent_column_id = c.column_id
and a.parent_object_id=c.object_id
join sys.columns d
on b.referenced_column_id = d.column_id
and a.referenced_object_id = d.object_id
where object_name(b.referenced_object_id) in
('tablename1','tablename2')
order by c.name
I needed to do something similar, where I needed the same foreign keys on multiple servers, except some had already been added. So I added a "IF NOT EXISTS" check to the beginning of the creation statements:
SELECT N'
IF NOT EXISTS (SELECT * FROM sys.foreign_keys
WHERE object_id = OBJECT_ID(''' + QUOTENAME(fk.name) + ''')
AND parent_object_id = OBJECT_ID(''' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name) +''')
)
BEGIN
ALTER TABLE '
+ QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
+ ' ADD CONSTRAINT ' + QUOTENAME(fk.name)
+ ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the columns in the constraint table
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.parent_column_id = c.column_id
AND fkc.parent_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
+ ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
+ '(' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the referenced columns
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.referenced_column_id = c.column_id
AND fkc.referenced_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ')
END'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs
ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0;
If you do not want the "IF NOT EXISTS" checks (they really shouldn't matter), just delete the top 5 lines and add "SELECT N' " right before the "BEGIN", like this:
SELECT N'BEGIN
ALTER TABLE '
+ QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
-- I found the core of this query online somewhere and I've been modifying it for some time. Credit to them for putting most of it together...

T-SQL Script to Delete All The Relationships Between A Bunch Of Tables in a Schema and Other Bunch in another Schema?

I have a set of tables (say Account, Customer) in a schema (say dbo) and I have some other tables (say Order, OrderItem) in another schema (say inventory). There's a relationship between the Order table and the Customer table. I want to delete all the relationships between the tables in the first schema (dbo) and the tables in the second schema (inventory), without deleting the relationships between tables inside the same schema.
Is that possible? Any help appreciated.
Use the metadata:
SELECT *
FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE
WHERE CONSTRAINT_NAME IN ( SELECT CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' )
SELECT *
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
WHERE CONSTRAINT_NAME IN ( SELECT CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' )
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
Add filter criteria to find the constraints you want to DROP, then insert them in the template:
ALTER TABLE {TABLE_SCHEMA}.{TABLE_NAME} DROP {CONSTRAINT_NAME}
And execute with dynamic SQL.
Please use the script below by copying and pasting it to the SQL Studio:
SELECT 'ALTER TABLE ' + TABLE_SCHEMA + '.[' + TABLE_NAME + '] DROP [' + CONSTRAINT_NAME + ']'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
you can get the available FKs that exist in your db by below script and then delete them :
select * from sys.objects o
join sys.schemas s on o.schema_id = s.schema_id
where o.type = 'F'
after that delete like below
ALTER TABLE {TABLE_SCHEMA}.{TABLE_NAME} DROP {CONSTRAINT_NAME}
I won't swear this will work on SQL 2005, as I don't have an instance to test it with, but if it does it will make it a simple copy/paste job in SSMS. I'll leave it to you to iterate the results and execute if that's what you want.
Replace schema_1 and schema_2 with the schema names you're trying to find relationships between.
declare #s1 int
declare #s2 int
set #s1 = schema_id( 'schema_1' )
set #s2 = schema_id( 'schema_2' )
select
N'alter table [' + s.name + N'].[' + o_p.name + N'] drop constraint [' + fk.name + N']'
from sys.foreign_keys fk
join sys.schemas s on ( s.schema_id = fk.schema_id )
join sys.objects o_p on ( o_p.object_id = fk.parent_object_id )
join sys.objects o_r on ( o_r.object_id = fk.referenced_object_id )
where
( o_p.schema_id = #s1 and o_r.schema_id = #s2 )
or ( o_p.schema_id = #s2 and o_r.schema_id = #s1 )

Resources