问题
So, I've been hunting for a solution for this for awhile and have come up with what sort of works ... but I can't help feeling that there must be something more elegant.
What I'm looking for is to be able to extract existing data from a populated database & incorporate that data into loading scripts. The database schema & configuration data will be rolled out multiple times, and will change as development continues, so it's important to be able to rebuild the configuration data from existing data, rather than from static data kept in scripts.
Here's what I've cobbled together:
create procedure #dump (
@TableName varchar(128)
)
as
set nocount on
set rowcount 0
declare @template varchar(max)
set @template = 'SET IDENTITY_INSERT [dbo].[' + @TableName + '] ON
MERGE INTO [dbo].[' + @TableName + '] AS [Target]
USING
(
VALUES
{vals}
)
AS [Source] ({fields})
ON [Target].[{pk}] = [Source].[{pk}]
WHEN MATCHED THEN UPDATE SET
{upds}
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
{fields}
)
VALUES
(
{fields}
);
SET IDENTITY_INSERT [dbo].[' + @TableName + '] OFF
--------------------------------------------------
'
declare @pk varchar(max) = ''
declare @vals varchar(max) = '/*
set concat_null_yields_null off
select ''' + '(' + ''' + replace(replace({casts} + '') ,'', '',,'', '', null,''), ''' + ',)' + ''', ''' + ',null)' + ''') from [' + @TableName + ']
*/'
declare @casts varchar(max) = ''
declare @fields varchar(max) = ''
declare @upds varchar(max) = ''
declare @inserts varchar(max) = ''
set @pk = SUBSTRING(@TableName, 1, len(@TableName) - 1) + 'ID'
declare cur_flds
cursor for select c.name, c.type
from syscolumns c
where c.id = object_id(@TableName)
order by c.colid
declare @fn varchar(max)
declare @ft int
open cur_flds
fetch next from cur_flds into @fn, @ft
while @@FETCH_STATUS = 0
begin
if len(@fields) > 0
set @fields = @fields + ', '
set @fields = @fields + '[' + @fn + ']'
if len(@casts) > 0
set @casts = @casts + ' + ' + ''','' + '
if @ft in(56,55,50,38,48)
set @casts = @casts + 'cast([' + @fn + '] as varchar)'
else if @ft = 111
set @casts = @casts + ''''''''' + ' + 'cast([' + @fn + '] as varchar) + ' + ''''''''''
else
set @casts = @casts + ''''''''' + ' + 'replace([' + @fn + '], ''''''''' + ', ' + ''''''''''''') + '''''''''
if @fn != @pk
begin
if len(@upds) > 0
set @upds = @upds + ', '
set @upds = @upds + '[Target].[' + @fn + '] = [Source].[' + @fn + ']'
end
fetch next from cur_flds into @fn, @ft
end
close cur_flds
deallocate cur_flds
set @vals = REPLACE(@vals, '{casts}', @casts)
set @template = REPLACE(@template, '{pk}', @pk)
set @template = REPLACE(@template, '{vals}', @vals)
set @template = REPLACE(@template, '{fields}', @fields)
set @template = REPLACE(@template, '{upds}', @upds)
set @template = REPLACE(@template, '{inserts}', @inserts)
print @template
go
exec #dump 'ActionItemSystems'
drop proc #dump
That ends up giving me output of:
SET IDENTITY_INSERT [dbo].[ActionItemSystems] ON
MERGE INTO [dbo].[ActionItemSystems] AS [Target]
USING
(
VALUES
/*
set concat_null_yields_null off
select '(' + replace(replace(cast([ActionItemSystemID] as varchar) + ',' + '''' + replace([ActionItemSystemName], '''', '''''') + '''' + ') ,', ',,', ', null,'), ',)', ',null)') from [ActionItemSystems]
*/
)
AS [Source] ([ActionItemSystemID], [ActionItemSystemName])
ON [Target].[ActionItemSystemID] = [Source].[ActionItemSystemID]
WHEN MATCHED THEN UPDATE SET
[Target].[ActionItemSystemName] = [Source].[ActionItemSystemName]
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
[ActionItemSystemID], [ActionItemSystemName]
)
VALUES
(
[ActionItemSystemID], [ActionItemSystemName]
);
SET IDENTITY_INSERT [dbo].[ActionItemSystems] OFF
From this point, I can take the commented-out bit
set concat_null_yields_null off
select '(' + replace(replace(cast([ActionItemSystemID] as varchar) + ',' + '''' + replace([ActionItemSystemName], '''', '''''') + '''' + ') ,', ',,', ', null,'), ',)', ',null)') from [ActionItemSystems]
execute that, and get output like:
(33,'7111-5 -Upstream/Seed Lab') ,
(32,'7301-Seed Lab') ,
(30,'7807 UFDF') ,
(14,'BAS Panel Upgrade') ,
(1,'Clean Steam') ,
(13,'DCS') ,
(2,'HWFI') ,
(3,'MCS') ,
(12,'MES') ,
(31,'Seed Lab') ,
(18,'UCS WRO') ,
(34,'Upstream Seed Lab') ,
(29,'Viral Filtration') ,
which can then be incorporated (sans the final comma) into the script.
Now, this solution functions, but it's fragile. It depends on various assumptions (e.g., that the Table Name will have a Primary Key of Table Name - trailing 's' and plus ID) that may not hold true for every solution. It also requires cutting & pasting, and rerunning from the start when the table structures change.
This is probably quite a lot of background ... which I'm partly sharing because I couldn't find anything similar out there & thought that somebody might benefit from this. However, I still come back to my real question, which is to say: where's the tool to generate this kind of script for VS Database Projects? There really should be something - something that would take into account whatever the primary key is, that would generate the thing entire, etc.
回答1:
You can try with this procedure for generating MERGE statements: https://github.com/readyroll/generate-sql-merge It is more advanced version of what you already have.
来源:https://stackoverflow.com/questions/29989065/population-of-visual-studio-database-project-data-loading-scripts-from-existing