Drop column and all dependent objects using data definition language - sql

I need to remove a column from a table, but when I try to remove it:
The object 'object_name' is dependent
on column 'column_name'.
ALTER TABLE DROP COLUMN column_name failed because
one or more objects access this
column.
I can look for the dependency in the system tables and remove it manually, but I need to do a migration (using SQL DDL) so all others members of the team just do the update, run the migration and donĀ“t have to mess up up system objects.

Try this code:
Declare #TABLENAME varchar(max), #COLUMN varchar(max)
SET #TABLENAME = 'YOURTableName'
SET #COLUMN = 'YOURColumnName'
Declare #CONSTRAINT varchar(max)
set #CONSTRAINT ='ALTER TABLE '+#TABLENAME+' DROP CONSTRAINT '
set #CONSTRAINT = #CONSTRAINT + (select SYS_OBJ.name as CONSTRAINT_NAME
from sysobjects SYS_OBJ
join syscomments SYS_COM on SYS_OBJ.id = SYS_COM.id
join sysobjects SYS_OBJx on SYS_OBJ.parent_obj = SYS_OBJx.id
join sysconstraints SYS_CON on SYS_OBJ.id = SYS_CON.constid
join syscolumns SYS_COL on SYS_OBJx.id = SYS_COL.id
and SYS_CON.colid = SYS_COL.colid
where
SYS_OBJ.uid = user_id() and SYS_OBJ.xtype = 'D'
and SYS_OBJx.name=#TABLENAME and SYS_COL.name=#COLUMN)
exec(#CONSTRAINT)
and then run your regular alter table:
ALTER TABLE YOURTABLENAME
DROP COLUMN YOURCOLUMNNAME
With the first code you remove all the dependencies on that column and then you can remove it without problems.
EDIT - Removing Default Values Constraints:
The code above does not seems to remove DEFAULT_CONSTRAINTS so, in that case you must also use:
DECLARE #ConstraintName nvarchar(200)
SELECT #ConstraintName = Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('__TableName__') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = N'__ColumnName__' AND object_id = OBJECT_ID(N'__TableName__'))
IF #ConstraintName IS NOT NULL
EXEC('ALTER TABLE __TableName__ DROP CONSTRAINT ' + #ConstraintName)

Related

Dynamic SQL Server Query loop through schema find primary key duplicate

EDIT: There seems to be some confusion around the create table statements. These are there solely as a demonstration of what tables *might come in to our synapse instance, not as actual code that will run. The important part of the question is contained in the latter half.
I am trying to create a stored procedure that loops through every table in a supplied schema and outputs the count of duplicate primary key rows for each table. Assume that the data is being supplied from elsewhere and the primary keys are not being enforced. For example I may have three tables in the stack schema:
CREATE TABLE stack.table1(
id int,
name NVARCHAR(MAX),
color NVARCHAR(20)
PRIMARY KEY (id))
INSERT INTO stack.table1 VALUES(1,'item1','yellow')
(2,'item2','blue')
(2,'item2','blue')
CREATE TABLE stack.table2(
id int,
name NVARCHAR(MAX),
size NVARCHAR(1)
PRIMARY KEY (id,size))
INSERT INTO stack.table2 VALUES(1,'item1','L')
(2,'item2','M')
(3,'item2','S')
CREATE TABLE stack.table3(
id int,
name NVARCHAR(MAX),
weight NVARCHAR(20)
PRIMARY KEY (id))
INSERT INTO stack.table1 VALUES(1,'item1','200lb')
(2,'item2','150lb')
(3,'item2','125lb')
I want to supply a variable to a stored procedure to indicate the schema (in this case 'stack') and have that procedure spit out a table with the names of the tables in the schema and the counts of duplicate primary key rows. So in this example a stored procedure called 'loopcheck' would look like this:
Query:
EXEC loopcheck #schema = 'stack'
Output:
table
duplicate_count
table1
1
table2
0
table3
0
I am using an Azure Synapse instance so there are several functions that are not available (such as FOR XML PATH and others.) Since each table may have a single column primary key or a composite primary key I need to join to the system provided tables to get primary key info. My general idea was like so:
CREATE procedure loopcheck #schema= NVARCHAR(MAX)
AS
BEGIN
create table #primarykey(
SCHEMA_NAME nvarchar(400),
TABLE_NAME nvarchar(500),
COLUMN_NAME nvarchar(500)
)
insert into #primarykey
select l.TABLE_SCHEMA,
l.TABLE_NAME,
l.COLUMN_NAME
from INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE l
inner join INFORMATION_SCHEMA.TABLE_CONSTRAINTS t on l.constraint_Name = t.CONSTRAINT_NAME
where
l.table_schema = #schema
CREATE TABLE #groupBy2(
TABLE_NAME nvarchar(50),
groupby nvarchar(200)
)
INSERT INTO #groupBy2
SELECT TABLE_NAME, STRING_AGG(CONVERT(NVARCHAR(max), COLUMN_NAME), ',') as groupby
FROM #primarykey
GROUP BY TABLE_NAME
DECLARE #currentTable NVARCHAR(MAX)=''
DECLARE #currentGroup NVARCHAR(MAX)=''
create table #work4(
TABLE_NAME nvarchar(400),
COUNT int)
DECLARE #final NVARCHAR(MAX)=
'INSERT INTO #work4
SELECT '+#currentTable+', COUNT(*) FROM '+#currentTable+'GROUP BY'+#currentGroup
WHILE (SELECT COUNT(*) FROM #groupby2)>0
BEGIN
SET #currentTable =(SELECT TOP 1 TABLE_NAME FROM #groupby2 ORDER BY TABLE_NAME)
SET #currentGroup =(SELECT TOP 1 groupby FROM #groupby2 ORDER BY TABLE_NAME)
exec #final
DELETE #groupby2 where TABLE_NAME =#currentTable
END
END
This code gives me an error:
Incorrect syntax near 'SELECT'
but doesn't give me the actual line it has the error on.
Your primary issue was syntax errors: parameter declaration should not have = between name and type name, and missing spaces in the dynamic SQL.
Also
A schema name (or any object) can be up to nvarchar(128), you can use the alias sysname
You don't need to do any loops or use temp tables, you can build one big dynamic statement to execute
CREATE procedure loopcheck
#schema sysname
AS
DECLARE #sql nvarchar(max) = (
SELECT STRING_AGG(CAST('
SELECT
TableName = ' + QUOTENAME(t.name, '''') + ',
IndexName = ' + QUOTENAME(i.name, '''') + ',
Duplicates = COUNT(*)
FROM (
SELECT 1 n
FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' t
GROUP BY ' + cols.agg + '
HAVING COUNT(*) > 1
) t
'
AS nvarchar(max)), 'UNION ALL')
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
AND s.name = #schema
JOIN sys.indexes i ON i.object_id = t.object_id
AND i.is_unique = 1
CROSS APPLY (
SELECT STRING_AGG('t.' + QUOTENAME(c.name), ', ')
FROM sys.index_columns ic
JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = t.object_id
WHERE ic.index_id = i.index_id
AND ic.object_id = i.object_id
AND ic.is_included_column = 0
) cols(agg)
);
PRINT #sql; -- for testing
EXEC sp_executesql #sql;
db<>fiddle
I feel like there might be a slightly more efficient method using GROUPING SETS in cases where there are multiple unique indexes/constraints on a single table, but I'll leave that to you.

How can we find the name of the constraint from the Database.?

I have table prsl which have auto generated name of the constraint. I want to search where the Database kept these name.
ALTER TABLE [dbo].[PRSL] DROP CONSTRAINT [PK__PRSL__1C1D47DC0BF1ACC7]
Actually, i want to drop these constraints dynamically.
For Example
SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[PRSL]')
drop all the constraint which are on a table.
Drop constraint 'when found'
If you're willing to display all constraints of a given table
select * from INFORMATION_SCHEMA.TABLE_CONSTRAINTS
where TABLE_NAME = 'YOUR TABLE NAME'
If you want to drop all constraints of the given table use this:
DECLARE #database nvarchar(50)
DECLARE #table nvarchar(50)
set #database = 'dotnetnuke'
set #table = 'tabs'
DECLARE #sql nvarchar(255)
WHILE EXISTS(select * from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where constraint_catalog = #database and table_name = #table)
BEGIN
select #sql = 'ALTER TABLE ' + #table + ' DROP CONSTRAINT ' + CONSTRAINT_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS
where constraint_catalog = #database and
table_name = #table
exec sp_executesql #sql
END
It worked for me...Hope it helps...

SQL: Query many tables with same column name but different structure for specific value

I'm working on cleaning up an ERP and I need to get rid of references to unused users and user groups. There are many foreign key constraints and therefor I want to be sure to really get rid of all traces!
I found this tidy tidbit of code to find all tables in my db with a certain column name, in this case let's look at the user groups:
select table_name from information_schema.columns
where column_name = 'GROUP_ID'
With the results I can search through the 40+ tables for my unused ID... but this is tedius. So I'd like to automate this and create a query that loops through all these tables and deletes the rows where it finds Unused_Group in the GROUP_ID column.
Before deleting anything I'd like to visualize the existing data, so I started to build something like this using string concatenation:
declare #group varchar(50) = 'Unused_Group'
declare #table1 varchar(50) = 'TABLE1'
declare #table2 varchar(50) = 'TABLE2'
declare #tableX varchar(50) = 'TABLEX'
select #query1 = 'SELECT ''' + rtrim(#table1) + ''' as ''Table'', '''
+ rtrim(#group) + ''' = CASE WHEN EXISTS (SELECT GROUP_ID FROM ' + rtrim(#table1)
+ ' WHERE GROUP_ID = ''' + rtrim(#group) + ''') then ''MATCH'' else ''-'' end FROM '
+ rtrim(#table1)
select #query2 = [REPEAT FOR #table2 to #tableX]...
EXEC(#query1 + ' UNION ' + #query2 + ' UNION ' + #queryX)
This gives me the results:
TABLE1 | Match
TABLE2 | -
TABLEX | Match
This works for my purposes and I can run it for any user group without changing any other code, and is of course easily adaptable to DELETE from these same tables, but is unmanageable for the 75 or so tables that I have to deal with between users and groups.
I ran into this link on dynamic SQL which was intense and dense enough to scare me away for the moment... but I think the solution might be in there somewhere.
I'm very familiar with FOR() loops in JS and other languages, where this would be a piece of cake with a well structured array, but apparently it's not so simple in SQL (I'm still learning, but found alot of negative talk about the FOR and GOTO solutions available...). Ideally a I'd have a script that queries to find tables with a certain column name, query each table as above, and spit me a list of matches, and then execute a second similar script to delete the rows.
Can anyone help point me in the right direction?
Ok, try this, there are three variables; column, colValue and preview. Column should be the column you're checking equality on (Group_ID), colValue the value you're looking for (Unused_Group) and preview should be 1 to view what you'll delete and 0 to delete it.
Declare #column Nvarchar(256),
#colValue Nvarchar(256),
#preview Bit
Set #column = 'Group_ID'
Set #colValue = 'Unused_Group'
Set #preview = 1 -- 1 = preview; 0 = delete
If Object_ID('tempdb..#tables') Is Not Null Drop Table #tables
Create Table #tables (tID Int, SchemaName Nvarchar(256), TableName Nvarchar(256))
-- Get all the tables with a column named [GROUP_ID]
Insert #tables
Select Row_Number() Over (Order By s.name, so.name), s.name, so.name
From sysobjects so
Join sys.schemas s
On so.uid = s.schema_id
Join syscolumns sc
On so.id = sc.id
Where so.xtype = 'u'
And sc.name = #column
Select *
From #tables
Declare #SQL Nvarchar(Max),
#schema Nvarchar(256),
#table Nvarchar(256),
#iter Int = 1
-- As long as there are tables to look at keep looping
While Exists (Select 1
From #tables)
Begin
-- Get the next table record to look at
Select #schema = SchemaName,
#table = TableName
From #tables
Where tID = #iter
-- If the table we're going to look at has dependencies on tables we have not
-- yet looked at move it to the end of the line and look at it after we look
-- at it's dependent tables (Handle foreign keys)
If Exists (Select 1
From sysobjects o
Join sys.schemas s1
On o.uid = s1.schema_id
Join sysforeignkeys fk
On o.id = fk.rkeyid
Join sysobjects o2
On fk.fkeyid = o2.id
Join sys.schemas s2
On o2.uid = s2.schema_id
Join #tables t
On o2.name = t.TableName Collate Database_Default
And s2.name = t.SchemaName Collate Database_Default
Where o.name = #table
And s1.name = #schema)
Begin
-- Move the table to the end of the list to retry later
Update t
Set tID = (Select Max(tID) From #tables) + 1
From #tables t
Where tableName = #table
And schemaName = #schema
-- Move on to the next table to look at
Set #iter = #iter + 1
End
Else
Begin
-- Delete the records we don't want anymore
Set #Sql = Case
When #preview = 1
Then 'Select * ' -- If preview is 1 select from table
Else 'Delete t ' -- If preview is not 1 the delete from table
End +
'From [' + #schema + '].[' + #table + '] t
Where ' + #column + ' = ''' + #colValue + ''''
Exec sp_executeSQL #SQL;
-- After we've done the work remove the table from our list
Delete t
From #tables t
Where tableName = #table
And schemaName = #schema
-- Move on to the next table to look at
Set #iter = #iter + 1
End
End
Turning this into a stored procedure would simply involve changing the variables declaration at the top to a sproc creation so you would get rid of...
Declare #column Nvarchar(256),
#colValue Nvarchar(256),
#preview Bit
Set #column = 'Group_ID'
Set #colValue = 'Unused_Group'
Set #preview = 1 -- 1 = preview; 0 = delete
...
And replace it with...
Create Proc DeleteStuffFromManyTables (#column Nvarchar(256), #colValue Nvarchar(256), #preview Bit = 1)
As
...
And you'd call it with...
Exec DeleteStuffFromManyTable 'Group_ID', 'Unused_Group', 1
I commented the hell out of the code to help you understand what it's doing; good luck!
You're on the right track with INFORMATION_SCHEMA objects. Execute the below in a query editor, it produces SELECT and DELETE statements for tables that contain GROUP_ID column with 'Unused_Group' value.
-- build select DML to manually review data that will be deleted
SELECT 'SELECT * FROM [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] WHERE [GROUP_ID] = ''Unused_Group'';'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'GROUP_ID';
-- build delete DML to remove data
SELECT 'DELETE FROM [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] WHERE [GROUP_ID] = ''Unused_Group'';'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'GROUP_ID';
Since this seems to be a one-time cleanup effort, and especially since you need to review data before it is deleted, I don't see the value in making this more complicated.
Consider adding referential integrity and enforcing cascade delete, if you can. It won't help with visualizing the data before you delete it, but will help controlling orphaned rows.

Why does SQL Server keep creating a DF constraint?

I'm trying to create upgrade and backout scripts in SQL. The upgrade script adds a column like so:
IF NOT EXISTS (SELECT * FROM sys.columns WHERE Name = N'ColumnName'
AND object_id = OBJECT_ID(N'[dbo].[TableName]'))
ALTER TABLE TableName
ADD ColumnName bit NOT NULL DEFAULT(0)
The backout script removes the column like so:
IF EXISTS (SELECT * FROM sys.columns WHERE Name = N'ColumnName'
AND object_id = OBJECT_ID(N'[dbo].[TableName]'))
ALTER TABLE TableName
DROP COLUMN ColumnName
However, the backout script throws this error:
Msg 5074, Level 16, State 1, Line 5
The object 'DF__TableName__ColumnName__1BF3D5BD' is dependent on column 'ColumnName'.
Msg 4922, Level 16, State 9, Line 5
ALTER TABLE DROP COLUMN ColumnName failed because one or more objects access this column.
I know how to drop the constraint, but the constraint's name changes everytime (the suffix changes). I either need SQL Server to stop creating this randomly-named constraint OR I need to be able to remove the constraint in my script using wild-card characters, since the name changes.
This is the default constraint that is added because of the DEFAULT(0) in your newly added column.
You can name this yourself so it has a known fixed name rather than relying on the auto name generation.
ALTER TABLE TableName
ADD ColumnName bit NOT NULL CONSTRAINT DF_Some_Fixed_Name DEFAULT(0)
Then to remove the column and constraint together
ALTER TABLE dbo.TableName
DROP CONSTRAINT DF_Some_Fixed_Name, COLUMN ColumnName
Run this:
declare #name as nvarchar(255);
SELECT #name = name FROM dbo.sysobjects
WHERE name LIKE 'DF__XXX__YYY__%' and type = 'D'
IF #name IS NOT NULL BEGIN
EXEC('ALTER TABLE XXX DROP CONSTRAINT ' + #name);
END
Run this if you want remove constraint:
DECLARE #tableName NVARCHAR(255) = '[INSERT]';
DECLARE #first5CharsFromColumnName NVARCHAR(255) = '[INSERT]';
DECLARE #name NVARCHAR(255);
SELECT #name = d.name FROM dbo.sysobjects d
INNER JOIN dbo.sysobjects t ON t.id = d.parent_obj
WHERE d.name LIKE '%'+#first5CharsFromColumnName+'%' AND d.type = 'D' AND t.name = #tableName
IF #name IS NOT NULL BEGIN
EXEC('ALTER TABLE '+#tableName+' DROP CONSTRAINT ' + #name);
END

Dropping unnamed constraints

I've created some foreign keys without an explicit name.
Then I've found SQL generated crazy names like FK__Machines__IdArt__760D22A7. Guess they will be generated with different names at different servers.
Is there any nice function to drop the unnamed FK constraints passing as arguments the tables and the fields in question?
For dropping an individual unnamed default constrain on a column use the following code:
DECLARE #ConstraintName VARCHAR(256)
SET #ConstraintName = (
SELECT obj.name
FROM sys.columns col
LEFT OUTER JOIN sys.objects obj
ON obj.object_id = col.default_object_id
AND obj.type = 'F'
WHERE col.object_id = OBJECT_ID('TableName')
AND obj.name IS NOT NULL
AND col.name = 'ColunmName'
)
IF(#ConstraintName IS NOT NULL)
BEGIN
EXEC ('ALTER TABLE [TableName] DROP CONSTRAINT ['+#ConstraintName+']')
END
If you want to do this for a default column, which is probably more common than the original question and I'm sure a lot of people will land on this from a Google search, then just change the line:
obj.type = 'F'
to
obj.type = 'D'
There is not a built in procedure to accomplish this, but you can build your own using the information in the information_schema views.
Table based example
Create Proc dropFK(#TableName sysname)
as
Begin
Declare #FK sysname
Declare #SQL nvarchar(4000)
Declare crsFK cursor for
select tu.Constraint_Name from
information_schema.constraint_table_usage TU
LEFT JOIN SYSOBJECTS SO
ON TU.Constraint_NAME = SO.NAME
where xtype = 'F'
and Table_Name = #TableName
open crsFK
fetch next from crsFK into #FK
While (##Fetch_Status = 0)
Begin
Set #SQL = 'Alter table ' + #TableName + ' Drop Constraint ' + #FK
Print 'Dropping ' + #FK
exec sp_executesql #SQL
fetch next from crsFK into #FK
End
Close crsFK
Deallocate crsFK
End
Although Gunner's answer puts people on the right track if you want to drop an actual DEFAULT constraint rather than an FKey constraint (which is what brought ME here too!) there are problems with it.
I think this fixes them all. (T-SQL)
CREATE PROC #DropDefaultConstraint #SchemaName sysname, #TableName sysname, #ColumnName sysname
AS
BEGIN
DECLARE #ConstraintName sysname;
SELECT #SchemaName = QUOTENAME(#SchemaName)
, #TableName = QUOTENAME(#TableName);
SELECT #ConstraintName = QUOTENAME(o.name)
FROM sys.columns c
JOIN sys.objects o
ON o.object_id = c.default_object_id
WHERE c.object_id = OBJECT_ID(#SchemaName+'.'+#TableName)
AND c.name = #ColumnName;
IF #ConstraintName IS NOT NULL
EXEC ('ALTER TABLE ' + #SchemaName + '.' + #TableName + ' DROP CONSTRAINT ' + #ConstraintName + '');
END
This will let you drop a specific foreign key constraint based on tablename + column name
After trying out the other answers I just had a poke around in the system tables until I found something likely looking.
The one you want is Constraint_Column_Usage which according to the docs Returns one row for each column in the current database that has a constraint defined on the column.
I've joined it to sys.objects to just get foreign keys.
In a procedure (this borrows from the other answers. cheers guys!):
Create Proc yourSchema.dropFK(#SchemaName NVarChar(128), #TableName NVarChar(128), #ColumnName NVarChar(128))
as
Begin
DECLARE #ConstraintName nvarchar(128)
SET #ConstraintName = (
select c.Constraint_Name
from Information_Schema.Constraint_Column_usage c
left join sys.objects o
on o.name = c.Constraint_Name
where c.TABLE_SCHEMA = #SchemaName and
c.Table_name = #TableName and
c.Column_Name = #ColumnName and
o.type = 'F'
)
exec ('alter table [' + #SchemaName + '].[' + #TableName + '] drop constraint [' + #ConstraintName + ']')
End
Neither of these worked for me so I had to come up with this to work on mssql server version both 12 and 14.
First, inspect the name given to the FK by the RDBMS, it has the same prefix and body but differs only in suffix hash.
Second, select names of these constraints.
Third, exec alter command that drops them.
Finally you can drop the column or table blocked by these
BEGIN TRANSACTION;
DECLARE #ConstraintName nvarchar(200)
SELECT #ConstraintName = name
FROM sys.objects
WHERE type_desc = 'FOREIGN_KEY_CONSTRAINT'
AND name LIKE 'FK__table_col_shortcut1___%'
EXEC('ALTER TABLE table1 DROP CONSTRAINT ' + #ConstraintName)
SELECT #ConstraintName = name
FROM sys.objects
WHERE type_desc = 'FOREIGN_KEY_CONSTRAINT'
AND name LIKE 'FK__table_col_shortcut2___%'
EXEC('ALTER TABLE table2 DROP CONSTRAINT ' + #ConstraintName)
SELECT #ConstraintName = name
FROM sys.objects
WHERE type_desc = 'FOREIGN_KEY_CONSTRAINT'
AND name LIKE 'FK__table_col_shortcut3___%'
EXEC('ALTER TABLE table3 DROP CONSTRAINT ' + #ConstraintName)
DROP TABLE table_referenced;
COMMIT TRANSACTION;
Lesson learnt, I will always create constraints explicitly from now on!
This will generate a script to rename default constraints to use the pattern
DF__table_name__column_name
SELECT 'EXEC sp_rename ''dbo.' + dc.name + ''', ''DF__' + t.name + '__' + c.name + '''' AS the_script,
t.name AS table_name,
c.name AS column_name,
dc.name AS old_constraint_name
FROM
sys.default_constraints dc
INNER JOIN sys.tables t
ON dc.parent_object_id = t.object_id
INNER JOIN sys.columns c
ON dc.parent_column_id = c.column_id
AND t.object_id = c.object_id
WHERE
dc.name <> 'DF__' + t.name + '__' + c.name