SQL点滴18—SqlServer中的merge操作,相当地风骚

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介: 原文:SQL点滴18—SqlServer中的merge操作,相当地风骚今天在一个存储过程中看见了merge这个关键字,第一个想法是,这个是配置管理中的概念吗,把相邻两次的更改合并到一起。后来在technet上搜索发现别有洞天,原来是另外一个sql关键字,t-sql的语法还是相当地丰富的。
原文: SQL点滴18—SqlServer中的merge操作,相当地风骚

今天在一个存储过程中看见了merge这个关键字,第一个想法是,这个是配置管理中的概念吗,把相邻两次的更改合并到一起。后来在technet上搜索发现别有洞天,原来是另外一个sql关键字,t-sql的语法还是相当地丰富的。本篇是一篇学习笔记,没有什么新意,这里给出technet上的地址连接供大家参考权威:http://technet.microsoft.com/zh-cn/library/bb510625.aspx,这里具体的语法不去深究了,只是把几个例子实际运行,剖析一番。

  

使用merge同时执行insert和update操作

我们经常会有这样的需求,根据某个字段或多个字段查找表中的一行或多行数据,如果查找成功得到匹配项,更新其中的其他一个或多个字段;如果查找失败则将“某个字段或多个字段”作为新的一行中的数据插入到表中。第一种方法是先更新,然后根据@@rowcount判断是否有匹配项,如果没有则插入。先使用下面的 代码创建一个存储过程。

 
 
1 use AdventureWorks
2 go
3 create procedure dbo.InsertUnitMeasure @UnitMeasureCode nchar ( 3 ), @Name nvarchar ( 25 )
4 as
5 begin
6 set nocount on ;
7 update Production.UnitMeasure set Name = @Name where UnitMeasureCode = @UnitMeasureCode
8 if ( @@ROWCOUNT = 0 )
9 begin
10 insert into Production.UnitMeasure(Name,UnitMeasureCode) values ( @Name , @UnitMeasureCode )
11 end
12 end
13 go

记得见过这样的笔试题目,要求是插入不存在的行,只要把上面语句中的update改成select就可以了,当时没有写出来,现在恍然大悟,也许是在考察@@ROWCOUNT的用法吧。这个语句也可以使用merge语句实现。下面我们使用merge关键字来修改这个存储过程。

 
 
1 alter procedure dbo.InsertUnitMeasure @UnitMeasureCode nchar ( 3 ), @Name nvarchar ( 25 )
2 as
3 begin
4 set nocount on
5 merge Production.UnitMeasure as target
6 using ( select @UnitMeasureCode , @Name ) as source (UnitMeasureCode,Name)
7 on (target.UnitMeasureCode = source.UnitMeasureCode)
8 when matched then update set Name = source.Name
9 when not matched then insert (UnitMeasureCode,Name) values (source.UnitMeasureCode,Name)
10 output deleted. * ,$action,inserted. * into MyTempTable;
11 end
12 go

这个语句使用merge修改存储过程,这个语句中又出现我不太了解的关键字using和$action。Using是用来指定和表InsertUnitMeasure中相匹配的数据源,这里的数据源来自外部输入,是通过两个输入参数得到。$action可能是一个占位符,表示上面的when字句进行的操作。至于inserted.*和deleted.* 就是插入和删除的数据行了,这个我在其中一篇文章中也提到,他们有点类似类中的this关键字,过可以看看:SQL点滴14—编辑数据。注意为了记录修改的过程我们需要创建一个临时表#MyTempTable来跟踪修改过程,所以在调用这个存储过程之前我们需要新建这个表,语句如下:

 
 
1 create table MyTempTable(
2 ExistingCode nchar ( 3 ),
3 ExistingName nvarchar ( 50 ),
4 ExistingDate datetime ,
5 ActionTaken nvarchar ( 50 ),
6 NewCode nchar ( 3 ),
7 [ NewName ] nvarchar ( 50 ),
8 NewDate datetime
9 )
10 Go

现在我们来执行下面的语句看看有什么样的结果:

 
 
