Delete unrelated records in one of some related tables - sql

There is a table MainTable with a column MyField. There are some tables related by foreign keys to MainTable. Their names are unknown.
I need:
to find and delete all rows in MainTable which have no relation to any other table
query should return all MyField values of these deleted rows
Or as an option: query should return all MyField values for rows which are to be deleted.
Thanks!
UpDate1
Sorry I forgot to say I use Sql Server 2005.
MainTable has PK.
Other tables have FK.
Example
create table MainTable
(
ID int IDENTITY primary key,
MyField nvarchar(256)
);
create table OtherTable1
(
ID int IDENTITY primary key,
MainTableID int foreign key references MainTable(ID)
);
create table OtherTable2
(
ID int IDENTITY primary key,
MainTableID int foreign key references MainTable(ID)
);
-- and so on
insert into MainTable (MyField) values('A');
insert into MainTable (MyField) values('BB');
insert into MainTable (MyField) values('CCC');
insert into MainTable (MyField) values('DDDD');
insert into MainTable (MyField) values('FFFFF');
insert into OtherTable1 (MainTableID) values(1);
insert into OtherTable1 (MainTableID) values(2);
insert into OtherTable2 (MainTableID) values(3);
Result i need:
MainTable
ID MyField
4 DDDD
5 FFFFF
Query should return
A
BB
CCC

You need dynamic SQL for this.
You can use the system views to pull out the foreign key information and build up a dynamic statement, either SELECT or DELETE.
DECLARE #sql nvarchar(max) = '
SELECT t.MyField
-- alternately DELETE t
FROM MainTable t
WHERE ' + (
SELECT STRING_AGG(CAST(
'
NOT EXISTS (SELECT 1
FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' t2.
WHERE ' + c.ForeignKeys + ')
'
AS nvarchar(max)), '
AND '
)
FROM sys.tables MyTable
JOIN sys.foreign_keys fk ON fk.referenced_object_id = MyTable.object_id
JOIN sys.tables t ON t.object_id = fk.parent_object_id
JOIN sys.schemas s ON s.schema_id = t.schema_id
OUTER APPLY (
SELECT STRING_AGG('t2.' + QUOTENAME(c.name) + ' = t.' + QUOTENAME(cMyTable.name), ' AND ')
FROM sys.foreign_key_columns fkc
JOIN sys.columns c ON c.object_id = fk.parent_object_id AND c.column_id = fkc.parent_column_id
JOIN sys.columns cMyTable ON cMyTable.object_id = MyTable.object_id AND cMyTable.column_id = fkc.referenced_column_id
WHERE fkc.constraint_object_id = fk.object_id
) c(ForeignKeys)
WHERE MyTable.name = 'MainTable'
);
PRINT #sql; -- for testing
EXEC sp_executesql #sql;
db<>fiddle

Related

Update table with matching primary keys

