使用一个存储过程完成数据插入和更新(使用xml)(通用insert和update)(mssql2008以上)

天天维护网站,天天写数据插入insert into。。。。天天写数据更新update .. set 。。。。而且每个页面,每个表,每个数据的结构都不太一样,即便是同一个表,应用不同,应用到的列也不一样,每次都重新写sql指令,好烦躁

于是,琢磨了一下,可不可以用一个存储过程自动完成数据插入和更新呢?

首先,应该把所有应用到的字段整理一下,作为参数传递给存储过程,比如:id,name,col,sort,desc,xxtime等等等等。。。

好吧,参数就这么麻烦,得了,把他们封装到一起吧,想想看,sql2008已经有不少关于xml的处理方法和函数了,那么,直接把所有参数封装到一个xml里,参数传递的问题就解决了,嗯。。。这个思路不错,这样,我们就传递“”就可以了,至于怎么弄成xml格式,这里就不再进行说明,太多方法可以做到了

然后,我们先创建一个测试用的存储过程

CREATE PROCEDURE test_update_with_xml
	@args xml
AS
BEGIN
	SET NOCOUNT ON;

	declare @handle int,@prepare int
	exec @prepare = sp_xml_preparedocument @handle output,@args
	;with t as (
		select * from openxml(@handle,'/r',1)
	),t1 as (
		select a.id as rowid,a.localname as col,convert(nvarchar(max),b.text) as val 
		from t a 
		left join t as b on b.parentid=a.id 
		where a.parentid=0
	)
    select * from t1
END

嗯。。。。测试一下,还可以,能得到一个返回的表,这个表里就是我们传入的xml的内容了,并且列和值也分开了

那么,我们继续第三步:保证数据的完整性

因为有很多时候,我们为了保证数据的完整,在创建表结构的时候,我们就设置了一些字段不允许为空,而有些时候,我们则设置了默认值,那么,对于我们的参数传递进来的,没有涉及到的字段未必是不需要的,所以,我们还得把目标表结构掉出来,并进行补足

好吧,上边这个第三步是为了插入数据准备的,判断依据则很简单,有主键字段的,则是更新,否则是插入

更新指令比较简单,因为数据行已存在,不存在插入失败的情况,所以我们先把更新弄出来

	declare @handle int,@prepare int
	exec @prepare = sp_xml_preparedocument @handle output,@args
	;with t as (
		select * from openxml(@handle,'/r',1)
	),t1 as (
		select a.id as rowid,a.localname as col,convert(nvarchar(max),b.text) as val 
		from t a 
		left join t as b on b.parentid=a.id 
		where a.parentid=0
	)
    select * into #t from t1
    declare @id int
    select @id = val from #t where col='id'
    if isnull(@id,0)=0
        begin
            -- 数据插入
        end
    else
        begin
            -- 数据更新
        end

那么,我们现在就把需要更新的字段信息补全,生成临时表#t1

	declare @tbid int,@tbname varchar(100)
    
    select @tbname='测试用的表名'

    select @tbid = id from sysobjects with (nolock) where name=@tbname

    select a.col,a.val,b.colorder,isnullable,s.text as def,t.name as coltype
	into #t1
	from #t a
	left join syscolumns b with (nolock) on a.col=b.name and b.id=@tbid
	left join syscomments s with (nolock) on b.cdefault=s.id
	left join systypes t with (nolock) on b.xtype=t.xusertype

    

再然后就是拼接字符串,生成update指令了,由于不确定到底更新哪些字段,所以,我们使用游标来协助一下

	declare cur cursor local for
	select col,(case 
		when val is not null then (case 
			when charindex('char',coltype)>0 then ''''+replace(val,'''','''''')+'''' 
			when coltype in ('date','datetime','uniqueidentifier') then ''''+val+''''
			else val
		end)
		when def is not null then def 
		when isnullable=0 then (case
			when charindex('char',coltype)>0 then ''''''
			when coltype in ('int','bigint','money','bit','decimal','float') then '0'
			when coltype in ('date','datetime') then 'getdate()'
			when coltype='uniqueidentifier' then ''''+convert(varchar(50),newid())+''''
		end) 
		else 'null' 
	end) as val
	from #t1 where col<>'p_id' and colorder is not null
	order by colorder

因为在临时表t1里,我们已经把是否允许为空,默认值都搞出来了,所以,在游标里,我们直接把这些都弄好,直接弄成可以用来拼接字符串的值,比如varchar类型的,就把值里的所有单引号进行转义替换,并且外边加上引号

