web-dev-qa-db-fra.com

Colonne de dépôt SQL Server 2005 avec contraintes

J'ai une colonne avec une contrainte "DEFAULT". Je voudrais créer un script qui supprime cette colonne.

Le problème est qu'il renvoie cette erreur:

Msg 5074, Level 16, State 1, Line 1  
The object 'DF__PeriodSce__IsClo__4BCC3ABA' is dependent on column 'IsClosed'. 
Msg 4922, Level 16, State 9, Line 1 
ALTER TABLE DROP COLUMN IsClosed failed because one or more objects access this column.

Je n'ai pas pu trouver un moyen facile de supprimer une colonne et toutes ses contraintes associées (seulement trouvé de gros scripts qui regardent dans la table système ... il DOIT (!!) être une "belle" façon de le faire.)

Et comme le nom de la contrainte DEFAULT a été généré de manière aléatoire, je ne peux pas le supprimer par son nom.


Mise à jour:
Le type de contrainte est "DEFAUT".

J'ai vu les solutions que vous avez toutes proposées mais je les trouve toutes vraiment "sales" ... Vous ne pensez pas? Je ne sais pas si c'est avec Oracle ou MySQL mais il est possible de faire quelque chose comme:

DROP COLUMN xxx CASCADE CONSTRAINTS 

Et il supprime toutes les contraintes associées ... Ou au moins il supprime automatiquement les contraintes mappées à cette colonne (au moins CHECK contraintes!)

N'y a-t-il rien de tel dans MSSQL?

60
Julien N

Cette requête trouve les contraintes par défaut pour une table donnée. Ce n'est pas joli, je suis d'accord:

select 
    col.name, 
    col.column_id, 
    col.default_object_id, 
    OBJECTPROPERTY(col.default_object_id, N'IsDefaultCnst') as is_defcnst, 
    dobj.name as def_name
from sys.columns col 
    left outer join sys.objects dobj 
        on dobj.object_id = col.default_object_id and dobj.type = 'D' 
where col.object_id = object_id(N'dbo.test') 
and dobj.name is not null

[EDIT] Mis à jour selon le commentaire de Julien N

21
edosoft

Voici un script qui supprimera la colonne ainsi que sa contrainte par défaut. Remplacez MYTABLENAME et MYCOLUMNNAME de manière appropriée.

declare @constraint_name sysname, @sql nvarchar(max)

select @constraint_name = name 
from sys.default_constraints 
where parent_object_id = object_id('MYTABLENAME')
AND type = 'D'
AND parent_column_id = (
    select column_id 
    from sys.columns 
    where object_id = object_id('MYTABLENAME')
    and name = 'MYCOLUMNNAME'
    )

set @sql = N'alter table MYTABLENAME drop constraint ' + @constraint_name
exec sp_executesql @sql

alter table MYTABLENAME drop column MYCOLUMNNAME

go
64
Jeremy Stein

Cela pourrait peut-être aider un peu plus:

declare @tablename nvarchar(200)
declare @colname nvarchar(200)
declare @default sysname, @sql nvarchar(max)

set @tablename = 'your table'
set @colname = 'column to drop'

select @default = name 
from sys.default_constraints 
where parent_object_id = object_id(@tablename)
AND type = 'D'
AND parent_column_id = (
    select column_id 
    from sys.columns 
    where object_id = object_id(@tablename)
    and name = @colname 
    )

set @sql = N'alter table ' + @tablename + ' drop constraint ' + @default
exec sp_executesql @sql

set @sql = N'alter table ' + @tablename + ' drop column ' + @colname
exec sp_executesql @sql

Il suffit de définir les variables @tablename & @colname pour supprimer la colonne.

16
jjroman

Je pense également que c'est une lacune dans SQL Server de ne pas avoir de goutte en cascade disponible. J'ai travaillé autour de lui en interrogeant les tables système de la même manière que les autres personnes décrites ici:

  • INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE répertorie uniquement les clés étrangères, les clés primaires et les contraintes uniques.
  • La seule façon de rechercher les contraintes par défaut est de les rechercher dans sys.default_constraints.
  • ce qui n'a pas encore été mentionné ici, c'est que indexes fait également échouer la suppression d'une colonne, vous devez donc également supprimer tous les index qui utilisent votre colonne avant de pouvoir supprimer une colonne.

Le script résultant n'est pas joli, mais je l'ai mis dans une procédure stockée pour pouvoir le réutiliser:

CREATE PROCEDURE DropColumnCascading @tablename nvarchar(500), @columnname nvarchar(500)
AS

SELECT CONSTRAINT_NAME, 'C' AS type
INTO #dependencies
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = @tablename AND COLUMN_NAME = @columnname

