Thursday, September 19, 2024

Comparing database structures in MSSQL server

Many firms, developing client-server applications, have a lot of the databases, from developers DB to testers DB. Keeping DB in actual state can be automated easily only if there is centralized access to all of them and there are no developments on any of them. The last influences a lot of the DB structure because there is a possibility to break it while developing.

Because of this there appears a problem of comparing DB structures which are under development, testing and being used by the end-users.

There are number of traditional ways to solve the above problem of finding differences between the etalon DB and the DB being checked. The most obvious way is getting metainfo from the both DBs in the client application and following comparing them. Along with ease and universality, this method is rather time-taking to implement on the client side. Meanwhile, to find any problems with structure integrity a short analysis without any UI is usually enough.

This problem can be solved easily in MSSQL server by using server stored procedures and a possibility of this server to make heterogeneous queries. The metadata structure of this server is open and can be studied via SQL Server Books Online supplied with it and Microsoft’s web-site – MSDN.

The most of server’s objects are situated in one system table, sysobjects. The heart of the method is to make a heterogeneous query which joins the sysobjects tables of checked and etalon DBs and to look through the resulting cursor to compare parameters of the same objects and then, to produce the results for user. For instance, we can write the following statement batch:

select * from etalondb.dbo.sysobjects etalon 
left join sysobjects test on test.[name] = etalon.[name] 
where etalon.[xtype] = 'U'   

Running this script for the etalon DB and then for the DB being checked, we can get a full picture of differences between the two DBs (absent and unnecessary tables). But, it is rather inconvenient to analyze the result of comparing as is. A more suitable form is messages saying about presence, absence or inconsistency of the checked objects. To do that, let us improve our algorithm by using temporary cursors. At that we use the PRINT statement to show the results.

declare @o_name varchar(50), @o_id int, @o_type char(2) 
declare @c_name varchar(50), @c_id int 
declare o_sysobjects cursor fast_forward for 
   select [name], [id], [type] from etalondb..sysobjects 
   where (type in ('F', 'U', 'V')) order by [name] 
open o_sysobjects 
fetch o_sysobjects into @o_name, @o_id, @o_type 
while @@fetch_status = 0 
begin 
   select @c_name = null, @c_id = null 
   select @c_name = [name], @c_id = [id] from sysobjects 
   where [name] = @o_name and isnull(objectproperty([id], 'ISMSShipped'), 1)  1 
   if (@c_name is not null) 
   begin 
      if (@o_type = 'U') 
      begin 
         exec sp_check_columns @c_name 
         exec sp_check_indexes @c_name 
      end else 
      if (@o_type = 'V') 
      begin 
         exec sp_check_columns @c_name 
      end else 
      if (@o_type = 'F') 
      begin 
         exec sp_check_foreignkey @c_name 
      end 
      select 'OK! ' + @o_type + '-' + @c_name 
   end else 
   if (isnull(objectproperty(@c_id, 'ISMSShipped'), 1)  1) 
   begin 
      select 'ERROR! etalondb.' + @o_type + '-' + @o_name 
   end 
   fetch o_sysobjects into @o_name, @o_id, @o_type 
end 
close o_sysobjects 
deallocate o_sysobjects   

Because of presence of the system objects in the same table we have to make the following check-up – (isnull(objectproperty(@c_id, ‘ISMSShipped’), 1) 1). Such objects include rules, defines, indexes which have been built with statistics by MSSQL server to perform self-tuning. These objects do not have to be serviced by developers and their presence or absence shouldn’t affect system work ability as a whole.

The need of join objects by the names in different DBs is because of ambiguity of their numerical identifiers, which are unique only within DB. That is why we should note that it is impossible to join by the names objects which have been made by the server itself. It happens because the server automatically assigns names to those objects. Fortunately, developers should just know about presence of an object and its properties, as a rule.

Checking columns may consist of simple check for presence as well as comparing data types which these columns store.

create procedure sp_check_columns @table_name varchar(50) as 
begin 
   declare @o_name varchar(50), @o_table_id int, @o_type_name varchar(50), 
      @o_length int, @o_isnullable int, @o_status int 
   declare @c_name varchar(50), @c_table_id int, @c_type_name varchar(50), 
      @c_length int, @c_isnullable int, @c_status int 
   set @o_table_id = object_id('etalondb..' + @table_name) 
   set @c_table_id = object_id(@table_name) 
   declare o_syscolumns cursor fast_forward for 
      select c.[name], t.[name], c.length , c.isnullable, c.status 
      from etalondb..syscolumns c, etalondb..systypes t 
      where c.usertype = t.usertype and c.[id] = @o_table_id 
   open o_syscolumns 
   fetch o_syscolumns into @o_name, @o_type_name, @o_length, @o_isnullable, @o_status 
   while @@fetch_status = 0 
   begin 
      select @c_name = null 
      select @c_name = c.[name], @c_type_name = t.[name], 
         @c_length = c.length, @c_isnullable = c.isnullable, @c_status = c.status 
      from syscolumns c, systypes t 
      where (c.usertype = t.usertype) and (c.[id] = @c_table_id) and (c.[name] = @o_name) 
      if (@c_name is null) or (@o_type_name  @c_type_name) 
         or (@o_length  @c_length) 
         or (@o_isnullable  @c_isnullable) or (@o_status  @c_status) 
      begin 
         select 'ERROR! etalondb.' + @table_name 
      end 
      fetch o_syscolumns into @o_name, @o_type_name, @o_length, 
         @o_isnullable, @o_status 
   end 
   close o_syscolumns 
   deallocate o_syscolumns 
end 

In all cases the results are written to the console only if an error has been found. It spares the developer from conservative review and analysis of the log.

The hardest task is checking indexes. It depends on complexity of store metadata structure in MSSQL server. One of such ways to implement this checking is in a full script listing for comparing metadata of the two DBs, which can be downloaded here.

We should note more features of checking objects which are based on scripts. They are server stored procedures, triggers, views, as well as rules and default constraints. The difficult is that it is very often impossible to make sure that the scripts are identical by using symbol-by-symbol comparing because the server adds some comments to the script headers. It may happen while getting script of the existing database by using Enterprise Manager or SQL-DMO Objects. Fortunately, manually added scripts aren’t affected by the server so that we can use simple check-ups.

create procedure sp_check_object_script @object_name varchar(50) as 
begin 
   declare @o_len int, @c_len int 
   select @o_len = len([ctext]) from etalondb..syscomments 
   where [id] = object_id('etalondb..' + @object_name) 
   select @c_len = len([ctext]) from syscomments where [id] = object_id(@object_name) 
   if (@o_len is null) or (@c_len is null) 
   begin 
      select 'ERROR! in calling of sp_check_object_script for object: ' + @object_name 
      return 
   end 
   if (@o_len  @c_len) 
   begin 
      select 'ERROR! the definitions of ' + @object_name + ' are differ' 
   end 
end  

In summary, we would like to note that performance of these check-ups depends on the DB structure evolution and performance and server utilized capacity. It is because, as a rule, there are heterogeneous queries in our scripts which run slower then ordinary queries within one DB.
The full version of the scripts used in this article can be downloaded here.

Sergey S. is a member of the Clever Components Team. The history of our site began from an Interbase DataPump and the original idea was to provide Delphi / Borland C++ and Interbase developers with high quality consultations. Nowadays we have many satisfied customers and can offer some excellent tools and utilities. We wish to maintain the highest standards in our service and keep our customers happy. Please feel free to contact us at info@clevercomponents.com

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles