You can also do this with dynamic SQL without having to manually build out all the column names. DECLARE @sql NVARCHAR(MAX), @c1 NVARCHAR(MAX), @c2 NVARCHAR(MAX); SELECT @c1 = N'', @c2 = N''; SELECT @c1 = @c1 + ',' + QUOTENAME(name), @c2 = @c2 + ' AND m.' + QUOTENAME(name) + ' = s.' + QUOTENAME(name) FROM sys.columns WHERE name <> 'LocationID' AND [object_id] = OBJECT_ID('dbo.table1'); SET @sql = ';WITH s AS ( SELECT ' + STUFF(@c1, 1, 1, '') + ' FROM dbo.table1 EXCEPT SELECT ' + STUFF(@c1, 1, 1, '') + ' FROM dbo.table1_master ) SELECT m.LocationID FROM s INNER JOIN dbo.table1 AS m ON 1 = 1 ' + @c2; SELECT @sql; --EXEC sp_executesql @sql; You can take the output of this query as is and store the query somewhere, or you can comment out the `SELECT` and uncomment the `EXEC` and leave it as permanent dynamic SQL - in this case it will automatically adapt to column changes in the two tables. Another idea (assuming LocationID is unique): ;WITH c AS ( SELECT t.LocationID, m.setting1, m.setting2, ... FROM dbo.table1 AS t CROSS JOIN dbo.table1_master AS m ) SELECT LocationID, setting1, setting2, ... FROM c EXCEPT SELECT LocationID, setting1, setting2, ... FROM dbo.table1;