Generate database content in SQL script by SQL script

A few years back I had a very though project, out team had to release a new version of the intranet portal based on junky and very ugly code that we did inherit after previous company. We have been the 3rd team working on the release, we did manage to deliver, but at a high cost of morale, man-hours and compromises. Suffice to say that we did each 400 man-hours per month, at the peak. Never again.

Team members needed to change database schema and test-data daily and independently, no one had time to integrate changes frequently, so „master” database backup was outdated most of the time. There where tools (eg. http://www.red-gate.com) that we couldn’t afford, at least we thought so, at the time 😉 . Soon this was a major source of problems. I have decided that we MUST do a daily database change integration and ALL team members have to work on a current database.

You need for your saw to be sharp, when you must do things repeatably, reliably and you have no time. I decided to write a neat script that would generate a SQL code with database content, that could then be manipulated in the text editors and kept in the CVS repository. Actually this script work as a tandem with SQL Server 2000 generate scripts function, for schema changes merging.

This is that background. I realise that the script could be written better or cleaner, but this is not a production code, it is supposed to create some output in a desired way, and it does. I use that script successfully for 5 years now (as other projects can benefit from it too). I have added some tweaks and modifications over time, but the foundation concept is the same and it work for well for me.

Features:

  • Works from Query Analyser/Management Studio
  • Allows you to generate content SQL script in 2 steps:
    • Generate content generation script
    • Generate content SQL script with result of the previous step
  • Takes care of table relationships and dependencies (sorry no circular dependencies support 🙁 )
  • Identity insert support
  • Generates an SQL script that can be easily manipulated in notepad and merged during CVS/SVN updates/commits
  • Allows you to keep database schema and content (test data, initial dictionary tables fill-up) only in clean SQL scripts, no backups necessary 🙂
    • Integrated with NAnt, NUnit and Cruise Control gives a powerful mechanism for reliable and repeatable test automation

How it works:

  • It uses SQL Server system tables to find out table relations and schema (uses a special view for this).
  • Sorts tables in order of their dependencies
  • Generates a content generation SQL script for a specific database.

What it needs:

  • Query results in Text
    • „Include column headers in the result set” – Off
    • „Maximum number of characters displayed in each column” – set to maximum, 8192.
-- ===================================================================
-- Generates database a script that will generate a script with
-- database content in SQL insert statements.
-- Works with MS SQL Server 2000/2005
--
--  Copyright 2002-2006 Janusz Skonieczny
--
--   Licensed under the Apache License, Version 2.0 (the "License");
--   you may not use this file except in compliance with the License.
--   You may obtain a copy of the License at
--
--       http://www.apache.org/licenses/LICENSE-2.0
--
--   Unless required by applicable law or agreed to in writing, software
--   distributed under the License is distributed on an "AS IS" BASIS,
--   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--   See the License for the specific language governing permissions and
--   limitations under the License.
--
-- Author: Janusz Skonieczny
--         jskonieczny@gmail.com
--         http://skonieczny.pl/software
--
-- ===================================================================
-- Last changes made:
-- $Id:  $
-- ===================================================================
-- Search & Replace db name : MyDatabase
--
-- Contact the author if you have any questions.
-- ===================================================================

USE [MyDatabase]

set nocount on

-- ========================================================================
-- Creates a a helper view with table names, table id's and tables
-- connected by FK
-- ========================================================================

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

CREATE VIEW v_Dependencies
AS

SELECT dep.name 	as name, ss.name as schema_name,
dep.id,
case when  dep.name = depc.name then null
else depc.name
end as depends
--	depc.name	as depends
--	depon.name,
--	dep.xtype,
--	depc.*,
--	''
FROM sysobjects dep
-- 	LEFT JOIN sysdepends on dep.id = sysdepends.id
-- 	LEFT JOIN sysobjects depon on sysdepends.depid = depon.id
--	LEFT OUTER JOIN (SELECT * from sysconstraints where sysconstraints.status & 3 = 3) const on dep.id = const.id
--	LEFT OUTER JOIN sysobjects depc on const.constid = depc.id
LEFT OUTER JOIN sysforeignkeys fk on dep.id = fk.fkeyid
LEFT OUTER JOIN sysobjects depc on fk.rkeyid = depc.id

LEFT OUTER JOIN sys.objects so on so.object_id = dep.id
LEFT outer JOIN sys.schemas ss on ss.schema_id = so.schema_id
WHERE
dep.xtype = 'U' AND dep.name <> 'dtproperties' AND dep.name <> 'sysdiagrams'
--and (sysconstraints.status & 3 = 3 or sysconstraints.status is null)
--and dep.xtype <> 'S' AND dep.name <> 'dtproperties'
--order by dep.xtype,depc.name
--order by name

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

DECLARE @database VARCHAR(255)
SELECT @database = '['+DB_NAME()+']'

-- ========================================================================
-- Sorts tables acording to their dependecies
-- (highest priority, least dependencies)
-- ========================================================================
declare @tablesTable Table(name nvarchar (128),id int,depends nvarchar (128),priority int,schema_name nvarchar(128))
declare @tablesTable2 Table(name2 nvarchar (128),id2 int,depends2 nvarchar (128),priority2 int,schema_name nvarchar(128))
declare @count int
declare @stopCount int
set @count = 1
set @stopCount = 1

-- select * from v_Dependencies

insert into @tablesTable (name,id,depends,priority,schema_name)
select name,id,depends,0,schema_name
from v_Dependencies v

update @tablesTable set priority = 2 where depends is null or depends = name

-- ustaw najnisze z jej priority dla kazdej z tabel -------
delete from @tablesTable2
insert into @tablesTable2 select * from @tablesTable
update @tablesTable set priority = (select min(priority2) from @tablesTable2 where name = name2)
delete from @tablesTable2
insert into @tablesTable2 select * from @tablesTable
-- ---------------------------------------------------

while @count > 0 and @stopCount < 40  begin

update @tablesTable set priority = priority + 1 where
depends in (select name2 from @tablesTable2 where priority2 > priority)
or depends = name or depends is null

-- ustaw najnisze z jej priority dla kazdej z tabel -------
delete from @tablesTable2
insert into @tablesTable2 select * from @tablesTable
update @tablesTable set priority = (select min(priority2) from @tablesTable2 where name = name2)
delete from @tablesTable2
insert into @tablesTable2 select * from @tablesTable
-- ---------------------------------------------------

select @count = count(*) from @tablesTable where priority = 0

update @tablesTable set priority = priority + 1 where priority > 0

select @stopCount = @stopCount + 1
--select name,depends,priority from @tablesTable order by name, Priority desc
end
delete from @tablesTable2
insert into @tablesTable2 (name2,priority2,id2) select schema_name+'.['+name+']',priority, id from @tablesTable group by name, priority, id, schema_name

-- ========================================================================

DECLARE @dbid INT

SELECT @dbid = dbid
FROM master..sysdatabases
WHERE name = @database

DECLARE @sql1 VARCHAR(8000)
DECLARE @sql2 VARCHAR(8000)
DECLARE @name NVARCHAR(255)
DECLARE @field_name VARCHAR(255)
DECLARE @type_name VARCHAR(255)
DECLARE @field_cnt INT
DECLARE @id INT
DECLARE @identity_fields INT
DECLARE @apos8 VARCHAR(8)
DECLARE @depends VARCHAR(2000)

set @apos8 = '''''''''' -- > '''' --> ''

print '-- ============================================================================== '
print '--  Skrypt generujacy skrypt z insertami wypeniajcymi baz pusta danych :'
print '--     '+ @database
print '--  Wygenerowany w dniu: ' + CONVERT(nvarchar,getDate(),120) + ' przez ' + SYSTEM_USER
print ''
print '--  Skrypt powinien zachowa zalenoci pomiedzy tabelami, to trzeba przetestowa '
print '--  $Id: $ '
print '-- ============================================================================== '

print 'use ' + @database
print 'set nocount on'

print ' PRINT ''-- ============================================================= '''
print ' PRINT ''--  Skrypt zawierajcy dane z bazy: ' + @database + ''''
print ' PRINT ''--  Wygenerowany w dniu: '' + CONVERT(nvarchar,getDate(),120) + '' przez '' + SYSTEM_USER '
print ' PRINT ''--  $Id: $ '''
print ' PRINT ''-- ============================================================= '''
print ' PRINT ''use ' + @database + ''''
print ' PRINT ''set nocount on'''
print ' PRINT '''''
print ' PRINT ''-- ============================================================= '''
print ' PRINT ''-- Oczysc baze '''
select ' PRINT ''DELETE ' + name2 + '''' from @tablesTable2 order by priority2 asc
print ' PRINT ''-- ============================================================= '''

DECLARE cur_tables CURSOR FOR
SELECT name2, id2
FROM @tablesTable2
order by Priority2 desc
--WHERE xtype = 'U' AND name <> 'dtproperties'
--FOR READ ONLY

OPEN cur_tables

FETCH NEXT FROM cur_tables
INTO @name, @id

WHILE @@FETCH_STATUS = 0
BEGIN
print ''
print ' PRINT ''-- ============================================================= '''
print ' PRINT ''-- 	' + @name + ''''
if (select count (*) from @tablesTable where name = @name and depends is not null) > 0 begin
print ' PRINT ''--'''
print ' PRINT ''-- Depends on: '''
select ' PRINT ''-- 	' + depends + '''' from @tablesTable where name = @name and depends is not null
end
print ' PRINT ''-- ============================================================= '''
print ' PRINT '''''
print ''
SELECT @sql1 = 'SELECT ''INSERT INTO ' + @name + ' ('
SELECT @sql2 = ''
SELECT @field_cnt = 0

SELECT @identity_fields = COUNT(*)
FROM syscolumns
WHERE id = @id AND (status & 128 <> 0)

IF @identity_fields > 0
BEGIN
SELECT 'SELECT ''SET IDENTITY_INSERT ' + @name + ' ON'''
END

DECLARE cur_fields CURSOR FOR
SELECT syscolumns.name, systypes.name
FROM syscolumns INNER JOIN systypes ON syscolumns.xtype = systypes.xtype
WHERE id = @id
FOR READ ONLY

OPEN cur_fields

FETCH NEXT FROM cur_fields
INTO @field_name, @type_name

WHILE @@FETCH_STATUS = 0
BEGIN

IF @type_name <> 'sysname'
BEGIN
IF @field_cnt > 0
BEGIN
SELECT @sql1 = @sql1 + ','
SELECT @sql2 = @sql2 + ' + '','''
END

SELECT @field_cnt = @field_cnt + 1

SELECT @sql1 = @sql1 + '[' + @field_name + ']'
SELECT @sql2 = @sql2 + '+ '

IF @type_name = 'int' OR @type_name = 'smallint' OR @type_name = 'bit' OR @type_name = 'tinyint'  OR  @type_name = 'bigint' OR  @type_name = 'float' OR  @type_name = 'money' begin
SELECT @sql2 = @sql2 + 'ISNULL (CONVERT (varchar, [' + @field_name + ']), ''NULL'') COLLATE POLISH_CI_AS'
end
ELSE IF @type_name = 'nvarchar' OR @type_name = 'varchar' OR @type_name = 'char' OR @type_name = 'nchar' or @type_name = 'uniqueidentifier' BEGIN
SELECT @sql2 = @sql2 + 'CASE WHEN [' + @field_name +']'
SELECT @sql2 = @sql2 + ' IS NULL THEN ''NULL'' ELSE '
SELECT @sql2 = @sql2 + ' + '''''''' + REPLACE ([' + @field_name + '], '''''''', '''''''''''') COLLATE POLISH_CI_AS + '''''''' END'
END
ELSE IF @type_name = 'ntext' or @type_name = 'text'BEGIN
SELECT @sql2 = @sql2 + 'CASE WHEN [' + @field_name +']'
SELECT @sql2 = @sql2 + ' IS NULL THEN ''NULL'' ELSE '
SELECT @sql2 = @sql2 + ' + '''''''' + REPLACE (CAST([' + @field_name + '] as nvarchar(4000)), '''''''', '''''''''''') COLLATE POLISH_CI_AS + '''''''' END '
END
ELSE IF @type_name = 'binary' OR @type_name = 'image' BEGIN
SELECT @sql2 = @sql2 + '''NULL'''  -- binariues not suported
END
ELSE IF @type_name = 'datetime' begin
SELECT @sql2 = @sql2 + 'CASE WHEN [' + @field_name +']'
SELECT @sql2 = @sql2 + ' IS NULL THEN ''NULL'' ELSE '' '
SELECT @sql2 = @sql2 + 'CONVERT (datetime, '''''' + '
SELECT @sql2 = @sql2 + 'CONVERT (varchar, '
SELECT @sql2 = @sql2 + @field_name
SELECT @sql2 = @sql2 + ', 121) + '''''', 121)'' END'
end
ELSE
PRINT 'Unknown type : ' + @type_name + ' in table : ' + @name + ' field : ' + @field_name
END

FETCH NEXT FROM cur_fields
INTO @field_name, @type_name
END

CLOSE cur_fields
DEALLOCATE cur_fields

SELECT @sql2 = @sql1 + ')
VALUES (''' + @sql2 + ' + '')''
FROM '  + @name
select @sql2

IF @identity_fields > 0
BEGIN
SELECT 'SELECT ''SET IDENTITY_INSERT ' + @name + ' OFF'''
END

FETCH NEXT FROM cur_tables
INTO @name, @id
END

CLOSE cur_tables
DEALLOCATE cur_tables

DROP VIEW v_Dependencies

You can download it here.

2 Comments

  1. Here’s a tool that will generate scripts for all objects in any 2000 or 2005 database – you may find it useful.

    The source is available so you can modify it to fit your needs. also there’s a batch file in the download that will execute all the scripts
    that are generated to deploy the database to any server, or use it as a BVT in a source control system.

    http://www.elsasoft.org/tools.htm

Dodaj komentarz