INSERT INTO #dependencies
select d.name, 'C'
from sys.default_constraints d
join sys.columns c ON c.column_id = d.parent_column_id AND c.object_id = d.parent_object_id
join sys.objects o ON o.object_id = d.parent_object_id
WHERE o.name = @tablename AND c.name = @columnname

INSERT INTO #dependencies
SELECT i.name, 'I'
FROM sys.indexes i
JOIN sys.index_columns ic ON ic.index_id = i.index_id and ic.object_id=i.object_id
JOIN sys.columns c ON c.column_id = ic.column_id and c.object_id=i.object_id
JOIN sys.objects o ON o.object_id = i.object_id
where o.name = @tableName AND i.type=2 AND c.name = @columnname AND is_unique_constraint = 0

DECLARE @dep_name nvarchar(500)
DECLARE @type nchar(1)

DECLARE dep_cursor CURSOR
FOR SELECT * FROM #dependencies

OPEN dep_cursor

FETCH NEXT FROM dep_cursor 
INTO @dep_name, @type;

DECLARE @sql nvarchar(max)

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @sql = 
        CASE @type
            WHEN 'C' THEN 'ALTER TABLE [' + @tablename + '] DROP CONSTRAINT [' + @dep_name + ']'
            WHEN 'I' THEN 'DROP INDEX [' + @dep_name + '] ON dbo.[' + @tablename + ']'
        END
    print @sql
    EXEC sp_executesql @sql
    FETCH NEXT FROM dep_cursor 
    INTO @dep_name, @type;
END

DEALLOCATE dep_cursor

DROP TABLE #dependencies

SET @sql = 'ALTER TABLE [' + @tablename + '] DROP COLUMN [' + @columnname + ']'

print @sql
EXEC sp_executesql @sql
6
pvolders
> select CONSTRAINT_NAME from INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
> WHERE TABLE_NAME = '<tablename>' AND COLUMN_NAME = 'IsClosed'

Ce n'est pas la bonne solution, comme cela est expliqué ici: http://msdn.Microsoft.com/en-us/library/aa175912.aspx que:

Malheureusement, le nom de la contrainte par défaut de la colonne n'est pas conservé dans la vue ANSI COLUMNS, vous devez donc revenir aux tables système pour trouver le nom

Le seul moyen que j'ai trouvé pour obtenir le nom de la contrainte DEFAULT est cette requête:

select  
    t_obj.name              as TABLE_NAME
    ,c_obj.name             as CONSTRAINT_NAME
    ,col.name               as COLUMN_NAME

from    sysobjects  c_obj
join    sysobjects  t_obj on c_obj.parent_obj = t_obj.id  
join    sysconstraints con on c_obj.id  = con.constid
join    syscolumns  col on t_obj.id = col.id
            and con.colid = col.colid
where
    c_obj.xtype = 'D'

Suis-je le seul à le trouver fou pour ne pas pouvoir supprimer facilement une contrainte qui ne concerne que les colonnes que j'essaye de supprimer?
J'ai besoin d'exécuter une demande avec 3 jointures juste pour obtenir le nom ...

6
Julien N

La réponse des pvolders était exactement ce dont j'avais besoin mais il manquait des statistiques qui causaient et des erreurs. Il s'agit du même code, moins la création d'une procédure stockée, plus l'énumération des statistiques et leur suppression. C'est le meilleur que j'ai pu trouver, donc s'il existe un meilleur moyen de déterminer quelles statistiques doivent être supprimées, veuillez ajouter.

DECLARE @tablename nvarchar(500), 
        @columnname nvarchar(500)

SELECT  @tablename = 'tblProject',
        @columnname = 'CountyKey'


SELECT CONSTRAINT_NAME, 'C' AS type
INTO #dependencies
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = @tablename AND COLUMN_NAME = @columnname

INSERT INTO #dependencies
select d.name, 'C'
from sys.default_constraints d
join sys.columns c ON c.column_id = d.parent_column_id AND c.object_id = d.parent_object_id
join sys.objects o ON o.object_id = d.parent_object_id
WHERE o.name = @tablename AND c.name = @columnname

INSERT INTO #dependencies
SELECT i.name, 'I'
FROM sys.indexes i
JOIN sys.index_columns ic ON ic.index_id = i.index_id and ic.object_id=i.object_id
JOIN sys.columns c ON c.column_id = ic.column_id and c.object_id=i.object_id
JOIN sys.objects o ON o.object_id = i.object_id
where o.name = @tableName AND i.type=2 AND c.name = @columnname AND is_unique_constraint = 0

INSERT INTO #dependencies
SELECT s.NAME, 'S'
FROM sys.stats AS s
INNER JOIN sys.stats_columns AS sc 
    ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id