那么更新指令就很简单的可以得到了

	open cur
	declare @sql nvarchar(max)
	set @sql = 'update '+@tbname+' set somefield=''somevalue''' -- 这里根据需要写一些默认的字段,如果没有的话,下边的@sql就把逗号放到最后

	fetch next from cur into @col,@val
	while @@fetch_status=0
		begin
            -- 如果没有默认需要更新的字段,逗号放在后边set @sql = @sql+@col+'='+@val +','
			set @sql = @sql +','+@col+'='+@val
			fetch next from cur into @col,@val
		end
    -- 如果逗号放在后边,需要去除最后的逗号
	set @sql = @sql + ' where p_id='+convert(varchar,@pid)

	exec(@sql)

	close cur
	deallocate cur

嗯,还不错,更新指令可以说完美的生成出来了

那么,我们现在来弄一弄插入指令

插入比更新麻烦的地方在于,更新,我们只需要匹配出表结构对应的字段即可,及以临时表#t为主要依据,但是插入数据的话,我们需要以syscolumns为主要依据,以避免出现遗漏的非空字段遗漏,造成插入失败的问题

那么,我们重新来写一个生成临时表的方法,用来生成插入字段

			;with t as (
				select * from openxml(@handle,'/r',1)
			),t1 as (
				select a.id as rowid,a.localname as col,convert(nvarchar(max),b.text) as val 
				from t a 
				left join t as b on b.parentid=a.id 
				where a.parentid=0
			)
			select isnull(col,b.name) as col,a.val,b.colorder,isnullable,b.def,b.coltype
            into #t2
			from t1 a
			full outer join (
				select b.name,colorder,isnullable,s.text as def,t.name as coltype from syscolumns b with (nolock)
				left join syscomments s with (nolock) on b.cdefault=s.id
				left join systypes t with (nolock) on b.xtype=t.xusertype
				where b.id=@tbid
                -- 对一些入库时不会涉及的字段,且允许为空,或者有默认值的,可以进行排除
				and b.name not in ('id','sn','state','isdel','oauth','p_create','detail_time')
			) b on a.col=b.name

嗯,这个临时表t2的内容就比之前的t1多了不少东西,后边也就简单了,还是使用游标,然后生成插入指令

	declare cur cursor local for
	select col,(case 
		when val is not null then (case 
			when charindex('char',coltype)>0 then ''''+replace(val,'''','''''')+'''' 
			when coltype in ('date','datetime','uniqueidentifier') then ''''+val+''''
			else val
		end)
		when def is not null then def 
		when isnullable=0 then (case
			when charindex('char',coltype)>0 then ''''''
			when coltype in ('int','bigint','money','bit','decimal','float') then '0'
			when coltype in ('date','datetime') then 'getdate()'
			when coltype='uniqueidentifier' then ''''+convert(varchar(50),newid())+''''
		end) 
		else 'null' 
	end) as val
	from #t2 where col<>'主键字段名' and colorder is not null
	order by colorder
	open cur

	set @sql = 'insert into ' + @tbname + '([fields]) values([values])'
	fetch next from cur into @col,@val
	while @@fetch_status=0
		begin
			set @sql = replace(@sql,',[fields]',','+@col+',[fields]')
					set @sql = replace(@sql,',[values]',','+@val+',[values]')
			fetch next from cur into @col,@val
		end
	set @sql = replace(@sql,',[fields]','')
	set @sql = replace(@sql,',[values]','')

	exec(@sql)
	close cur
	deallocate cur

嗯,这个貌似也不错,可以用了

好吧,这样一来就很简单了,我们将这个内容调整一下,就可以做出一个通用的存储过程,对单表内容进行插入和更新了

下边就是完整的存储过程

CREATE PROCEDURE InsertOrUpdate
	@args xml,@tbname varchar(100)
