I have a SQL Server 2008 database with several views. I’m looking for a way to definitively determine whether a particular view has a column that can only contain unique values (i.e. can be used as a primary key).
Some of these views have simple definitions, but some are also quite complex, so I find that manually analysing each of these views would be very time-consuming.
As well, some of these views have columns that concatenate values from other columns in the view in order to be “unique”, but I can’t be certain whether these values are actually unique because in some cases I have found that they are not.
One simple approach I had was to use the following query to determine if any duplicate values exist for a particular column in a view:
SELECT column_name FROM view_name GROUP BY column_name HAVING COUNT(column_name) > 1
The above query works fine when there is enough data in the view to catch any non-unique values, but this still isn’t definitive.
Is there a way to have SQL Server perform this analysis to determine whether a column in a view can only contain unique values?
Short answer: you’ll have to do your own dirty work. Like @mrdenny says, there is no way of automating this task.
Very long answer: SQL Server doesn’t have an easy way of determining unique columns in a view, in part because of how complex (and dynamic) views can be. There are two methods to make a first approximation, however, and these methods can work in concert. The first is what you already have: performing queries against the view to see if you can find anything which already violates uniqueness constraints. If it does, you can throw that column (or set of columns) out immediately. If it doesn’t, that combination might be a valid unique key, but there is no guarantee.
The second method is to reason from the data model, starting with unique constraints (assuming you have those on your tables!). There are three nice ways that you can have enforced uniqueness on a table: a primary key, a unique key constraint, or a unique index. All three of them show up in the
sys.indexes system table and have the
is_unique property set to 1. There are also some not-so-nice ways like using triggers to enforce uniqueness.
You could try messing around with
sys.dm_sql_referenced_entities and a query like this might be helpful to give you a starting point:
declare @ViewName sysname = 'MYVIEW'; with viewcolumns as ( select * from sys.dm_sql_referenced_entities(@ViewName, 'OBJECT') dsre /* where is_selected = 1 --uncomment if using SQL 2012 */ ), uniquereferences as ( select i.object_id, object_schema_name(i.object_id) as SchemaName, object_name(i.object_id) as TableName, i.name as IndexName, c.name as ColumnName, case when vc.referenced_entity_name IS NOT NULL then 1 else 0 end as HasReference from sys.indexes i inner join sys.index_columns ic on i.index_id = ic.index_id and i.object_id = ic.object_id inner join sys.columns c on c.column_id = ic.column_id and c.object_id = ic.object_id left outer join viewcolumns vc on vc.referenced_id = c.object_id and vc.referenced_minor_id = c.column_id where i.is_unique = 1 ), sufficientreferences as ( select object_id, IndexName, min(HasReference) as HasReference from uniquereferences group by object_id, IndexName having min(HasReference) = 1 ) select ur.object_id, ur.SchemaName, ur.TableName, ur.IndexName, ur.ColumnName from uniquereferences ur inner join sufficientreferences sr on ur.IndexName = sr.IndexName and ur.object_id = sr.object_id where ur.HasReference = 1 /* Optional: remove referenced tables; if you have a 1:1 reference, leave this bit out */ and not exists ( select * from sys.foreign_keys fk inner join viewcolumns vc on fk.parent_object_id = vc.referenced_id where vc.referenced_minor_id = 0 and fk.referenced_object_id = ur.object_id );
For SQL 2008, the is_selected flag is not available, so there’s no way to tell if the column returned by that function is actually part of the SELECT clause or if it is is used in a join or filter. With SQL 2012, you could at least limit your query to the columns that actually are part of the SELECT clause.
What you get from this is not a set of unique keys for the view. What you get is a set of columns which make up unique keys on their underlying tables. The difference is that you could have a reference table with a unique key constraint on the Name column, and that Name column would show up in the above query even if the view joins the reference table to the base table (thereby causing repeated use of the reference table’s Name column). To help alleviate that, I have a
NOT EXISTS clause which removes cases in which the object is the referenced table in a foreign key relationship with another table in the view, so our unique index for the reference data table should not show up.
What this does allow you to do is reduce your possible answer space. But even then, you’ll be doing a lot of spadework. The more complex your views get, the less valuable this is. For example, if you have a UNION ALL in your query, the statement above might show you a candidate column set which is wrong, because those columns might be duplicated in the other half of the UNION ALL. Or if you have cross-server queries,
sys.dm_sql_referenced_entities might not even show you any column names. In other words, the query above is a semi-functional aid and certainly not a method of automating the process.
Note that this does depend upon having unique constraints specified. If your only unique keys are surrogate primary keys, it might not be quite as easy to find a candidate column set because not even SQL Server knows that the column combination is supposed to be unique.