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.
Related
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.
I've been tasked with moving all tables in a single snapshot replication that have primary keys to the transaction replication. We get vendor updates and they may have added keys to tables that were in the Snapshot replication.
I've tried to break it down into 2 steps, finding all tables in a snapshot replication, and then checking to see if those tables have a primary key.
I've tried to piece together a few different code samples, but I may need to start over, here's what I've got so far.
--=============================================================================================
SELECT DB_NAME () PublisherDB
, sp.name AS PublisherName
, sa.name AS TableName
, UPPER (srv.srvname) AS SubscriberServerName
,*
FROM dbo.syspublications sp
JOIN dbo.sysarticles sa ON sp.pubid = sa.pubid
JOIN dbo.syssubscriptions s ON sa.artid = s.artid
JOIN master.dbo.sysservers srv ON s.srvid = srv.srvid;
--=============================================================================================
SELECT DB_NAME () AS db
, SCHEMA_NAME (o.schema_id) AS [Schema]
, so.name AS table_name
, so.type
, CASE WHEN TABLE_NAME IN (
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY'
) THEN 1 ELSE 0 END AS HasPrimaryKey
--INTO #t2
FROM sys.objects o WITH (NOLOCK)
INNER JOIN sysobjects so WITH (NOLOCK)
--INNER JOIN #t1 ON t1.
LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS t2 ON t2.TABLE_NAME = so.name ON so.id = o.object_id
WHERE (
(so.xtype = 'U') -- user table xtype: learn.microsoft.com/en-us/sql/relational-databases/system-compatibility-views/sys-sysobjects-transact-sql?view=sql-server-ver15
OR (so.xtype = 'V') -- view
OR (so.xtype = 'P') -- stored procedure
)
AND so.category <> 2
AND so.name IN (
SELECT DISTINCT OBJECT_NAME (objid) FROM dbo.sysarticles
)
ORDER BY so.name
, so.type;
--=============================================================================================
DECLARE #jobId UNIQUEIDENTIFIER;
DECLARE #jobName sysname;
SELECT #jobId = jobs.job_id
, #jobName = jobs.name
FROM msdb.dbo.sysjobs jobs (NOLOCK)
JOIN msdb.dbo.syscategories categories (NOLOCK) ON jobs.category_id = categories.category_id
WHERE categories.name = 'REPL-Snapshot'
AND jobs.name LIKE '%db-name%';
SELECT #jobId
, #jobName;
EXEC sp_start_job #job_id = #jobId;
This is what I ended up going with. I pieced together and tweaked various snippets of code I've found.
Some from here: https://dataedo.com/kb/query/sql-server/list-tables-with-their-primary-keys
Other code from here: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/50c6890b-8dc1-46c6-aeda-d97149a9692f/list-all-replicated-tables-and-their-destination?forum=sqlreplication
--====================================================================================================================================================
-- Get tables in Snapshot replication for the selected DB.
--====================================================================================================================================================
IF OBJECT_ID ('tempdb..#t1') IS NOT NULL DROP TABLE #t1;
SELECT pub.name AS [Publication]
, CASE WHEN pub.name LIKE '%Snapshot%' THEN 'SnapShot'
WHEN pub.name LIKE '%Transaction%' THEN 'Transaction' ELSE NULL END AS ReplicationType
, art.name AS [Article]
, serv.name AS [Subsriber]
, sub.dest_db AS [DestinationDB]
, obj.object_id
, CASE WHEN obj.type = 'U' THEN 'Table'
WHEN obj.type = 'V' THEN 'View'
WHEN obj.type = 'P' THEN 'SP' ELSE NULL END AS ObjectType
INTO #t1
FROM dbo.syssubscriptions sub
INNER JOIN sys.servers serv ON serv.server_id = sub.srvid
INNER JOIN dbo.sysarticles art ON art.artid = sub.artid
INNER JOIN dbo.syspublications pub ON pub.pubid = art.pubid
INNER JOIN sys.objects obj ON obj.object_id = art.objid
WHERE CASE WHEN pub.name LIKE '%Snapshot%' THEN 'SnapShot'
WHEN pub.name LIKE '%Transaction%' THEN 'Transaction' ELSE NULL END = 'Snapshot';
--====================================================================================================================================================
-- Check for primary keys on the above tables
--====================================================================================================================================================
SELECT SCHEMA_NAME (tab.schema_id) AS [schema_name]
, tab.[name] AS table_name
, pk.[name] AS pk_name
, SUBSTRING (column_names, 1, LEN (column_names) - 1) AS [columns]
FROM sys.tables tab
LEFT OUTER JOIN sys.indexes pk ON tab.object_id = pk.object_id
AND pk.is_primary_key = 1
CROSS APPLY (
SELECT col.[name] + ', '
FROM sys.index_columns ic
INNER JOIN sys.columns col ON ic.object_id = col.object_id
AND ic.column_id = col.column_id
WHERE ic.object_id = tab.object_id
AND ic.index_id = pk.index_id
ORDER BY col.column_id
FOR XML PATH ('')
) D(column_names)
WHERE pk.object_id IN (
SELECT object_id FROM #t1
)
ORDER BY SCHEMA_NAME (tab.schema_id)
, tab.[name];
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
Is there any way of matching column names to the column names which the indexes have.
So for example lets say index1 uses columns name,age,address and index2 uses (in the same table) name,age.(All for one specific table)
If i pass name,age and the table name to a stored proc, what query should i be writing which will return me index2 and not index1.
I have come across many examples of how to list columns with index and table names like:
How can we check that table have index or not?
But still had problem writing the sql query for my usage.
Any help would be much appreciated,
Thank you
(This is for Microsoft sql server 2008 - 2012)
This is what i have written , it did not work properly and hence the question
select index1.name,sys.tables.name, Stuff((SELECT ',' + sys.columns.name AS [text()]
FROM
(
select sys.columns.name
from sys.columns
inner join sys.index_columns On sys.index_columns.index_column_id=sys.columns.column_id
inner join sys.indexes on sys.indexes.index_id=sys.index_columns.index_id
where sys.indexes.index_id=index1.index_id
) x
For XML PATH (''), type ).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') As coLName
from sys.indexes as index1
inner join sys.tables on sys.tables.object_id=index1.object_id
inner join sys.index_columns On index1.index_id=sys.index_columns.index_id AND sys.index_columns.object_id = sys.tables.object_id
inner join sys.columns on sys.columns.column_id=sys.index_columns.column_id And sys.columns.object_id=sys.tables.object_id
where sys.tables.name=TABLE_NAME
You can try this query with CTE
DECLARE #searchIndex nvarchar(100) = 'name,age',
#tableName nvarchar(100) = 'your_tableName'
;WITH cte AS
(
SELECT i.name AS index_name, c.name
FROM sys.indexes i LEFT JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
LEFT JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE i.object_id = OBJECT_ID(#tableName) AND ic.is_included_column != 1
), cte2 AS
(
SELECT c2.index_name,
STUFF((SELECT ',' + c.name
FROM cte c
WHERE c.index_name = c2.index_name
FOR XML PATH, TYPE).value('.[1]', 'nvarchar(max)'), 1, 1, '') [columns]
FROM cte c2
GROUP BY c2.index_name
)
SELECT *
FROM cte2
WHERE [columns] = #searchIndex
In second scenario no matter order specify columns and space
DECLARE #searchIndex nvarchar(100) = ' age, name' ,
#tableName nvarchar (100) = 'your_tableName'
;WITH ParsSearchIndex AS
(
SELECT SUBSTRING(#searchIndex , 0 , CHARINDEX ( ',' , #searchIndex )) AS val ,
CAST(STUFF (#searchIndex + ',' , 1, CHARINDEX( ',', #searchIndex), '') AS nvarchar (100 )) AS stval
UNION ALL
SELECT LTRIM(SUBSTRING (stval , 0, CHARINDEX( ',', stval))),
CAST(STUFF (stval , 1, CHARINDEX( ',' , stval ), '' ) AS nvarchar(100 ))
FROM ParsSearchIndex
WHERE stval != ''
), max_ParsSearchIndex AS
(
SELECT val, COUNT(*) OVER() AS cnt
FROM ParsSearchIndex
WHERE val != ''
), cte AS
(
SELECT i.name AS index_name, c.name , ic .is_included_column,
MAX(ic .index_column_id) OVER( PARTITION BY i. index_id) AS maxIndex_column_id
FROM sys.indexes i LEFT JOIN sys. index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
LEFT JOIN sys. columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE i.object_id = OBJECT_ID(#tableName )
), cte2 AS
(
SELECT c2.index_name , COUNT (*) AS cnt, c2. maxIndex_column_id,
STUFF((SELECT ',' + c .name
FROM cte c
WHERE c .index_name = c2 .index_name
FOR XML PATH , TYPE ).value( '.[1]', 'nvarchar(max)'), 1 , 1, '') [columns]
FROM cte c2
WHERE c2.is_included_column != 1 AND EXISTS (
SELECT 1
FROM max_ParsSearchIndex p
WHERE c2 .name = p .val AND p .cnt = c2.maxIndex_column_id
)
GROUP BY c2 .index_name, c2. maxIndex_column_id
)
SELECT index_name, [columns]
FROM cte2
WHERE cnt = maxIndex_column_id
This worked for me.
DECLARE #searchIndex nvarchar(100) = 'age,name',
#schemeName nvarchar(100) = 'dbo',
#tableName nvarchar(100) = 'tableName'
SELECT
name
FROM (SELECT
indexes.name,
(SELECT
c1.name + ','
FROM sys.schemas s1
INNER JOIN sys.tables t1
ON s1.schema_id = t1.schema_id
AND tables.object_id = t1.object_id
INNER JOIN sys.columns c1
ON t1.object_id = c1.object_id
INNER JOIN sys.indexes i1
ON t1.object_id = i1.object_id
AND indexes.index_id = i1.index_id
INNER JOIN sys.index_columns ic1
ON t1.object_id = ic1.object_id
AND i1.index_id = ic1.index_id
AND c1.column_id = ic1.column_id
WHERE schemas.schema_id = s1.schema_id
ORDER BY c1.name
FOR xml PATH (''))
AS colName
FROM sys.schemas
INNER JOIN sys.tables
ON schemas.schema_id = tables.schema_id
AND tables.name = #tableName
INNER JOIN sys.indexes
ON tables.object_id = indexes.object_id
WHERE schemas.name = #schemeName) A
WHERE A.colName = #searchIndex + ','
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