1 exec InsertUnitMeasure @UnitMeasureCode = ' ABC ' , @Name = ' New Test Value1 '
2 EXEC InsertUnitMeasure @UnitMeasureCode = ' XYZ ' , @Name = ' Test Value ' ;
3 EXEC InsertUnitMeasure @UnitMeasureCode = ' ABC ' , @Name = ' Another Test Valuea ' ;
4 Go

首先使用语句:select * from Production.UnitMeasure order by ModifiedDate desc 来查看目标表中的数据变化如图1:img_a4f8b52e8c789f02b080b7f1b5714309.png

图1

这里虽然三次执行了存储过程,但是由于第一次和第三次的@UnitMeasureCode的值是相同的’ABC’所以第二次肯定是进行更新操作。所以最后表中新增了两条记录。然后使用下面的语句查看记录表MyTempTable中的跟踪信息如图2

img_aa166430f1f4c070cb3cc60bf56987ce.png

图2

我们可以看到前面两条语句执行的是插入操作,所以原有的值都是空,因为在插入之前他们还不存在。第三条新型的是更新操作,更新UnitMeasureCode为’ABC’的记录。
 
  

使用merge在单个语句中执行insert和update操作

在AdventureWorks数据库中有ProductInventory表,存储的是存货信息,SalesOrderDetail表中存储的是订单信息,现在如果每天减去对SalesOrderDetail表中每种产品所下的订单数,更新ProductInventory表中的 Quantity列。如果随着时间推移订单数导致产品库存量下降到0或者更少,则从ProductInventory表中删除该产品对应的行。下面的语句创建一个存储过程实现上面的逻辑。

 
 
1 CREATE PROCEDURE Production.usp_UpdateInventory
2 @OrderDate datetime
3 AS
4 MERGE Production.ProductInventory AS target
5 USING ( SELECT ProductID, SUM (OrderQty) FROM Sales.SalesOrderDetail AS sod
6 JOIN Sales.SalesOrderHeader AS soh
7 ON sod.SalesOrderID = soh.SalesOrderID
8 AND soh.OrderDate = @OrderDate
9 GROUP BY ProductID) AS source (ProductID, OrderQty)
10 ON (target.ProductID = source.ProductID)
11 WHEN MATCHED AND target.Quantity - source.OrderQty <= 0
12 THEN DELETE
13 WHEN MATCHED
14 THEN UPDATE SET target.Quantity = target.Quantity - source.OrderQty,
15 target.ModifiedDate = GETDATE ()
16 OUTPUT $action, Inserted.ProductID, Inserted.Quantity, Inserted.ModifiedDate, Deleted.ProductID,
17 Deleted.Quantity, Deleted.ModifiedDate;
18 GO

这个语句比第一个要复杂一点,注意当匹配成功并且总量小于0的时候直接使用一个delete就可以将此条记录删除,output语句直接把操作结果输出,相当地神奇。最后运行下面的 语句得到如图3的结果。注意这个语句相当于将2003年5月1号的订单量减去。如果多次运行的话就相当于多减了一次,整个表中数据条数会减少的。

EXECUTE Production.usp_UpdateInventory '20030501'

img_234dd42255973433fc71b4fc810cc993.png

图3

  

借助派生源表,使用merge对目标表执行update和insert操作

这次我们已知有一些表数据,我们要和Sales.SalesReason这个表中的数据做对比,如果和SalesReason表中的Name字段匹配时就更新表中的ReasonType列,如果没有匹配项的时候就插入这一行新的数据。在这里是使用表值构造函数指定源表的多个行,使用表变量存储更新记录,注意表变量的使用范围。代码如下:

 
 
1 declare @SummaryOfChanges table (Change varchar ( 20 ))
2 merge into Sales.SalesReason as target
3 using( values ( ' Recommendation ' , ' Other ' ),( ' Review ' , ' Marketing ' ),( ' Internet ' , ' Promotion ' )) as source( [ NewName ] ,NewReasonType)
4 on target.Name = source. [ NewName ]
5 when matched then update set ReasonType = source.NewReasonType
6 when not matched by target then insert (Name,ReasonType) values ( [ NewName ] ,NewReasonType)
7 output $action into @SummaryOfChanges ;
8 select Change, COUNT ( * ) as CountPerChange from @SummaryOfChanges group by Change

