-
Notifications
You must be signed in to change notification settings - Fork 0
/
DatabaseStaticAnalysis01.sql
265 lines (247 loc) · 10.6 KB
/
DatabaseStaticAnalysis01.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
DatabaseStaticAnalysis01.sql
Some simple static analysis queries for Microsoft SQL Server (2005, 2008, 2012)
by dportas @ acm.org
v1.02 2011-05-14
These queries perform some very basic checks for commonly encountered problems in SQL Server table designs and index implementation.
Each query identifies a different type of issue - no rows returned = no problem found.
Public domain. No warranty given or implied.
*/
DECLARE @TableName NVARCHAR(128);
SET @TableName = N'%'; -- Table name mask
-- [Q1] No index
-- This query identifies tables without any indexes at all. Every table ought to have at least one index
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'no index' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id AND type>0)
ORDER BY SchemaName, TableName;
-- [Q2] No unique index
-- Returns tables without a unique index. No unique index means no keys, no way to guarantee uniqueness
-- and/or potentially sub-obtimal query plans (if a theoretically unique index wasn't declared as such)
-- Tables without an index at all (already reported by [Q1]) are excluded.
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'no unique index' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id
AND i.is_unique = 1)
EXCEPT
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'no unique index' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id
AND i.type>0)
ORDER BY SchemaName, TableName;
-- [Q3] No clustered index
-- This query returns any heap tables - tables without a clustered index.
-- As a general rule tables should be clustered unless there's a good reason to make them heaps.
-- Tables without an index at all (already reported by [Q1]) are excluded.
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'no clustered index' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id
AND i.type=1)
EXCEPT
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'no clustered index' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id
AND i.type>0)
ORDER BY SchemaName, TableName;
-- [Q4] Unique index on IDENTITY only
-- Identifies tables whose only unique index is on an IDENTITY column.
-- IDENTITY is typically (though not always) reserved for surrogate keys, in which case there should generally exist some
-- alternate key (the domain/natural key) for that table as well. Not necessarily an absolute rule but definitely worth a
-- second look if a table has no key other than an IDENTITY column.
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'unique index on identity only' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
(
SELECT 1
FROM sys.indexes i
JOIN sys.index_columns ic
ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
JOIN sys.columns c
ON ic.object_id = c.object_id
AND ic.column_id = c.column_id
WHERE i.is_unique=1 AND i.object_id = o.object_id
GROUP BY i.object_id, i.index_id
HAVING MAX(CASE c.is_identity WHEN 1 THEN 1 ELSE 0 END)=0
)
EXCEPT
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, N'unique index on identity only' txt
FROM sys.objects o WHERE type = N'U' AND o.name LIKE @TableName AND NOT EXISTS
( SELECT *
FROM sys.indexes i
WHERE i.object_id = o.object_id
AND i.is_unique = 1)
ORDER BY SchemaName, TableName;
-- [Q5] IDENTITY without unique index
-- Usually an IDENTITY column is supposed to be a key, or sometimes part of a key. The IDENTITY property on its own does not
-- guarantee uniqueness. This query checks for potentially non-unique IDENTITY columns, i.e. those without a unique index.
SELECT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, i.name ColumnName, N'IDENTITY without unique index' txt
FROM sys.objects o
JOIN sys.identity_columns i
ON o.object_id = i.object_id
AND o.name LIKE @TableName
WHERE o.type = N'U' AND NOT EXISTS
(
SELECT 1
FROM sys.indexes i
JOIN sys.index_columns ic
ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
JOIN sys.columns c
ON ic.object_id = c.object_id
AND ic.column_id = c.column_id
WHERE i.is_unique=1 AND i.object_id = o.object_id
GROUP BY i.object_id, i.index_id
HAVING COUNT(NULLIF(c.is_identity,0))=1
);
-- [Q6] Nullable unique index
-- Returns the name of any unique index containing a nullable column.
-- Nullable "unique" indexes are not often a wise implementation choice. Their behaviour and interpretation is highly
-- inconsistent between different software and tools and is frequently misunderstood by users and developers alike.
-- Very commonly nullable unique indexes are created in error - the unique index being added without anyone noticing a
-- nullable column.
-- Definitely worth reviewing any cases returned by this query.
SELECT DISTINCT SCHEMA_NAME(MAX(o.schema_id)) SchemaName, MAX(o.name) TableName, MAX(i.name) IndexName, N'nullable unique index' txt
FROM sys.objects o
JOIN sys.indexes i
ON o.object_id = i.object_id
JOIN sys.index_columns ic
ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
JOIN sys.columns c
ON ic.object_id = c.object_id
AND ic.column_id = c.column_id
WHERE o.type = N'U' AND o.name LIKE @TableName AND i.is_unique=1
GROUP BY o.object_id, i.index_id
HAVING MAX(CASE is_nullable WHEN 1 THEN 1 ELSE 0 END)=1;
-- [Q7] Nullable foreign keys
-- Returns any foreign key constraint that includes a nullable column.
-- Similar remarks apply here as to Q6.
SELECT SCHEMA_NAME(MAX(o.schema_id)) SchemaName, o.name TableName,
OBJECT_NAME(fkc.constraint_object_id) ConstraintName,
MIN(CASE WHEN c.is_nullable = 1 THEN c.name END) ColumnName,
N'nullable foreign key' txt
FROM sys.foreign_key_columns fkc
JOIN sys.columns c
ON fkc.parent_object_id = c.object_id
AND fkc.parent_column_id = c.column_id
JOIN sys.objects o
ON o.object_id = fkc.parent_object_id
AND o.name LIKE @TableName
AND c.is_nullable = 1
GROUP BY o.schema_id, o.name, fkc.constraint_object_id;
-- [Q8] Unindexed foreign keys
-- Foreign keys are usually a good candidate for indexes. This query returns the name of any foreign key constraint that
-- doesn't have a matching index.
SELECT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName,
OBJECT_NAME(fkc.constraint_object_id) ConstraintName,
MIN(CASE WHEN ic.object_id IS NULL THEN c.name END) ColumnName,
N'unindexed foreign key' txt
FROM sys.foreign_key_columns fkc
JOIN sys.objects o
ON fkc.parent_object_id = o.object_id
JOIN sys.columns c
ON fkc.parent_object_id = c.object_id
AND fkc.parent_column_id = c.column_id
LEFT JOIN sys.index_columns ic
ON fkc.parent_object_id = ic.object_id
AND fkc.parent_column_id = ic.column_id
WHERE o.name LIKE @TableName
GROUP BY o.schema_id, o.name, fkc.constraint_object_id
HAVING COUNT(ic.object_id)<COUNT(*);
-- [Q9] Untrusted constraints
-- Looks for constraints marked as untrusted meaning they may have been violated and won't be considered by the optimiser.
-- The fix is to re-evaluate them using the WITH CHECK clause, e.g.: ALTER TABLE tablename WITH CHECK CHECK CONSTRAINT ALL;
SELECT DISTINCT SCHEMA_NAME(o.schema_id) SchemaName, o.name TableName, c.name ConstraintName, N'untrusted constraint' txt
FROM sys.objects c
JOIN sys.objects o
ON c.parent_object_id = o.object_id
WHERE OBJECTPROPERTY(c.object_id,'CnstIsNotTrusted')=1
AND o.name LIKE @TableName;
-- [Q10] System-named constraints
-- It's good practice to specify a name for every constraint (including defaults, which in SQL Server terms are known
-- as "constraints"). This query attempts to spot constraints that have no user-defined name. The query doesn't rely
-- only on the is_system_named property because if constraints have been scripted and recreated then that property may
-- not be retained.
SELECT SchemaName, TableName, ConstraintName, N'system-named constraint' txt
FROM
(
SELECT s.name SchemaName, o.name TableName, c.name ConstraintName
FROM sys.key_constraints c
JOIN sys.objects o
ON c.parent_object_id = o.object_id
JOIN sys.schemas s
ON o.schema_id = s.schema_id
WHERE c.is_system_named = 1 OR LEFT(RIGHT(c.name,10),2)=N'__' AND RIGHT(c.name,8) LIKE N'%[0123456789]%[0123456789]%'
UNION
SELECT s.name SchemaName, o.name TableName, c.name ConstraintName
FROM sys.default_constraints c
JOIN sys.objects o
ON c.parent_object_id = o.object_id
JOIN sys.schemas s
ON o.schema_id = s.schema_id
WHERE c.is_system_named = 1 OR LEFT(RIGHT(c.name,10),2)=N'__' AND RIGHT(c.name,8) LIKE N'%[0123456789]%[0123456789]%'
UNION
SELECT s.name SchemaName, o.name TableName, c.name ConstraintName
FROM sys.foreign_keys c
JOIN sys.objects o
ON c.parent_object_id = o.object_id
JOIN sys.schemas s
ON o.schema_id = s.schema_id
WHERE c.is_system_named = 1 OR LEFT(RIGHT(c.name,10),2)=N'__' AND RIGHT(c.name,8) LIKE N'%[0123456789]%[0123456789]%'
UNION
SELECT s.name SchemaName, o.name TableName, c.name ConstraintName
FROM sys.check_constraints c
JOIN sys.objects o
ON c.parent_object_id = o.object_id
JOIN sys.schemas s
ON o.schema_id = s.schema_id
WHERE c.is_system_named = 1 OR LEFT(RIGHT(c.name,10),2)=N'__' AND RIGHT(c.name,8) LIKE N'%[0123456789]%[0123456789]%'
) c
WHERE c.TableName LIKE @TableName
ORDER BY SchemaName, TableName, ConstraintName;
-- [Q11] Inconsistent names / types
-- This query is based on the assumption that a column of any given name represents the same attribute in any table.
-- The query looks for columns with the same name but different datatypes in different tables - possible indication
-- that the naming isn't consistent or that the wrong type was used.
SELECT s.name AS SchemaName,
o.name AS TableName,
c.name AS ColumnName,
t.name AS TypeName,
c.max_length AS MaxLength,
c.precision,
c.scale,
'inconsistent name / type' AS txt
FROM sys.objects o
JOIN sys.schemas s
ON o.schema_id = s.schema_id
JOIN sys.columns c
ON o.object_id = c.object_id
JOIN sys.types t
ON c.user_type_id = t.user_type_id
WHERE OBJECTPROPERTY(o.object_id,'IsUserTable')=1
AND c.name IN
(
SELECT name
FROM
(
SELECT DISTINCT c.name, t.name typename, c.max_length, c.precision, c.scale
FROM sys.columns c
JOIN sys.types t
ON c.user_type_id = t.user_type_id
WHERE OBJECTPROPERTY(c.object_id,'IsUserTable')=1
) t
GROUP BY name
HAVING COUNT(*)>1
)
AND o.name LIKE @TableName
ORDER BY ColumnName, SchemaName, TableName;