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?
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;
Since you need it for one-off purposes, but you don't want it always present, I think your best bet it to write a stored proc.
Just delete from the table furthest away first, and work your way back up the tree
Here's an example:
Create Proc CascaseDeleteMyTable
#MyTableId Int
As
Delete From ChildTable33 Where ChildParent33Id In (Select ChildParent33Id From ChildParent33 Where MyTableId = #MyTableId)
Delete From ChildTable2 Where MyTableId = #MyTableId
GO
Related
I've to count how many times a key is being used across tables. I have the following code that gets all the tables that reference the key:
SELECT s.SCHEMA_NAME,
OBJECT_NAME(f.parent_object_id) AS TableName,
COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName
FROM sys.foreign_keys AS f
INNER JOIN sys.foreign_key_columns AS fc
ON f.OBJECT_ID = fc.constraint_object_id
OUTER APPLY
(
SELECT i.SCHEMA_NAME
FROM INFORMATION_SCHEMA.SCHEMATA i
INNER JOIN SYS.TABLES s
ON i.SCHEMA_NAME = SCHEMA_NAME(s.SCHEMA_ID)
WHERE f.parent_object_id = s.object_id
) AS s
WHERE OBJECT_NAME (f.referenced_object_id) = 'Languages'
Result:
SchemaName, TableName, ColumnName
And this is the query I have to do for each returned row, and finally SUM all of them:
SELECT COUNT(*)
FROM SchemaName.TableName t
WHERE t.ColumnName = #LanguageId
I've been reading about CURSORs and dynamic SQL but I couldn't manage a way to make it work (never used any of them).
The use of cursors / dynamic SQL is not a requirement. If there are simpler ways I appreciate.
EDIT: I managed to make it work.
EDIT2: Some refactoring and full implementation of the actual requirement.
DECLARE #WantedDefaultLanguageId INT = 1;
--Internal Variables
DECLARE #DefaultLanguageId INT = (SELECT Id FROM i18n.Languages WHERE IsDefault = 1)
, #SqlCommand NVARCHAR(1000)
, #SchemaName SYSNAME
, #TableName SYSNAME
, #FieldName SYSNAME
, #CurrentValue INT
, #DefaultTotal INT = 0
, #WantedTotal INT = 0;
DECLARE relationships CURSOR
LOCAL FAST_FORWARD READ_ONLY
FOR SELECT schemaNames.SCHEMA_NAME,
OBJECT_NAME(foreignKeys.parent_object_id) AS TableName,
COL_NAME(foreignKeysColumns.parent_object_id, foreignKeysColumns.parent_column_id) AS ColumnName
FROM sys.foreign_keys AS foreignKeys
INNER JOIN sys.foreign_key_columns AS foreignKeysColumns
ON foreignKeys.OBJECT_ID = foreignKeysColumns.constraint_object_id
OUTER APPLY
(
SELECT metadata.SCHEMA_NAME
FROM INFORMATION_SCHEMA.SCHEMATA metadata
INNER JOIN SYS.TABLES AS sysTables
ON metadata.SCHEMA_NAME = SCHEMA_NAME(sysTables.SCHEMA_ID)
WHERE foreignKeys.parent_object_id = sysTables.object_id
) AS schemaNames
WHERE OBJECT_NAME (foreignKeys.referenced_object_id) = 'Languages';
IF #DefaultLanguageId = #WantedDefaultLanguageId
SELECT 1;
ELSE
BEGIN
OPEN relationships
FETCH NEXT FROM relationships
INTO #SchemaName, #TableName, #FieldName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SqlCommand = '
SELECT #CurrentValue = COUNT(*)
FROM ' + #SchemaName + '.' + #TableName + ' tableName
WHERE tableName.' + #FieldName + ' = ' + CAST(#DefaultLanguageId AS nvarchar(1000))
EXEC sp_executesql #SqlCommand, N'#CurrentValue INT OUTPUT', #CurrentValue OUTPUT
SET #DefaultTotal += #CurrentValue
--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
SET #SqlCommand = '
SELECT #CurrentValue = COUNT(*)
FROM ' + #SchemaName + '.' + #TableName + ' tableName
WHERE tableName.' + #FieldName + ' = ' + CAST(#WantedDefaultLanguageId AS nvarchar(1000))
EXEC sp_executesql #SqlCommand, N'#CurrentValue INT OUTPUT', #CurrentValue OUTPUT
SET #WantedTotal += #CurrentValue
--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
FETCH NEXT FROM relationships
INTO #SchemaName, #TableName, #FieldName;
END
CLOSE relationships
DEALLOCATE relationships
SELECT CASE
WHEN #WantedTotal = #DefaultTotal THEN
1
ELSE 0
END
END;
It's taking a very long time to run. This query won't be ran very often but any help to improve it / better ways to implement this functionality is appreciated.
(I'll close the question later on).
Edit3: Ok so here is a example of what I need:
Table: Language
Id Language
1 English
RelationalTable1
... LanguageId
1
1
2
AllOtherRelationalTables
...
I need to do a COUNT of how many times the LanguageId = 1 (for all tables that reference the Language Table). The code is working, but would like to know if there is a simpler way of implementing this / improving it.
I'm not exactly sure what precisely you're asking for, but this MAY help you...
This SQL will get you all the FK column information:
SELECT fkeys.[name] AS FKName,
OBJECT_NAME(fkeys.parent_object_id) AS TableName,
(SELECT STUFF((SELECT ',' + c.[name]
FROM sys.foreign_keys fk INNER JOIN sys.tables t ON fk.parent_object_id = t.object_id
INNER JOIN sys.columns as c ON t.object_id = c.object_id
INNER JOIN sys.foreign_key_columns AS fc ON c.column_id = fc.parent_column_id
AND fc.constraint_object_id = fk.object_id
AND fc.parent_object_id = fk.parent_object_id
WHERE fk.[name] = fkeys.[name]
FOR XML PATH ('')), 1, 1, '')) AS FKFolumns,
OBJECT_NAME(fkeys.referenced_object_id) AS ReferencedTableName,
(SELECT STUFF((SELECT ',' + c.[name]
FROM sys.foreign_keys fk INNER JOIN sys.tables t ON fk.referenced_object_id = t.object_id
INNER JOIN sys.columns as c ON t.object_id = c.object_id
INNER JOIN sys.foreign_key_columns AS fc ON c.column_id = fc.referenced_column_id
AND fc.constraint_object_id = fk.object_id
AND fc.referenced_object_id = fk.referenced_object_id
WHERE fk.[name] = fkeys.[name]
FOR XML PATH ('')), 1, 1, '')) AS ReferencedFKColumns
FROM sys.foreign_keys fkeys
You can use this to extract the count of things that reference a given table and column:
WITH AllFKInfo AS (
SELECT fkeys.[name] AS FKName,
OBJECT_NAME(fkeys.parent_object_id) AS TableName,
(SELECT STUFF((SELECT ',' + c.[name]
FROM sys.foreign_keys fk INNER JOIN sys.tables t ON fk.parent_object_id = t.object_id
INNER JOIN sys.columns as c ON t.object_id = c.object_id
INNER JOIN sys.foreign_key_columns AS fc ON c.column_id = fc.parent_column_id
AND fc.constraint_object_id = fk.object_id
AND fc.parent_object_id = fk.parent_object_id
WHERE fk.[name] = fkeys.[name]
FOR XML PATH ('')), 1, 1, '')) AS FKFolumns,
OBJECT_NAME(fkeys.referenced_object_id) AS ReferencedTableName,
(SELECT STUFF((SELECT ',' + c.[name]
FROM sys.foreign_keys fk INNER JOIN sys.tables t ON fk.referenced_object_id = t.object_id
INNER JOIN sys.columns as c ON t.object_id = c.object_id
INNER JOIN sys.foreign_key_columns AS fc ON c.column_id = fc.referenced_column_id
AND fc.constraint_object_id = fk.object_id
AND fc.referenced_object_id = fk.referenced_object_id
WHERE fk.[name] = fkeys.[name]
FOR XML PATH ('')), 1, 1, '')) AS ReferencedFKColumns
FROM sys.foreign_keys fkeys
)
SELECT ReferencedTableName, ReferencedFKColumns, COUNT(ReferencedFKColumns) AS CountOfReferences
FROM AllFKInfo
GROUP BY ReferencedTableName, ReferencedFKColumns
ORDER BY ReferencedTableName, ReferencedFKColumns
The answer in the post works, probably not the best solution but does the job.
DECLARE #WantedDefaultLanguageId INT = 1;
--Internal Variables
DECLARE #DefaultLanguageId INT = (SELECT Id FROM i18n.Languages WHERE IsDefault = 1)
, #SqlCommand NVARCHAR(1000)
, #SchemaName SYSNAME
, #TableName SYSNAME
, #FieldName SYSNAME
, #CurrentValue INT
, #DefaultTotal INT = 0
, #WantedTotal INT = 0;
DECLARE relationships CURSOR
LOCAL FAST_FORWARD READ_ONLY
FOR SELECT schemaNames.SCHEMA_NAME,
OBJECT_NAME(foreignKeys.parent_object_id) AS TableName,
COL_NAME(foreignKeysColumns.parent_object_id, foreignKeysColumns.parent_column_id) AS ColumnName
FROM sys.foreign_keys AS foreignKeys
INNER JOIN sys.foreign_key_columns AS foreignKeysColumns
ON foreignKeys.OBJECT_ID = foreignKeysColumns.constraint_object_id
OUTER APPLY
(
SELECT metadata.SCHEMA_NAME
FROM INFORMATION_SCHEMA.SCHEMATA metadata
INNER JOIN SYS.TABLES AS sysTables
ON metadata.SCHEMA_NAME = SCHEMA_NAME(sysTables.SCHEMA_ID)
WHERE foreignKeys.parent_object_id = sysTables.object_id
) AS schemaNames
WHERE OBJECT_NAME (foreignKeys.referenced_object_id) = 'Languages';
IF #DefaultLanguageId = #WantedDefaultLanguageId
SELECT 1;
ELSE
BEGIN
OPEN relationships
FETCH NEXT FROM relationships
INTO #SchemaName, #TableName, #FieldName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SqlCommand = '
SELECT #CurrentValue = COUNT(*)
FROM ' + #SchemaName + '.' + #TableName + ' tableName
WHERE tableName.' + #FieldName + ' = ' + CAST(#DefaultLanguageId AS nvarchar(1000))
EXEC sp_executesql #SqlCommand, N'#CurrentValue INT OUTPUT', #CurrentValue OUTPUT
SET #DefaultTotal += #CurrentValue
--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
SET #SqlCommand = '
SELECT #CurrentValue = COUNT(*)
FROM ' + #SchemaName + '.' + #TableName + ' tableName
WHERE tableName.' + #FieldName + ' = ' + CAST(#WantedDefaultLanguageId AS nvarchar(1000))
EXEC sp_executesql #SqlCommand, N'#CurrentValue INT OUTPUT', #CurrentValue OUTPUT
SET #WantedTotal += #CurrentValue
--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
FETCH NEXT FROM relationships
INTO #SchemaName, #TableName, #FieldName;
END
CLOSE relationships
DEALLOCATE relationships
SELECT CASE
WHEN #WantedTotal = #DefaultTotal THEN
1
ELSE 0
END
END;
I'm trying to retrieve all primary keys iin a fact table and then count the number of records in that fact table grouped by that id, however so far i can only seem to get all the primary keys and its talbe. i guess i need to make some kind of subquery
SELECT
tab1.name AS [FactTable],
col1.name AS [PrimaryKey]
FROM sys.indexes ind1
INNER JOIN sys.tables tab1
ON tab1.object_id = ind1.object_id
INNER JOIN sys.schemas sch1
ON tab1.schema_id = sch1.schema_id
INNER JOIN sys.columns col1
ON col1.object_id = tab1.object_id AND col1.name like '%Id'
WHERE tab1.name like 'Fact%' AND ind1.is_primary_key = 1
sample output
primaryKey countRecordsGroupedByPrimaryKey
2 4000
3 8343
4 203
1 4023
I going out on a limb and guessing that you want to count the number of each dimensionPK used in your fact table. If a fact table has references to two different dimensions, you need two different statements to count the usage by that dimension.
For the query below, provide your fact table name and schema and it should generate a count statement joining your fact and dim and grouping by the join keys.
- If a fact table has two different FK relationships, you'll get two different statements.
- If a table uses a composite PK, both key columns will be included in the join
This is complicated and I don't have any tables with multiple FKs to test it on, so please let me know if it does what you want.
DECLARE #NameOfTableWithFKs sysname = 'your fact table name',
#SchemaOfTableWithFKs sysname = 'dbo';
WITH JoinColumns
AS (SELECT QUOTENAME(OBJECT_SCHEMA_NAME(parent.object_id)) + '.' + QUOTENAME(OBJECT_NAME(parent.object_id)) AS ParentTableName,
QUOTENAME(OBJECT_SCHEMA_NAME(referenced.object_id)) + '.' + QUOTENAME(OBJECT_NAME(referenced.object_id)) AS ReferencedTableName,
QUOTENAME(OBJECT_NAME(parent.object_id)) + '.' + QUOTENAME(parent.name) + ' = ' + QUOTENAME(OBJECT_NAME(referenced.object_id)) + '.'
+ QUOTENAME(referenced.name) AS JoinColumn,
QUOTENAME(OBJECT_NAME(referenced.object_id)) + '.' + QUOTENAME(referenced.name) AS GroupingColumn
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.columns AS parent
ON parent.object_id = fkc.parent_object_id
AND parent.column_id = fkc.parent_column_id
INNER JOIN sys.columns AS referenced
ON referenced.object_id = fkc.referenced_object_id
AND referenced.column_id = fkc.referenced_column_id
WHERE OBJECT_NAME(parent.object_id) = #NameOfTableWithFKs
AND OBJECT_SCHEMA_NAME(parent.object_id) = #SchemaOfTableWithFKs
),
JoinTables
AS (SELECT QUOTENAME(OBJECT_SCHEMA_NAME(tbl.object_id)) + '.' + QUOTENAME(OBJECT_NAME(tbl.object_id)) AS ParentTableName,
QUOTENAME(OBJECT_SCHEMA_NAME(rtbl.object_id)) + '.' + QUOTENAME(OBJECT_NAME(rtbl.object_id)) AS ReferencedTableName
FROM sys.tables AS tbl
INNER JOIN sys.foreign_keys AS cstr
ON cstr.parent_object_id = tbl.object_id
INNER JOIN sys.tables AS rtbl
ON rtbl.object_id = cstr.referenced_object_id
WHERE OBJECT_NAME(tbl.object_id) = #NameOfTableWithFKs
AND OBJECT_SCHEMA_NAME(tbl.object_id) = #SchemaOfTableWithFKs
)
SELECT 'SELECT Count(*)' + ( SELECT ', ' + JC.GroupingColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = jt.ParentTableName
AND JC.ReferencedTableName = jt.ReferencedTableName
FOR XML PATH('')
) + ' FROM ' + JT.ParentTableName + ' INNER JOIN ' + JT.ReferencedTableName + ' ON'
+ SUBSTRING(( SELECT ' AND ' + JC.JoinColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = JT.ParentTableName
AND JC.ReferencedTableName = JT.ReferencedTableName
FOR XML PATH('')
), 5, 8000
) + ' GROUP BY ' + SUBSTRING(( SELECT ', ' + JC.GroupingColumn
FROM JoinColumns AS JC
WHERE JC.ParentTableName = JT.ParentTableName
AND JC.ReferencedTableName = JT.ReferencedTableName
FOR XML PATH('')
), 2, 8000
)
FROM JoinTables AS JT;
If I understand the question correctly you are wanting to count all the rows in some tables based on some criteria. Not really sure why you care about the primary key portion since by definition a primary key must be unique so this could still be simplified to not check for primary key but whatever.
I did remove the join to sys.columns because why does it matter the name of the column unless you want only those table named Fact% and has a column named %Id.
This should get you pretty close as I understand it.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select TableName = ''' + tab1.name + ''', NumRows = count(*) from ' + QUOTENAME(sch1.name) + '.' + QUOTENAME(tab1.name) + ' UNION ALL '
FROM sys.indexes ind1
INNER JOIN sys.tables tab1
ON tab1.object_id = ind1.object_id
INNER JOIN sys.schemas sch1
ON tab1.schema_id = sch1.schema_id
--INNER JOIN sys.columns col1
-- ON col1.object_id = tab1.object_id AND col1.name like '%Id'
WHERE tab1.name like 'Fact%'
AND ind1.is_primary_key = 1
select #SQL = LEFT(#SQL, LEN(#SQL) - 10) + ' ORDER BY TableName'
select #SQL --uncomment the exec line below once you are comfortable that the dynamic sql is what you want.
--exec sp_executesql #SQL
I try to guess missing foreign keys using the following SQL code
DECLARE #ColumnList AS TABLE
(
TableName varchar(255) NOT NULL,
ColumnName varchar(255) NOT NULL,
PKTableName varchar(255),
PKColumnName varchar(255),
HasForeignKey bit NOT NULL
)
-- Find all column names that occur more than once.
-- Exclude archive and staging tables.
INSERT INTO #ColumnList
(TableName,
ColumnName,
PKTableName,
PKColumnName,
HasForeignKey)
SELECT t.NAME AS TableName,
c.NAME AS ColumnName,
NULL AS PKTableName,
NULL AS PKColumnName,
CASE
WHEN f1.parent_object_id IS NOT NULL THEN 1
WHEN f2.referenced_object_id IS NOT NULL THEN 1
ELSE 0
END AS HasForeignKey
FROM sys.tables AS t
JOIN sys.columns AS c
ON c.object_id = t.object_id
JOIN sys.types AS y
ON c.system_type_id = y.system_type_id
LEFT JOIN sys.foreign_key_columns AS f1
ON f1.parent_object_id = t.object_id AND f1.parent_column_id = c.column_id
LEFT JOIN sys.foreign_key_columns AS f2
ON f2.referenced_object_id = t.object_id AND f2.referenced_column_id = c.column_id
WHERE t.is_ms_shipped = 0 AND y.NAME IN ('bigint', 'int', 'smallint', 'tinyint', 'uniqueidentifier');
SELECT TableName,
ColumnName,
PKTableName,
PKColumnName
FROM #ColumnList
WHERE HasForeignKey = 0
AND ColumnName IN (SELECT ColumnName
FROM #ColumnList
GROUP BY ColumnName
HAVING Count(*) > 1)
ORDER BY ColumnName,
TableName;
That works fine and displays many candidates. I do not know, however, how to display the table name and the column name of the possible primary key. Any help will be appreciated.
Try this
DECLARE #ColumnList AS TABLE
(
TableName varchar(255) NOT NULL,
ColumnName varchar(255) NOT NULL,
PKTableName varchar(255),
PKColumnName varchar(255),
HasForeignKey bit NOT NULL
)
-- Find all column names that occur more than once.
-- Exclude archive and staging tables.
INSERT INTO #ColumnList
(TableName,
ColumnName,
PKTableName,
PKColumnName,
HasForeignKey)
SELECT t.NAME AS TableName,
c.NAME AS ColumnName,
t2.NAME AS PKTableName,
c2.NAME AS PKColumnName,
CASE
WHEN f1.parent_object_id IS NOT NULL THEN 1
WHEN f2.referenced_object_id IS NOT NULL THEN 1
ELSE 0
END AS HasForeignKey
FROM sys.tables AS t
JOIN sys.columns AS c
ON c.object_id = t.object_id
JOIN sys.types AS y
ON c.system_type_id = y.system_type_id
LEFT JOIN sys.columns c2
ON (c.Name = c2.Name)
JOIN sys.tables t2
ON (c2.object_id = t2.object_id AND t.object_id <> t2.object_id)
LEFT JOIN sys.foreign_key_columns AS f1
ON f1.parent_object_id = t.object_id AND f1.parent_column_id = c.column_id
LEFT JOIN sys.foreign_key_columns AS f2
ON f2.referenced_object_id = t.object_id AND f2.referenced_column_id = c.column_id
WHERE t.is_ms_shipped = 0 AND y.NAME IN ('bigint', 'int', 'smallint', 'tinyint', 'uniqueidentifier');
SELECT TableName,
ColumnName,
PKTableName,
PKColumnName
FROM #ColumnList
WHERE HasForeignKey = 0
AND ColumnName IN (SELECT ColumnName
FROM #ColumnList
GROUP BY ColumnName
HAVING Count(*) > 1)
ORDER BY ColumnName,
TableName;
I would try doing a LEFT OUTER JOIN to primary keys based on the column name:
LEFT OUTER JOIN
(
INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CCU ON
CCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG AND
CCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA AND
CCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME AND
CCU.TABLE_CATALOG = TC.TABLE_CATALOG AND
CCU.TABLE_SCHEMA = TC.TABLE_SCHEMA AND
CCU.TABLE_NAME = TC.TABLE_NAME
) ON
CCU.COLUMN_NAME = C.ColumnName AND -- Use the #ColumnList.ColumnName here
TC.CONSTRAINT_TYPE = 'PRIMARY KEY'
This breaks down a bit with composite PKs. It also won't work if the names don't match - for example, some people will use "id" for the PK of "Person", but in the FK it would be "person_id".
I'm changing from local time to UTC-time in our database.
There are alot of triggers that copies information to history tables that currently uses GETDATE().
I would like to find every trigger that uses GETDATE() (instead of GETUTCDATE()) in the database, is there any way to do this automatic?
I've listed them by select * from sys.triggers but I also need to see the actual code to be able to find the use of GETDATE().
Your could try the following:
SELECT o.[name],
c.[text]
FROM sys.objects AS o
INNER JOIN sys.syscomments AS c
ON o.object_id = c.id
WHERE o.[type] = 'TR'
Here's the script I used to export triggers:
DECLARE #t VARCHAR (MAX)
SET #t = ''
SELECT #t = #t + 'IF EXISTS (SELECT 1 FROM sys.triggers WHERE object_id = OBJECT_ID(N''' + s.name + '.' + o.name +'''))
DROP TRIGGER ' + s.name + '.' + o.name + '
GO
' + OBJECT_DEFINITION (OBJECT_ID( s.name + '.' + o.name )) +'
GO
'
FROM sys.objects o
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
INNER JOIN sys.objects o2 ON o.parent_object_id = o2.object_id
WHERE o. [type] = 'TR'
AND (
OBJECTPROPERTY ( o.object_id , 'ExecIsInsertTrigger' ) = 1
OR
OBJECTPROPERTY ( o.object_id , 'ExecIsUpdateTrigger' ) = 1
OR
OBJECTPROPERTY ( o.object_id , 'ExecIsDeleteTrigger' ) = 1
)
SELECT #t AS [processing-instruction(x)] FOR XML PATH ('')
More details here if it doesn't make sense to anyone:
http://paulfentonsql.co.uk/2015/09/01/generate-createdrop-statements-for-all-triggers-of-a-given-type/
If you want to export all triggers from the database... here's some code:
DECLARE #vchServerName VARCHAR(500)
DECLARE #vchDBName VARCHAR(500)
DECLARE #intLoop INTEGER
DECLARE #intTotalRows INTEGER
DECLARE #intId INTEGER
DECLARE #vchName VARCHAR(500)
DECLARE #vchSQL VARCHAR(4000)
-- supress count (just so log looks nicer!)
SET NOCOUNT ON
-- get current DB and server
SET #vchDBName = DB_NAME()
SET #vchServerName = ##servername
-- get list of XXX
SELECT ROW_NUMBER() OVER (ORDER BY o.object_id ) fldRowNum
,o.object_id fldId
,s.name + '.' + o.name fldName
INTO #tblFound
FROM sys.objects o
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE [type] = 'TR'
SET #intTotalRows = ##ROWCOUNT
SET #intLoop = 1
-- loop thru list
WHILE #intLoop <= #intTotalRows
BEGIN
SELECT #intID = fldId
,#vchName = fldName
FROM #tblFound
WHERE fldRowNum = #intLoop
PRINT 'Exporting ' + #vchName + '...'
-- NOTE: I'm using a version of bcp that doens't have -D parameter so I need to use DB name here
SET #vchSQL = 'SELECT c.[text] FROM ' + #vchDBName + '.sys.syscomments AS c WHERE c.id = ' + CONVERT(VARCHAR,#intID)
SET #vchSQL = 'bcp "' + #vchSQL + '" queryout "c:\temp\' + #vchName + '.sql" -c -t -T -S ' + #vchServerName
EXEC master..XP_CMDSHELL #vchSQL
SET #intLoop = #intLoop + 1
END
DROP TABLE #tblFound
PRINT 'Done'
I'm wondering what the simplest way to list all indexes for all tables in a database is.
Should I call sp_helpindex for each table and store the results in a temp table, or is there an easier way?
Can anyone explain why constraints are stored in sysobjects but indexes are not?
Here's an example of the kind of query you need:
select
i.name as IndexName,
o.name as TableName,
ic.key_ordinal as ColumnOrder,
ic.is_included_column as IsIncluded,
co.[name] as ColumnName
from sys.indexes i
join sys.objects o on i.object_id = o.object_id
join sys.index_columns ic on ic.object_id = i.object_id
and ic.index_id = i.index_id
join sys.columns co on co.object_id = i.object_id
and co.column_id = ic.column_id
where i.[type] = 2
and i.is_unique = 0
and i.is_primary_key = 0
and o.[type] = 'U'
--and ic.is_included_column = 0
order by o.[name], i.[name], ic.is_included_column, ic.key_ordinal
;
This one is somewhat specific to a certain purpose (I use it in a little C# app to find duplicate indexes and format the output so it's actually readable by a human). But you could easily adapt it to your needs.
You could reference sysindexes
Another trick is to look at the text of sp_helpindex to see how it reconstructs information from the underlying tables.
sp_helptext 'sp_helpindex'
I don't have a reference for this, but I believe constraints are not stored in sysobjects because they are a different kind of thing; sysindexes contains meta-data about objects in sysobjects.
If you need more information, here is a nice SQL script, which I use from time to time:
DECLARE #TabName varchar(100)
CREATE TABLE #temp (
TabName varchar(200), IndexName varchar(200), IndexDescr varchar(200),
IndexKeys varchar(200), IndexSize int
)
DECLARE cur CURSOR FAST_FORWARD LOCAL FOR
SELECT name FROM sysobjects WHERE xtype = 'U'
OPEN cur
FETCH NEXT FROM cur INTO #TabName
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #temp (IndexName, IndexDescr, IndexKeys)
EXEC sp_helpindex #TabName
UPDATE #temp SET TabName = #TabName WHERE TabName IS NULL
FETCH NEXT FROM cur INTO #TabName
END
CLOSE cur
DEALLOCATE cur
DECLARE #ValueCoef int
SELECT #ValueCoef = low FROM Master.dbo.spt_values WHERE number = 1 AND type = N'E'
UPDATE #temp SET IndexSize =
((CAST(sysindexes.used AS bigint) * #ValueCoef)/1024)/1024
FROM sysobjects INNER JOIN sysindexes ON sysobjects.id = sysindexes.id
INNER JOIN #temp T ON T.TabName = sysobjects.name AND T.IndexName = sysindexes.name
SELECT * FROM #temp
ORDER BY TabName, IndexName
DROP TABLE #temp
Here is a script that will return SQL statements to recreate all the indexes in a database.
SELECT ' CREATE ' +
CASE
WHEN I.is_unique = 1 THEN ' UNIQUE '
ELSE ''
END +
I.type_desc COLLATE DATABASE_DEFAULT + ' INDEX ' +
I.name + ' ON ' +
SCHEMA_NAME(T.schema_id) + '.' + T.name + ' ( ' +
KeyColumns + ' ) ' +
ISNULL(' INCLUDE (' + IncludedColumns + ' ) ', '') +
ISNULL(' WHERE ' + I.filter_definition, '') + ' WITH ( ' +
CASE
WHEN I.is_padded = 1 THEN ' PAD_INDEX = ON '
ELSE ' PAD_INDEX = OFF '
END + ',' +
'FILLFACTOR = ' + CONVERT(
CHAR(5),
CASE
WHEN I.fill_factor = 0 THEN 100
ELSE I.fill_factor
END
) + ',' +
-- default value
'SORT_IN_TEMPDB = OFF ' + ',' +
CASE
WHEN I.ignore_dup_key = 1 THEN ' IGNORE_DUP_KEY = ON '
ELSE ' IGNORE_DUP_KEY = OFF '
END + ',' +
CASE
WHEN ST.no_recompute = 0 THEN ' STATISTICS_NORECOMPUTE = OFF '
ELSE ' STATISTICS_NORECOMPUTE = ON '
END + ',' +
' ONLINE = OFF ' + ',' +
CASE
WHEN I.allow_row_locks = 1 THEN ' ALLOW_ROW_LOCKS = ON '
ELSE ' ALLOW_ROW_LOCKS = OFF '
END + ',' +
CASE
WHEN I.allow_page_locks = 1 THEN ' ALLOW_PAGE_LOCKS = ON '
ELSE ' ALLOW_PAGE_LOCKS = OFF '
END + ' ) ON [' +
DS.name + ' ] ' + CHAR(13) + CHAR(10) + ' GO' [CreateIndexScript]
FROM sys.indexes I
JOIN sys.tables T
ON T.object_id = I.object_id
JOIN sys.sysindexes SI
ON I.object_id = SI.id
AND I.index_id = SI.indid
JOIN (
SELECT *
FROM (
SELECT IC2.object_id,
IC2.index_id,
STUFF(
(
SELECT ' , ' + C.name + CASE
WHEN MAX(CONVERT(INT, IC1.is_descending_key))
= 1 THEN
' DESC '
ELSE
' ASC '
END
FROM sys.index_columns IC1
JOIN sys.columns C
ON C.object_id = IC1.object_id
AND C.column_id = IC1.column_id
AND IC1.is_included_column =
0
WHERE IC1.object_id = IC2.object_id
AND IC1.index_id = IC2.index_id
GROUP BY
IC1.object_id,
C.name,
index_id
ORDER BY
MAX(IC1.key_ordinal)
FOR XML PATH('')
),
1,
2,
''
) KeyColumns
FROM sys.index_columns IC2
--WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables
GROUP BY
IC2.object_id,
IC2.index_id
) tmp3
)tmp4
ON I.object_id = tmp4.object_id
AND I.Index_id = tmp4.index_id
JOIN sys.stats ST
ON ST.object_id = I.object_id
AND ST.stats_id = I.index_id
JOIN sys.data_spaces DS
ON I.data_space_id = DS.data_space_id
JOIN sys.filegroups FG
ON I.data_space_id = FG.data_space_id
LEFT JOIN (
SELECT *
FROM (
SELECT IC2.object_id,
IC2.index_id,
STUFF(
(
SELECT ' , ' + C.name
FROM sys.index_columns IC1
JOIN sys.columns C
ON C.object_id = IC1.object_id
AND C.column_id = IC1.column_id
AND IC1.is_included_column =
1
WHERE IC1.object_id = IC2.object_id
AND IC1.index_id = IC2.index_id
GROUP BY
IC1.object_id,
C.name,
index_id
FOR XML PATH('')
),
1,
2,
''
) IncludedColumns
FROM sys.index_columns IC2
--WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables
GROUP BY
IC2.object_id,
IC2.index_id
) tmp1
WHERE IncludedColumns IS NOT NULL
) tmp2
ON tmp2.object_id = I.object_id
AND tmp2.index_id = I.index_id
WHERE I.is_primary_key = 0
AND I.is_unique_constraint = 0
--AND T.name NOT LIKE 'mt_%'
--AND I.name NOT LIKE 'mt_%'
--AND I.Object_id = object_id('Person.Address') --Comment for all tables
--AND I.name = 'IX_Address_PostalCode' --comment for all indexes
I do not have a clear explanation why indexes are not stored in sys.objects. But I would like to contribute to find a simple way to list all indexes for all tables and views in a database. The following query retrieves all indexes including their type and also their object id and object type.
use /*Enter here your database*/
go
select A.Object_id,B.name,B.type,B.type_desc, A.index_id,A.type,A.type_desc
from sys.indexes A left join sys.objects B on A.object_id=B.object_id
where B.type = 'U' or B.type='V' /*filtering on U or V to retrieve tables and views only*/
order by B.name ASC /*Optional sorting*/
I've written this code to iterate through all the databases in your server and push it to a table in a database named Maintenance. You should create this database first and then create a table in that database with the following fields:
CREATE TABLE [dbo].[DBCC_Stats](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DatabaseName] [varchar](50) NULL,
[SchemaName] [nvarchar](128) NULL,
[TableName] [sysname] NOT NULL,
[StatName] [nvarchar](128) NULL,
[modification_counter] [bigint] NULL,
[rows] [bigint] NULL,
[rows_sampled] [bigint] NULL,
[% Rows Sampled] [bigint] NULL,
[last_updated] [datetime2](7) NULL,
[DateEntered] [datetime] NULL,
CONSTRAINT [PK_DBCC_Stats] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DBCC_Stats] ADD CONSTRAINT [DF_DBCC_Stats_DateEntered] DEFAULT (getdate()) FOR [DateEntered]
To use the stored procedure below you'd pass in the server name.
usp_Execute_Stats '[YourServerName]'
`CREATE PROCEDURE usp_Execute_Stats
#ServerName varchar(100)
AS
BEGIN
DECLARE #strSQL varchar(max)
SET #strSQL='USE ?
SELECT ''' + '?' + ''' AS DatabaseName,OBJECT_SCHEMA_NAME(obj.object_id) SchemaName, obj.name TableName,
stat.name StatName, modification_counter,
[rows], rows_sampled, rows_sampled* 100 / [rows] AS [% Rows Sampled],
last_updated
FROM ' + #ServerName + '.' + '?' + '.sys.objects AS obj
INNER JOIN ' + #ServerName + '.' + '?' + '.sys.stats AS stat ON stat.object_id = obj.object_id
CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp
WHERE obj.is_ms_shipped = 0
ORDER BY modification_counter DESC'
INSERT INTO Maintenance.dbo.vwDBCC_Stats
EXEC sp_MSforeachdb #strSQL
--Delete older logs
DELETE Maintenance.dbo.DBCC_Stats
--WHERE DatabaseName IN('Master','Model','MSDB','TempDB')
WHERE [DateEntered] < getdate()-14
END`