INNER JOIN sys.columns AS c 
    ON sc.object_id = c.object_id AND c.column_id = sc.column_id
WHERE s.object_id = OBJECT_ID(@tableName)
AND c.NAME = @columnname
AND s.NAME LIKE '_dta_stat%'

DECLARE @dep_name nvarchar(500)
DECLARE @type nchar(1)

DECLARE dep_cursor CURSOR
FOR SELECT * FROM #dependencies

OPEN dep_cursor

FETCH NEXT FROM dep_cursor 
INTO @dep_name, @type;

DECLARE @sql nvarchar(max)

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @sql = 
        CASE @type
            WHEN 'C' THEN 'ALTER TABLE [' + @tablename + '] DROP CONSTRAINT [' + @dep_name + ']'
            WHEN 'I' THEN 'DROP INDEX [' + @dep_name + '] ON dbo.[' + @tablename + ']'
            WHEN 'S' THEN 'DROP STATISTICS [' + @tablename + '].[' + @dep_name + ']'
        END
    print @sql
    EXEC sp_executesql @sql
    FETCH NEXT FROM dep_cursor 
    INTO @dep_name, @type;
END

DEALLOCATE dep_cursor

DROP TABLE #dependencies

SET @sql = 'ALTER TABLE [' + @tablename + '] DROP COLUMN [' + @columnname + ']'

print @sql
EXEC sp_executesql @sql
4
Steve

Vous pouvez obtenir les noms des contraintes en interrogeant les vues système information_schema.

select CONSTRAINT_NAME from INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = '<tablename>' AND COLUMN_NAME = 'IsClosed'
4
Steve Sheldon

Juste pour construire sur la réponse de Jeremy Stein, j'ai créé une procédure stockée pour cela, et je l'ai configurée pour qu'elle puisse être utilisée pour supprimer une colonne qui a ou n'a pas de contraintes par défaut. Ce n'est pas vraiment efficace car il interroge sys.columns deux fois, mais cela fonctionne.

CREATE PROCEDURE [dbo].[RemoveColumnWithDefaultConstraints] 
    -- Add the parameters for the stored procedure here
    @tableName nvarchar(max), 
    @columnName nvarchar(max)
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    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 = (@columnName) 
        AND object_id = OBJECT_ID(@tableName))
    IF @ConstraintName IS NOT NULL
        EXEC('ALTER TABLE ' + @tableName + ' DROP CONSTRAINT ' + @ConstraintName)

    IF EXISTS(SELECT * FROM sys.columns WHERE Name = @columnName  
        AND Object_ID = Object_ID(@tableName))
        EXEC('ALTER TABLE ' + @tableName + ' DROP COLUMN ' + @columnName)       
END
GO
4
Stealth Rabbi

Rechercher le nom de la contraint ou utiliser la vue de conception MSSQL n'est pas toujours une option. Je veux actuellement créer un script pour supprimer une colonne avec une contraint dessus. Utiliser le nom n'est pas une option car le nom est généré et je veux utiliser le script dans différents environnements Dev/Sys/Prod/etc. L'utilisation du nom n'est alors pas possible car les noms de contraint diffèrent selon l'environnement. Je devrai probablement examiner le tableau des systèmes, mais je suis d'accord qu'il devrait y avoir une option plus simple disponible.

2
Jeroen

Je crois que supprimer explicitement les contraintes avant de supprimer la colonne est une solution "plus propre". De cette façon, vous ne supprimez pas les contraintes dont vous n'êtes peut-être pas conscient. Si la suppression échoue toujours, vous savez qu'il reste des contraintes supplémentaires. J'aime contrôler exactement ce qui arrive à ma base de données.

De plus, l'écriture des scripts garantit explicitement le script et, espérons-le, les résultats peuvent être reproduits exactement comme vous le souhaitez.

1
DCNYAM

Je suis juste tombé sur ça. Vous pouvez supprimer la colonne avec des contraintes à l'aide de la vue de conception MSSQL. Faites un clic droit sur la colonne que vous souhaitez supprimer (avec ou sans contraintes) et vous pouvez la supprimer sans aucun problème. Ha .. J'avais déjà l'air stupide.

0
Bitmask

Que voulez-vous dire généré de façon aléatoire? Vous pouvez rechercher les contraintes sur la colonne spécifique dans le studio de gestion ou via la vue sys.tables et trouver le ou les noms.

Ensuite, vous pouvez modifier votre script pour supprimer les contraintes avant de supprimer la colonne. De quel type de contrainte s'agit-il? S'il s'agit d'une contrainte de clé étrangère, assurez-vous que cela ne nuira pas à l'intégrité des données dans votre base de données.

0
DCNYAM