执行完上面的语句之后我们得到下面的结果说明执行了2次插入,1次更新,如图4。那么是不是这样的 呢,我们查看Sales.SalesReason这个表发现原来已经有’Review’这一条数据了,对它执行了更新,剩下的’Recommendation’,’Internet’执行的是插入操作。如果再次执行上面的语句就会得到UPDATE 3这样的结果,因为已经存在这三条数据了所以都执行UPDATE。

img_11c1583f03dd5047c6560b7c5e348f7c.png

图4

  

将merge执行的结果插入到另外一个表中

我们还可以将merge操作得到的结果写入到另外一个表中,如下的语句将更新的每条数据信息写入到一个新建的表Production.UpdatedInventory中,代码如下:

 
 
1 INSERT INTO Production.UpdatedInventory
2 SELECT ProductID, LocationID, NewQty, PreviousQty
3 FROM
4 ( MERGE Production.ProductInventory AS target
5 USING ( SELECT ProductID, SUM (OrderQty)
6 FROM Sales.SalesOrderDetail AS sod
7 JOIN Sales.SalesOrderHeader AS soh
8 ON sod.SalesOrderID = soh.SalesOrderID
9 AND soh.OrderDate BETWEEN ' 20030701 ' AND ' 20030731 '
10 GROUP BY ProductID) AS source (ProductID, OrderQty)
11 ON target.ProductID = source.ProductID
12 WHEN MATCHED AND target.Quantity - source.OrderQty >= 0
13 THEN UPDATE SET target.Quantity = target.Quantity - source.OrderQty
14 WHEN MATCHED AND target.Quantity - source.OrderQty <= 0
15 THEN DELETE
16 OUTPUT $action, Inserted.ProductID, Inserted.LocationID, Inserted.Quantity AS NewQty, Deleted.Quantity AS PreviousQty)
17 AS Changes (Action, ProductID, LocationID, NewQty, PreviousQty) WHERE Action = ' UPDATE ' ;
18 GO

执行这个语句再查询表得到如下图5的结果,我们可以看到新的销售量总是比以前的销售量要少,因为执行一次就要减去订单量。

img_fa8b697ba328df73dee41892ba2beea8.png

图5

这里我们只记录了更新的变化,如果想记录所有的操作可以去掉最后的一个限制条件WHERE Action = 'UPDATE',那就要修改记录表的结构了,这个和第二个例子有些相似,只不过将记录在实际的表中,而第二个例子仅仅输出这些操作记录。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
8天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
51 10
|
2月前
|
SQL 算法 数据库
【数据库SQL server】关系数据库标准语言SQL之数据查询
【数据库SQL server】关系数据库标准语言SQL之数据查询
95 0
|
8天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
66 6
|
30天前
|
SQL 安全 数据库
第三章用sql语句操作数据
第三章用sql语句操作数据
10 0
|
1月前
|
SQL druid Java
【MyBatis】2、MyBatis 的动态 SQL 和增删改操作
【MyBatis】2、MyBatis 的动态 SQL 和增删改操作
29 0
|
2月前
|
SQL 算法 数据库
【数据库SQL server】关系数据库标准语言SQL之数据更新
【数据库SQL server】关系数据库标准语言SQL之数据更新
33 0
|
2月前
|
SQL 数据库 数据库管理
【数据库SQL server】关系数据库标准语言SQL的基本知识
【数据库SQL server】关系数据库标准语言SQL的基本知识
57 0
|
1月前
|
SQL 数据库 数据安全/隐私保护
Sql Server数据库Sa密码如何修改
Sql Server数据库Sa密码如何修改
|
2月前
|
SQL 算法 数据库
【数据库SQL server】关系数据库标准语言SQL之视图
【数据库SQL server】关系数据库标准语言SQL之视图
76 0
|
18天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
16 0