I have two tables TABLE1 and TABLE2.
I need to update TABLE1 with the matching primary keys from TABLE2.
Here is my code to get the primary key from TABLE1:
SELECT C.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS T
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE C ON C.CONSTRAINT_NAME = T.CONSTRAINT_NAME
WHERE C.TABLE_NAME = 'TABLE1'
AND T.CONSTRAINT_TYPE = 'PRIMARY KEY'
This is the code to get primary key for TABLE2:
SELECT C.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS T
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE C ON C.CONSTRAINT_NAME = T.CONSTRAINT_NAME
WHERE C.TABLE_NAME = 'TABLE2'
AND T.CONSTRAINT_TYPE = 'PRIMARY KEY'
And this code is used to update TABLE1:
MERGE INTO <TABLE1>
USING <TABLE2> ON (TABLE1.COLUMN_NAME = TABLE2.COLUMN_NAME)
WHEN MATCHED THEN
UPDATE
SET <TABLE1.COLUMN_NAME> = <TABLE1.COLUMN_NAME>
I need to merge all snippets of code to update the TABLE1, please help me how to get the primary keys and update with a single code.
As #GuidoG recommended, you will probably need to use dynamic SQL to perform this feat. Furthermore, from your question it is hard to figure out how to determine that Table1 and Table2 are related to one another.
Let us suppose that you have a table with table pairs available, and Table1 and Table 2 are one of those pairs. We'll need to figure out what the primary keys of both tables are, and then build the dynamic SQL using this information.
DECLARE #tableCombinations TABLE ([MainTable] NVARCHAR(255), [SubTable] NVARCHAR(255))
INSERT INTO #tableCombinations ([MainTable], [SubTable]) VALUES ('Table1','Table2')
DECLARE #q NVARCHAR(MAX) = ''
;WITH PrimaryKeys AS (
SELECT [Schema_Name] = sch.[name]
, [Table_Name] = tbl.[name]
, [Column_Name] = cols.[name]
FROM [sys].[indexes] i
JOIN [sys].[index_columns] ic ON i.[index_id] = ic.[index_id] AND i.[object_id] = ic.[object_id]
JOIN [sys].[columns] cols ON cols.[object_id] = ic.[object_id] AND cols.[column_id] = ic.[column_id]
JOIN [sys].[tables] tbl ON ic.[object_id] = tbl.[object_id]
JOIN [sys].[schemas] sch ON tbl.[schema_Id] = sch.[schema_id]
WHERE i.[is_primary_key] = 1
)
SELECT #q += 'MERGE INTO ' + QUOTENAME(pkMain.[Schema_Name]) + '.' + QUOTENAME(pkMain.[Table_Name]) +' mt
USING ' + QUOTENAME(pkSub.[Schema_Name]) + '.' + QUOTENAME(pkSub.[Table_Name]) + 'st ON (
mt.' + QUOTENAME(pkMain.[Column_Name]) + ' = st.' + pkSub.[Column_Name] + ')
WHEN MATCHED THEN UPDATE SET mt.' + QUOTENAME(pkMain.[Column_Name]) + ' = st.' + pkSub.[Column_Name] + ';' + CHAR(13) + CHAR(10)
FROM #tableCombinations tc
JOIN [PrimaryKeys] pkMain ON tc.[MainTable] = pkMain.[Table_Name]
JOIN [PrimaryKeys] pkSub ON tc.[SubTable] = pkSub.[Table_Name]
SET #q = REPLACE(#q, ' ', '')
SELECT #q
Of course, if this yields the correct results, you can directly execute this query using [sys].[sp_executesql]. For more reliable results, be sure to include the schema names of the table combinations.

How to get the relationship between 2 random tables in relational database

