Search code examples
sql-servercascading-deletes

How to cascade-delete temporarily or on-demand?


Sometimes I'm trying to delete just ONE row in MSSQL and I fall into countless deletes up the hierarchy because of references due to foreign-key constraints. Is there any quick way to automatically cascade-delete without having to setup the foreign-key constraints with cascade delete? It's just this one time that I need the cascade-delete... on-demand -- not always.

Any chance? Any equivalents?


Solution

  • If you want a point and shoot dynamic sql solution, this uses a recursive query to build a table hierarchy for foreign keys that descends from a particlar key. With that it generates the delete statements that need to be executed, in order (hopefully), to delete a particular row from a table.

    use AdventureWorks2012
    
    declare @tablename sysname = N'Production.Product';
    declare @primarykeycolumn sysname = N'ProductId';
    declare @value nvarchar(128) = '2';
    declare @sql nvarchar(max);
    
    ;with tableHierarchy as (
    select
        object_id = p.object_id
      , parent_id = cast(null as int)
      , schemaName = schema_name(p.schema_id)
      , tableName = object_name(p.object_id)
      , parentObjectName = cast(null as sysname)
      , parentToChild = cast(object_name(p.object_id) as varchar(max))
      , childToParent = cast(object_name(p.object_id) as varchar(max))
      , treelevel = 0
      , keyName = p.name
      , columnName = c.name
      , columnId = c.column_id
      , parentColumnName = c.name
    from sys.objects as p
      inner join sys.columns c
        on p.object_id = c.object_id
    where p.object_id  = object_id(@tablename)
      and c.name = @primarykeycolumn
    union all
    select
        object_id = fk.parent_object_id
      , parent_id = fk.referenced_object_id
      , schemaName = schema_name(fk.schema_id)
      , tableName = object_name(fk.parent_object_id)
      , parentObjectName = object_name(fk.referenced_object_id)
      , parentToChild = parentToChild + ' \ ' + cast(object_name(fk.parent_object_id) as varchar(128))
      , childToParent = cast(object_name(fk.parent_object_id) as varchar(128)) + ' \ ' + childToParent
      , treelevel = th.treelevel + 1
      , keyName = fk.name
      , columnName = c.name
      , columnId = c.column_id
      , parentColumnName = rc.name
    from tableHierarchy as th
      inner join sys.foreign_keys as fk
        on fk.referenced_object_id = th.object_id
       and fk.referenced_object_id != fk.parent_object_id 
      inner join sys.foreign_key_columns fkc
        on fk.object_id = fkc.constraint_object_id
      and fkc.referenced_column_id = th.columnId
      inner join sys.columns c
        on fkc.parent_object_id = c.object_id
       and fkc.parent_column_id = c.column_id
      inner join sys.columns rc
        on fkc.referenced_object_id = rc.object_id
       and fkc.referenced_column_id = rc.column_id
    )
    select @sql = stuff((
      select 
          char(10)
        --+'/* treelevel: '+convert(nvarchar(10),treelevel)
        --+' | ' + childtoparent +' */'+char(10)
        +'delete from '+quotename(schemaName)+'.'+quotename(tableName)
        +' where '+quotename(columnName)+' = '+@value+';'
      from tableHierarchy
      group by treelevel, childtoparent, schemaName, tableName, columnName
      order by treelevel desc, childtoparent
      for xml path (''), type).value('.','nvarchar(max)')
      ,1,1,'')
      option ( maxrecursion 100 );
    
    select @sql as CodeGenerated;
    --exec sp_executesql @sql;
    

    Code generated:

    delete from [Sales].[SalesOrderDetail] where [ProductID] = 2;
    delete from [Production].[BillOfMaterials] where [ComponentID] = 2;
    delete from [Production].[BillOfMaterials] where [ProductAssemblyID] = 2;
    delete from [Production].[ProductCostHistory] where [ProductID] = 2;
    delete from [Production].[ProductDocument] where [ProductID] = 2;
    delete from [Production].[ProductInventory] where [ProductID] = 2;
    delete from [Production].[ProductListPriceHistory] where [ProductID] = 2;
    delete from [Production].[ProductProductPhoto] where [ProductID] = 2;
    delete from [Production].[ProductReview] where [ProductID] = 2;
    delete from [Purchasing].[ProductVendor] where [ProductID] = 2;
    delete from [Purchasing].[PurchaseOrderDetail] where [ProductID] = 2;
    delete from [Sales].[ShoppingCartItem] where [ProductID] = 2;
    delete from [Sales].[SpecialOfferProduct] where [ProductID] = 2;
    delete from [Production].[TransactionHistory] where [ProductID] = 2;
    delete from [Production].[WorkOrder] where [ProductID] = 2;
    delete from [Production].[Product] where [ProductID] = 2;