AS
BEGIN
	SET NOCOUNT ON;

	declare @handle int,@prepare int,@tbid int,@pk varchar(100)
	exec @prepare = sp_xml_preparedocument @handle output,@args
    -- 获得表id
    select @tbid = id from sysobjects with (nolock) where name=@tbname
    -- 获得主键字段名
    select @pk=c.name 
    from sysindexes i with (nolock) 
    inner join sysindexkeys k with (nolock) on i.id=k.id and i.indid=k.indid 
    inner join sysobjects o with (nolock) on o.parent_obj=i.id and o.xtype='pk' and     o.name=i.name 
    inner join syscolumns c with (nolock) on c.id=k.id and c.colid=k.colid
    where i.id=@tbid
    -- 解析xml到临时表#t
	;with t as (
		select * from openxml(@handle,'/r',1)
	),t1 as (
		select a.id as rowid,a.localname as col,convert(nvarchar(max),b.text) as val 
		from t a 
		left join t as b on b.parentid=a.id 
		where a.parentid=0
	)
	select a.col,a.val,b.colorder,isnullable,s.text as def,t.name as coltype
	into #t
	from t1 a
	left join syscolumns b with (nolock) on a.col=b.name and b.id=@tbid
	left join syscomments s with (nolock) on b.cdefault=s.id
	left join systypes t with (nolock) on b.xtype=t.xusertype
 
	declare @id int,@sql nvarchar(max),@col varchar(50),@val nvarchar(max)
    -- 获得主键值
	select @id = val from #t where col=@pk
 
	if isnull(@id,0)=0
		begin
            -- 如果主键为0,则重新整理生成需要插入字段集合的临时表
			delete from #t
 
			;with t as (
				select * from openxml(@handle,'/r',1)
			),t1 as (
				select a.id as rowid,a.localname as col,convert(nvarchar(max),b.text) as val 
				from t a 
				left join t as b on b.parentid=a.id 
				where a.parentid=0
			)
			insert into #t
			select isnull(col,b.name) as col,a.val,b.colorder,isnullable,b.def,b.coltype
			from t1 a
			full outer join (
				select b.name,colorder,isnullable,s.text as def,t.name as coltype from syscolumns b with (nolock)
				left join syscomments s with (nolock) on b.cdefault=s.id
				left join systypes t with (nolock) on b.xtype=t.xusertype
				where b.id=@tbid
				and b.name not in (@pk)
			) b on a.col=b.name
			where a.col is not null or (isnullable=0 and def is null)
		end
 
	declare cur cursor local for
	select col,(case 
		when val is not null then (case 
			when charindex('char',coltype)>0 then ''''+replace(val,'''','''''')+'''' 
			when coltype in ('date','datetime','uniqueidentifier') then ''''+val+''''
			else val
		end)
		when def is not null then def 
		when isnullable=0 then (case
			when charindex('char',coltype)>0 then ''''''
			when coltype in ('int','bigint','money','bit','decimal','float') then '0'
			when coltype in ('date','datetime') then 'getdate()'
			when coltype='uniqueidentifier' then ''''+convert(varchar(50),newid())+''''
		end) 
		else 'null' 
	end) as val
	from #t where col<>@pk and colorder is not null
	order by colorder
	open cur
 
	if @id>0
		begin
 
			set @sql = 'update ' + @tbname + ' set '
 
			fetch next from cur into @col,@val
			while @@fetch_status=0
				begin
					set @sql = @sql + '[' + @col + ']' + '=' + @val +','
					fetch next from cur into @col,@val
				end
			
			set @sql = stuff(@sql,len(@sql),1,'')
			set @sql = @sql + ' where ' + @pk + '='+convert(varchar,@id)
 
			select @sql
			--exec(@sql)
 
		end
	else
		begin
			set @sql = 'insert into ' + @tbname + '('+@pk+',[fields]) values('+@pk+',[values]);select @id = scope_identity()'
 
			fetch next from cur into @col,@val
			while @@fetch_status=0
				begin
					set @sql = replace(@sql,',[fields]',','+@col+',[fields]')
					set @sql = replace(@sql,',[values]',','+@val+',[values]')
					fetch next from cur into @col,@val
				end
			set @sql = replace(@sql,',[fields]','')
			set @sql = replace(@sql,',[values]','')
			set @sql = replace(@sql,'('+@pk+',','(')
 
			select @sql
			--exec sp_executesql @sql,N'@id int output',@id=@id output

		end
	close cur
	deallocate cur
 
	select @id
 
	drop table #t
END

当然了,这个存储过程仅仅是用来更新或插入一个单表数据的,而实际应用中,我们经常同时更新多个表的内容,毕竟有些字段我们会放到相关的关联表中,那么,针对这种情况,我们只需要再写一个特定的存储过程,同样还是将xml传递进来,然后进行分发即可,例如

CREATE PROCEDURE update_product
	@args xml
AS
BEGIN
	SET NOCOUNT ON;

    exec dbo.InsertOrUpdate @args,'table_product'

    exec dbo.InsertOrUpdate @args,'table_product_images'
END

至于分发到各个表的内容,根据需要自己整理一下即可,反正 for xml 方法别的介绍文章也是一大片了

你可能感兴趣的:(sql,sql,xml,通用,insert,update)