My requirement is to Join table A and table D. I know there are tables B and C in between which have PK and FK relationships. I want a query to know the tables B and C and the keys involved in the relationship. I need to know this for the entire database. Database diagram will give me the relationship but I don't have permissions to view the diagram. Moreover, I need to look for each set of tables. That's why I am trying to write a query that does the job for me.
In short, if I provide Table A and Table D to the query then query should give me essential columns required (in our case B.id2,C.id3) to join A and D.
SELECT A.*, D.* FROM A Join B on A.id1 = B.id2 Join C on B.id2 = C.id3 Join D on C.id3 = D.id4
Thanks in advance.
I pulled up the one I wrote at work; looks like I didn't actually bother with the CTE (since they're a royal pain in the butt IMHO). It's a little lengthy since it's just code I ripped from a proc. My version originally didn't include the columns, but I added them in. I just concatenate them as though they were an ON clause, but you can tweak that how you like.
use AdventureWorks2014
go
declare
#BaseTable nvarchar(128) = 'SalesPerson',
#BaseTableSchema nvarchar(128) = 'Sales'
declare
#Depth int = 0,
#RowCount int,
#Ident int
declare #KeyHierarchy table
(
Depth int,
ReferencingTableSchema nvarchar(128),
ReferencingTableName nvarchar(128),
ReferencingObjectId int,
ReferencingTableNameFull as quotename(ReferencingTableSchema) + '.' + quotename(ReferencingTableName),
ReferencedTableSchema nvarchar(128),
ReferencedTableName nvarchar(128),
ReferencedObjectId int,
ReferencedTableNameFull as quotename(ReferencedTableSchema) + '.' + quotename(ReferencedTableName),
MatchingColumns nvarchar(max)
primary key clustered (ReferencingTableSchema, ReferencingTableName)
)
insert into #KeyHierarchy
(
Depth,
ReferencingTableSchema,
ReferencingTableName,
ReferencingObjectId
)
select
Depth = #Depth,
ReferencingTableSchema = #BaseTableSchema,
ReferencingTableName = #BaseTable,
ReferencingObjectId = object_id(#BaseTableSchema + '.' + #BaseTable)
select #RowCount = 1
while #RowCount > 0
begin
insert into #KeyHierarchy
(
Depth,
ReferencedTableSchema,
ReferencedTableName,
ReferencedObjectId,
ReferencingTableSchema,
ReferencingTableName,
ReferencingObjectId,
MatchingColumns
)
select distinct
Depth = #Depth + 1,
ReferencedTableSchema = object_schema_name(f.referenced_object_id),
ReferencedTableName = object_name(f.referenced_object_id),
ReferencedObjectId = f.referenced_object_id,
ReferencingTableSchema = object_schema_name(f.parent_object_id),
ReferencingTableName = object_name(f.parent_object_id),
ReferencingObjectId = f.parent_object_id,
MatchingColumns = stuff
(
(
select
concat
(
' and parent.',
quotename(col_name(c.parent_object_id, c.parent_column_id)),
' = ',
'referenced.',
quotename(col_name(c.referenced_object_id, c.referenced_column_id))
)
from sys.foreign_key_columns c
where f.object_id = c.constraint_object_id
order by c.constraint_column_id
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 4, ''
)
from #KeyHierarchy k
inner join sys.foreign_keys f
on f.referenced_object_id = k.ReferencingObjectId
and f.parent_object_id not in (select ReferencingObjectId from #KeyHierarchy where Depth < #Depth + 1)
where k.Depth = #Depth
select
#RowCount = ##RowCount,
#Depth += 1
end
select
BaseTable = #BaseTable,
ReferencingTableNameFull,
ReferencedTableNameFull,
MatchingColumns,
Depth
from #KeyHierarchy
order by Depth, ReferencingTableNameFull
This has some problems but it is a start
select t.name [t_name], c.name [c_name], f.*
from sys.foreign_keys f
join sys.tables t
on f.parent_object_id = t.object_id
and f.type = 'F'
left join sys.columns c
on c.object_id = f.referenced_object_id
and c.system_type_id = 56
You can fiddle about from this starting point:
with
ForeignKeys as (
-- All foreign keys with their associated tables and columns.
select fk.name as ConstraintName, fk.object_id as ContraintObjectId,
pt.name as ParentTable, pt.object_id as ParentTableObjectId, pc.name as ParentColumn, pc.column_id as ParentColumnId,
ft.name as ForeignTable, ft.object_id as ForeignTableObjectId, fc.name as ForeignColumn, fc.column_id as ForeignColumnId
from sys.foreign_keys as fk inner join
sys.foreign_key_columns as fkc on fkc.constraint_object_id = fk.object_id inner join
sys.tables as pt on pt.object_id = fkc.parent_object_id inner join
sys.columns as pc on pc.object_id = pt.object_id and pc.column_id = fkc.parent_column_id inner join
sys.tables as ft on ft.object_id = fkc.referenced_object_id inner join
sys.columns as fc on fc.object_id = ft.object_id and fc.column_id = fkc.referenced_column_id ),
RelatedTables as (
-- All ordered pairs of directly related tables.
select distinct ParentTable, ParentTableObjectId, ForeignTable, ForeignTableObjectId
from ForeignKeys ),
RelatedTablesHierarchy as (
-- Hierarchy of related tables.
-- Start from each distinct table that has a foreign key relation ...
select ParentTable, ParentTableObjectId, ForeignTable, ForeignTableObjectId,
Cast( ParentTable + '»' + ForeignTable as NVarChar(4000) ) as Path
from RelatedTables
union all
-- ... and add any other table to which the foreign table is related.
select RTH.ParentTable, RTH.ParentTableObjectId, RT.ForeignTable, RT.ForeignTableObjectId,
Cast( Path + '»' + RT.ForeignTable as NVarChar(4000) )
from RelatedTablesHierarchy as RTH inner join
RelatedTables as RT on RT.ParentTableObjectId = RTH.ForeignTableObjectId
-- NB: Avoid getting caught in reference loops.
where '»' + Path + '»' not like '%»' + RT.ForeignTable + '»%'
)
-- Output the results with Path made a little easier to read.
select Replace( Path, '»', ' » ' ) as Path, ParentTable, ParentTableObjectId, ForeignTable, ForeignTableObjectId
from RelatedTablesHierarchy
order by Path;
The initial query is certainly carrying some extra baggage, but you will need it to determine the table and column names necessary for joins. (Tip: Use QuoteName() when assembling object names into statements.)
NB: The common table expression is resistant to reference loops, e.g. tables that have foreign key references to themselves as well as longer loops involving multiple tables.

Create Sql Server view using table name from sysobjects table

I have around 200 tables. I want to create a view from all these tables. I feel it is inefficient to hardcode all the table names and do an UNION ALL in the view definition.
Instead I am planning to retrieve the table name from sysobjects table like
Select name from sysobjects where name like 'Warehouse_Inventory%'
How can I use these table names and create a view out of it?
Note: I am selecting only 10 columns which are common. If any column is not present in a table, I want to display NULL for it.
This Query may help you..
SELECT 'CREATE VIEW VIEW_NAME AS'
UNION ALL
SELECT 'SELECT * FROM ['+NAME+']
UNION ALL' FROM SYS.TABLES where name like 'Warehouse_Inventory%'
I am not sure why you want to use sys.sysojects instead of other sys views. Also now sure why when you want to union all tables you would want to search by a table name..... I would probably recommend a cursor on tables and temp tables to hold your results if you have 200 tables just do to size of the query but if you really really want to do it via union all here is a way...
Build a list of the 10 columns you want. Then run the query. you may need to tweak and add some cast/convert function to ensure everything is the right datatypes this can be done dynamically with sys.types and sys.columns or just make sure everything is a NVARCHAR(???) by altering my dynamic sql below and move forward.
DECLARE #ListOfColumns AS TABLE (ColumnName VARCHAR(100))
INSERT INTO #ListOfColumns (ColumnName) VALUES ('col1'),('col2'),('col3')
DECLARE #SQLStatement NVARCHAR(MAX)
;WITH cteColumnsTableCross AS (
SELECT
SchemaName = s.name
,t.schema_id
,TableName = t.name
,l.ColumnName
FROm
#ListOfColumns l
CROSS JOIN sys.tables t
INNER JOIN sys.schemas s
ON t.schema_id = s.schema_id
)
, cteColumns AS (
SELECT
x.SchemaName
,x.TableName
,x.ColumnName
,ColumnExists = IIF(c.name IS NOT NULL,1,0)
,RowNum = ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY x.TableName DESC)
--you can add data type by getting from sys.columns and sys.types if desired
FROM
cteColumnsTableCross x
LEFT JOIN sys.tables t
ON x.TableName = t.name
AND x.schema_id = t.schema_id
LEFT JOIN sys.columns c
ON t.object_id = c.object_id
AND x.ColumnName = c.name
)
, cteSelectStatements AS (
SELECT
TableName = t.name
,TableSelect = 'SELECT TableName = ''' + t.name + ''', ' +
STUFF(
(SELECT ', ' + c.ColumnName + ' = ' + IIF(c.ColumnExists = 0,'NULL',c.ColumnName)
FROM
cteColumns c
WHERE t.name = c.Tablename
FOR XML PATH(''))
,1,1,'')
+ ' FROM ' + t.name +
IIF((ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY t.name DESC)) > 1,' UNION ALL ','')
FROM
sys.tables t
)
SELECT #SQLStatement = STUFF(
(SELECT ' ' + TableSelect
FROM
cteSelectStatements
ORDER BY
TableName
FOR XML PATH(''))
,1,1,'')
PRINT #SQLStatement
--EXECUTE #SQLStatement

Delete or change the primary key constraint to UNIQUE constraint using SQL Server 2008

I really need to know if there is any way I can change or delete the primary key constraint of a table to UNIQUE constraint
When I try to drop the primary constraint from the Entreprise table:
ALTER TABLE Entreprise
DROP CONSTRAINT PK__Entrepri__AABA1D8F1B0907CE
I get this error :
Msg 3725, Level 16, State 0, Line 1
The constraint 'PK_Entrepri_AABA1D8F1B0907CE' is being referenced by table 'Dossier', foreign key constraint 'Cle_FDOs'.
Msg 3727, Level 16, State 0, Line 1
Could not drop constraint. See previous errors.
So the problem is I don't want to delete the rows in the dossier table
This is the Entreprise table :
create table Entreprise
(
ID_Entreprise integer ,
Raison_Social varchar(100),/*Nom Entreprise*/
Num_Raison_Sociale varchar(20) unique ,
Adress varchar(100),
Abreviation varchar(10),
CNSS_Entreprise integer unique,
Eligible varchar(20),/*AUTOMATIQUE par raport aux CNSS_Entreprise*/
Effectif integer,/*NB SALARIE*/
Ville varchar(20),
Responsable varchar(20),
EMAIL_Responsable varchar(20),
Tel_Responsable varchar(20),
Fax_Responsable varchar(20),
Directeur varchar(20),
EMAIL_Directeur varchar(20),
Tel_Directeur varchar(20),
Fax_Directeur varchar(20),
RIB varchar(60),/*ici non sur le dossier lo*/
Nom_Giac varchar(50) foreign key references GIAC(Nom_Giac),
primary key(Nom_Giac,ID_Entreprise)
)
GO
and this is the Dossier table:
create table Dossier
(
ID_Dossier integer primary key,
ID_Entreprise int,/*AUTOMATIQE par rapotrt aux la cnss de l'entreprise qui l'a donne*/
Date_Depot datetime ,
Type_Etude varchar(2),/*DS IF combobox*/
Dernier_Type varchar(2),/* AUTOMATIQUE */
Eligibile varchar(3),/* par raport aux Dernier Type et CNSS et COTISTAION EXERCICES */
Fiche_Information varchar(3),/*checkbox o/n */
Buletin_Adhesion varchar(3),
Fiche_Renseignment varchar(3),
Attestation varchar(3),
Date_Debut datetime,
Date_Fin datetime,
--Etat_Dossier varchar(3), /* hado m7aydine mn war9a*/
--Motif text,/*en cas de rejet, peu prendre null apart le cnss et cotisation ex et dernier formation *//* hado m7aydine mn war9a*/
ID_Cabinet integer foreign key references Cabinet(ID_Cabinet),
Montant_Demander decimal(6,2),
Duree integer,
Porcentage_Taux varchar(3), /* combobox 70% 80% */
Nom_Giac varchar(50),
constraint Cle_FDOs foreign key(Nom_Giac,ID_Entreprise) references Entreprise(Nom_Giac,ID_Entreprise),
)
GO
You cannot do the "change" automatically, with a single SQL instruction, but you can achieve that if you want to.
First, you need to drop the foreign-keys of those tables containing references to the referenced table, Enterprise, in your concrete case.
You need to drop the foreign-key from Dossier, then drop the primary-key from Enterprise, and create a UNIQUE constraint.
Another question would be, why are you interested on doing that?
Maybe you can read this other SO thread discussing about the matter.
As the error is suggesting, you need to delete the foreign-key reference first. This will not delete the records in Dossier (see my SQL Fiddle example.) :
ALTER TABLE Dossier DROP CONSTRAINT Cle_FDOs;
I ran into this problem several times and ending up making a stored proc that can take any primary key constraint and turn into unique index. It drops and recreates anything referencing the table in question, (which is the tricky part for highly referenced tables).
It can handle multi-part primary keys.
CREATE PROCEDURE spPrimaryKeyToUniqueKey
#schema VARCHAR(255),
#tableName VARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #constraintsToDrop NVARCHAR(MAX) = '';
DECLARE #constraintsToCreate NVARCHAR(MAX) = '';
DECLARE #indexesToDrop NVARCHAR(MAX) = '';
DECLARE #indexesToCreate NVARCHAR(MAX) = '';
;WITH _fks AS (
SELECT
FkName = fk.[name]
, SchemaName = sch.[name]
, TableName = tab1.[name]
, ColumnName = col1.[name]
, ReferencedSchema = sch2.[name]
, ReferencedTableName = tab2.[name]
, ReferencedColumnName = col2.[name]
, ReferencedColOrder = ic.key_ordinal
FROM sys.foreign_key_columns fkc
JOIN sys.foreign_keys fk ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tab1 ON tab1.object_id = fkc.parent_object_id
JOIN sys.schemas sch ON tab1.schema_id = sch.schema_id
JOIN sys.columns col1 ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
JOIN sys.tables tab2 ON tab2.object_id = fkc.referenced_object_id
JOIN sys.schemas sch2 ON sch2.schema_id = tab2.schema_id
JOIN sys.columns col2 ON col2.column_id = fkc.referenced_column_id AND col2.object_id = tab2.object_id
JOIN sys.indexes i ON fk.key_index_id = i.index_id AND i.object_id = tab2.object_id
JOIN sys.index_columns ic ON col2.column_id = ic.column_id AND ic.object_id = tab2.object_id AND i.index_id = ic.index_id
)
, _fksWithColList AS (
SELECT
f.FkName
, f.SchemaName
, f.TableName
, f.ReferencedSchema
, f.ReferencedTableName
, TableColNameList = STUFF(
(SELECT ',[' + ff.ColumnName + ']'
FROM _fks ff
WHERE f.FkName = ff.FkName AND f.SchemaName = ff.SchemaName AND f.TableName = ff.TableName AND f.ReferencedSchema = ff.ReferencedSchema AND f.ReferencedTableName = ff.ReferencedTableName
ORDER BY ff.ReferencedColOrder
FOR XML PATH('')), 1, 1, '')
, ReferencedTableColNameList = STUFF(
(SELECT ',[' + ff.ReferencedColumnName + ']'
FROM _fks ff
WHERE f.FkName = ff.FkName AND f.SchemaName = ff.SchemaName AND f.TableName = ff.TableName AND f.ReferencedSchema = ff.ReferencedSchema AND f.ReferencedTableName = ff.ReferencedTableName
ORDER BY ff.ReferencedColOrder
FOR XML PATH('')), 1, 1, '')
FROM _fks f
GROUP BY f.FkName, f.SchemaName, f.TableName, f.ReferencedSchema, f.ReferencedTableName
)
, _commands AS (
SELECT *,
DropStatement = REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT IF EXISTS [{2}];',
'{0}', SchemaName),
'{1}', TableName),
'{2}', FkName),
CreateStatement = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT IF EXISTS [{2}]; ALTER TABLE [{0}].[{1}] ADD CONSTRAINT [{2}] FOREIGN KEY ({3}) REFERENCES [{4}].[{5}]({6});',
'{0}', SchemaName),
'{1}', TableName),
'{2}', FkName),
'{3}', TableColNameList),
'{4}', ReferencedSchema),
'{5}', ReferencedTableName),
'{6}', ReferencedTableColNameList)
FROM _fksWithColList
WHERE ReferencedSchema = #schema AND ReferencedTableName = #tableName
)
SELECT #constraintsToDrop = #constraintsToDrop + DropStatement + CHAR(13)+CHAR(10), #constraintsToCreate = #constraintsToCreate + c.CreateStatement + CHAR(13)+CHAR(10)
FROM _commands c
;WITH _indexes AS (
SELECT
TableName = t.[name]
, SchemaName = s.[name]
, IndexName = i.[name]
, ColumnName = c.[name]
, ColOrder = ic.key_ordinal
, ColDesc = ic.is_descending_key
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.columns c ON t.object_id = c.object_id
JOIN sys.index_columns ic ON t.object_id = ic.object_id AND c.column_id = ic.column_id AND i.index_id = ic.index_id
WHERE s.[name] = #schema AND t.[name] = #tableName AND i.is_primary_key = 1
)
, indexesWithColList AS (
SELECT
TableName
, SchemaName
, IndexName
, IndexColList = STUFF(
(SELECT ',[' + ii.ColumnName + ']' + IIF(ii.ColDesc = 1, ' DESC', '')
FROM _indexes ii
WHERE i.TableName = ii.TableName AND i.SchemaName = ii.SchemaName AND i.IndexName = ii.IndexName AND ii.ColOrder > 0
ORDER BY ii.ColOrder
FOR XML PATH('')), 1, 1, '')
, IndexColNameList = STUFF(
(SELECT '_' + ii.ColumnName
FROM _indexes ii
WHERE i.TableName = ii.TableName AND i.SchemaName = ii.SchemaName AND i.IndexName = ii.IndexName AND ii.ColOrder > 0
ORDER BY ii.ColOrder
FOR XML PATH('')), 1, 1, '')
FROM _indexes i
GROUP BY i.TableName, i.SchemaName, i.IndexName
)
, _commands AS (
SELECT *,
DropStatement = REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT {2};',
'{0}', i.SchemaName),
'{1}', i.TableName),
'{2}', i.IndexName),
CreateStatement = REPLACE(REPLACE(REPLACE(REPLACE('CREATE UNIQUE NONCLUSTERED INDEX [{0}] ON [{1}].[{2}] ({3});',
'{0}', 'UX_' + i.SchemaName + '_' + i.TableName + '_' + i.IndexColNameList),
'{1}', i.SchemaName),
'{2}', i.TableName),
'{3}', i.IndexColList)
FROM indexesWithColList i
)
SELECT #indexesToDrop = #indexesToDrop + c.DropStatement + CHAR(13)+CHAR(10), #indexesToCreate = #indexesToCreate + c.CreateStatement + CHAR(13)+CHAR(10)
FROM _commands c
DECLARE #sql NVARCHAR(MAX);
SET #sql = REPLACE(REPLACE(REPLACE(REPLACE('
SET XACT_ABORT ON;
BEGIN TRAN t1
--Drop foreign keys
{0}
--Drop indexes
{1}
--Create indexes
{2}
--Recreate foreign keys
{3}
COMMIT TRAN t1
',
'{0}', #constraintsToDrop),
'{1}', #indexesToDrop),
'{2}', #indexesToCreate),
'{3}', #constraintsToCreate);
PRINT #sql;
EXEC sp_executesql #sql
END
GO

SQLServer: How to sort table names ordered by their foreign key dependency

The following SQL separates tables according to their relationship. The problem is with the tables that sort under the 3000 series. Tables that are part of foreign keys and that use foreign keys. Anyone got some clever recursive CTE preferably or a stored procedure to do the necessary sorting?? Programs connectiong to the database are not considered a solution.
Edit: I posted the answer in the "answers" based on the first solution
Free "right answer" to be had for anyone reposting my own "right" answer!
WITH
AllTables(TableName) AS
(
SELECT OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id)
FROM dbo.sysobjects so
INNER JOIN sys.all_columns ac ON
so.ID = ac.object_id
WHERE
so.type = 'U'
AND
ac.is_rowguidcol = 1
),
Relationships(ReferenceTableName, ReferenceColumnName, TableName, ColumnName) AS
(
SELECT
OBJECT_SCHEMA_NAME (fkey.referenced_object_id) + '.' +
OBJECT_NAME (fkey.referenced_object_id) AS ReferenceTableName
,COL_NAME(fcol.referenced_object_id,
fcol.referenced_column_id) AS ReferenceColumnName
,OBJECT_SCHEMA_NAME (fkey.parent_object_id) + '.' +
OBJECT_NAME(fkey.parent_object_id) AS TableName
,COL_NAME(fcol.parent_object_id, fcol.parent_column_id) AS ColumnName
FROM sys.foreign_keys AS fkey
INNER JOIN sys.foreign_key_columns AS fcol ON
fkey.OBJECT_ID = fcol.constraint_object_id
),
NotReferencedOrReferencing(TableName) AS
(
SELECT TableName FROM AllTables
EXCEPT
SELECT TableName FROM Relationships
EXCEPT
SELECT ReferenceTableName FROM Relationships
),
OnlyReferenced(Tablename) AS
(
SELECT ReferenceTableName FROM Relationships
EXCEPT
SELECT TableName FROM Relationships
),
-- These need to be sorted based on theire internal relationships
ReferencedAndReferencing(TableName, ReferenceTableName) AS
(
SELECT r1.Tablename, r2.ReferenceTableName FROM Relationships r1
INNER JOIN Relationships r2
ON r1.TableName = r2.ReferenceTableName
),
OnlyReferencing(TableName) AS
(
SELECT Tablename FROM Relationships
EXCEPT
SELECT ReferenceTablename FROM Relationships
)
SELECT TableName, 1000 AS Sorting FROM NotReferencedOrReferencing
UNION
SELECT TableName, 2000 AS Sorting FROM OnlyReferenced
UNION
SELECT TableName, 3000 AS Sorting FROM ReferencedAndReferencing
UNION
SELECT TableName, 4000 AS Sorting FROM OnlyReferencing
ORDER BY Sorting
My rendition with moderate tweaks: This one is SQL-2005+ and works on databases without the "rowguidcol":
WITH TablesCTE(SchemaName, TableName, TableID, Ordinal) AS
(
SELECT
OBJECT_SCHEMA_NAME(so.object_id) AS SchemaName,
OBJECT_NAME(so.object_id) AS TableName,
so.object_id AS TableID,
0 AS Ordinal
FROM
sys.objects AS so
WHERE
so.type = 'U'
AND so.is_ms_Shipped = 0
UNION ALL
SELECT
OBJECT_SCHEMA_NAME(so.object_id) AS SchemaName,
OBJECT_NAME(so.object_id) AS TableName,
so.object_id AS TableID,
tt.Ordinal + 1 AS Ordinal
FROM
sys.objects AS so
INNER JOIN sys.foreign_keys AS f
ON f.parent_object_id = so.object_id
AND f.parent_object_id != f.referenced_object_id
INNER JOIN TablesCTE AS tt
ON f.referenced_object_id = tt.TableID
WHERE
so.type = 'U'
AND so.is_ms_Shipped = 0
)
SELECT DISTINCT
t.Ordinal,
t.SchemaName,
t.TableName,
t.TableID
FROM
TablesCTE AS t
INNER JOIN
(
SELECT
itt.SchemaName as SchemaName,
itt.TableName as TableName,
itt.TableID as TableID,
Max(itt.Ordinal) as Ordinal
FROM
TablesCTE AS itt
GROUP BY
itt.SchemaName,
itt.TableName,
itt.TableID
) AS tt
ON t.TableID = tt.TableID
AND t.Ordinal = tt.Ordinal
ORDER BY
t.Ordinal,
t.TableName
Thank you for a working solution NXC. You put me on the right track to solve the problem using a recursive CTE.
WITH
TablesCTE(TableName, TableID, Ordinal) AS
(
SELECT
OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id) AS TableName,
so.id AS TableID,
0 AS Ordinal
FROM dbo.sysobjects so INNER JOIN sys.all_columns ac ON so.ID = ac.object_id
WHERE
so.type = 'U'
AND
ac.is_rowguidcol = 1
UNION ALL
SELECT
OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id) AS TableName,
so.id AS TableID,
tt.Ordinal + 1 AS Ordinal
FROM
dbo.sysobjects so
INNER JOIN sys.all_columns ac ON so.ID = ac.object_id
INNER JOIN sys.foreign_keys f
ON (f.parent_object_id = so.id AND f.parent_object_id != f.referenced_object_id)
INNER JOIN TablesCTE tt ON f.referenced_object_id = tt.TableID
WHERE
so.type = 'U'
AND
ac.is_rowguidcol = 1
)
SELECT DISTINCT
t.Ordinal,
t.TableName
FROM TablesCTE t
INNER JOIN
(
SELECT
TableName as TableName,
Max (Ordinal) as Ordinal
FROM TablesCTE
GROUP BY TableName
) tt ON (t.TableName = tt.TableName AND t.Ordinal = tt.Ordinal)
ORDER BY t.Ordinal, t.TableName
For thoose wondering what this is useable for: I will use it to safely empty a database without violating any foreign key relations. (By truncating in descending order)
I will also be able to safely fill the tables with data from another database by filling the tables in ascending order.
You can use an iterative algorithm, which is probably less convoluted than a CTE. Here's an example that sorts according to depth:
declare #level int -- Current depth
,#count int
-- Step 1: Start with tables that have no FK dependencies
--
if object_id ('tempdb..#Tables') is not null
drop table #Tables
select s.name + '.' + t.name as TableName
,t.object_id as TableID
,0 as Ordinal
into #Tables
from sys.tables t
join sys.schemas s
on t.schema_id = s.schema_id
where not exists
(select 1
from sys.foreign_keys f
where f.parent_object_id = t.object_id)
set #count = ##rowcount
set #level = 0
-- Step 2: For a given depth this finds tables joined to
-- tables at this given depth. A table can live at multiple
-- depths if it has more than one join path into it, so we
-- filter these out in step 3 at the end.
--
while #count > 0 begin
insert #Tables (
TableName
,TableID
,Ordinal
)
select s.name + '.' + t.name as TableName
,t.object_id as TableID
,#level + 1 as Ordinal
from sys.tables t
join sys.schemas s
on s.schema_id = t.schema_id
where exists
(select 1
from sys.foreign_keys f
join #Tables tt
on f.referenced_object_id = tt.TableID
and tt.Ordinal = #level
and f.parent_object_id = t.object_id
and f.parent_object_id != f.referenced_object_id)
-- The last line ignores self-joins. You'll
-- need to deal with these separately
set #count = ##rowcount
set #level = #level + 1
end
-- Step 3: This filters out the maximum depth an object occurs at
-- and displays the deepest first.
--
select t.Ordinal
,t.TableID
,t.TableName
from #Tables t
join (select TableName as TableName
,Max (Ordinal) as Ordinal
from #Tables
group by TableName) tt
on t.TableName = tt.TableName
and t.Ordinal = tt.Ordinal
order by t.Ordinal desc
This causes problems with self-referencing tables. You'll need to manually exclude any foreign keys that point to self referencing tables.
INNER JOIN sys.foreign_keys f
ON (f.parent_object_id = so.id AND f.parent_object_id != f.referenced_object_id)
/* Manually exclude self-referencing tables - they cause recursion problems*/
and f.object_id not in /*Below are IDs of foreign keys*/
( 1847729685,
1863729742,
1879729799
)
INNER JOIN TablesCTE tt