【SQLServer】Merge语法

概述

MERGE语句,也被称为“upsert”,根据与源表联接的结果,对目标表进行插入、更新或删除操作。 例如,根据与另一个表的区别,在一个表中插入、更新或删除行,从而同步两个表。
MERGE 语句允许将数据源与目标表或视图联接,然后根据该联接的结果对目标执行多项操作。
MERGE 语句可以执行以下操作:

  • 有条件地在目标表中插入或更新行。
    如果目标表中存在相应行,则更新一个或多个列;否则,会将数据插入新行。
  • 同步两个表。
    根据与源数据的差别在目标表中插入、更新或删除行。

当两个表具有匹配特性的复杂混合时,针对 MERGE 语句介绍的条件行为的效果最佳。例如,插入不存在的行,或更新匹配的行。
如果仅根据另一个表的行更新另一个表时,可以使用 INSERT、UPDATE 和 DELETE 语句来提高性能和可伸缩性。
例如:
INSERT tbl_A (col, col2)
SELECT col, col2
FROM tbl_B
WHERE NOT EXISTS (SELECT col FROM tbl_A A2 WHERE A2.col = tbl_B.col);

Merge语法

-- MERGE 语句用于执行插入、更新或删除操作,根据目标表和源表之间的匹配结果。
MERGE
    -- TOP (expression) [PERCENT]:可选,指定返回结果的行数或百分比。
    [ TOP ( expression ) [ PERCENT ] ]
    -- INTO:关键字,表示合并操作将影响目标表。
    [ INTO ] <target_table> [ WITH ( <merge_hint> ) ] [ [ AS ] table_alias ] -- 这一行指定了目标表<target_table>,并且给它起了别名table_alias ,方便在后续的语句中使用。
    -- USING:关键字,指定要与目标表联接的数据源。
    USING <table_source> [ [ AS ] table_alias ]  -- 定义源数据
    -- ON:关键字,定义MERGE操作的条件,即当目标表target中的ID列与源数据source中的id列匹配时。
    ON <merge_search_condition> - 定义了MERGE操作的条件,即当目标表target中的ID列与源数据source中的id列匹配时
    -- WHEN MATCHED:当目标表中的记录与源表中的记录匹配时执行的语句。
    [ WHEN MATCHED [ AND <clause_search_condition> ] 
        THEN <merge_matched> ] [ ...n ]
    -- WHEN NOT MATCHED:当目标表中的记录与源表中的记录不匹配时执行的语句。
    [ WHEN NOT MATCHED [ BY TARGET ] [ AND <clause_search_condition> ] -- 当条件不匹配时,即ID不存在时
        THEN <merge_not_matched> ] 
    -- WHEN NOT MATCHED BY SOURCE:当源表中的记录与目标表中的记录不匹配时执行的语句。
    [ WHEN NOT MATCHED BY SOURCE [ AND <clause_search_condition> ]
        THEN <merge_matched> ] [ ...n ] 
    -- <output_clause>:可选,用于指定MERGE操作的结果输出。
    [ <output_clause> ]
    -- OPTION (<query_hint> [ ,...n ] ):可选,用于指定查询优化器使用的查询提示。
    [ OPTION ( <query_hint> [ ,...n ] ) ]
;

MERGE 语法包括五个主要子句:

  • MERGE 子句用于指定作为插入、更新或删除操作目标的表或视图。
  • USING 子句用于指定要与目标联接的数据源。
  • ON 子句用于指定决定目标与源的匹配位置的联接条件。
  • WHEN 子句(WHEN MATCHED、WHEN NOT MATCHED BY TARGET 和 WHEN NOT MATCHED BY SOURCE)基于 ON 子句的结果和在 WHEN 子句中指定的任何其他搜索条件指定所要采取的操作。
  • OUTPUT 子句针对插入、更新或删除的目标中的每一行返回一行。

注意
Merge语句必须指定三个 MATCHED 子句中的至少一个子句,但可以按任何顺序指定。 无法在同一个 MATCHED 子句中多次更新一个变量。
并且MERGE 语句需要一个分号 (😉 作为语句终止符。 如果运行没有终止符的 MERGE 语句,将引发错误 10713。

性能优化

搜索条件使用原则

必须正确指定用于匹配源行和目标行的搜索条件和用于从源或目标中筛选行的其他搜索条件,以确保获得正确结果。建议遵循以下指导原则:

  • 在 ON <merge_search_condition> 子句中仅指定决定源表与目标表中数据的匹配的搜索条件;
  • 不要包括与其他值(如常量)的比较。

若要从源表或目标表筛选出行,请使用以下方法之一:

  • 在适当的 WHEN 子句中指定用于行筛选的搜索条件。例如,WHEN NOT MATCHED AND S.EmployeeName LIKE ‘S%’ THEN INSERT…;
  • 对返回筛选行的源表或目标表定义视图,并且将该视图作为源表或目标表进行引用。如果该视图是针对目标表定义的,则针对该视图的任何操作都必须满足更新视图所需的条件;
  • 使用 WITH <通用表表达式> 子句从源表或目标表中筛选出行。此方法类似于在 ON 子句中指定附加搜索条件,并可能产生不正确的结果。建议您避免使用此方法,或者在采用它前进行全面测试。

MERGE 语句中联接操作的优化方式与 SELECT 语句中联接操作的优化方式相同。 也就是说,当 SQL Server 处理联接时,查询优化器从多种可行方法中选择最高效的方法来处理联接。 如果源表和目标表的大小相似,且前面介绍的索引准则已应用于源表和目标表,那么合并联接运算符是最高效的查询计划。 这是由于对两个表都只扫描一次,并且无需对数据进行排序。 如果源表小于目标表,最好使用嵌套循环运算符。
通过在 MERGE 语句中指定 OPTION (<query_hint>) 子句,可以强制使用某种特定联接。 建议不要将哈希联接用作 MERGE 语句的查询提示,因为该联接类型不使用索引。

有关索引的最佳做法

通过使用 MERGE 语句,可以使用单个语句替换各个 DML 语句。 由于操作是在单个语句中执行的,因此可以提高查询性能,从而最大限度地减少处理源表和目标表中数据的次数。 然而,性能的提升取决于是否进行了正确的索引和联接以及是否遵守了其他注意事项。
若要提高 MERGE 语句的性能,我们建议您遵循以下索引准则:

  • 创建索引以促进 MERGE 的源与目标之间的联接:
    • 在源表的联接列上创建索引,该索引具有涵盖目标表的联接逻辑的键。 如果可能,该索引应该是唯一的。
    • 此外,在目标表中的联接列上创建索引。 如果可能,该索引应该是唯一的聚集索引。
    • 这两个索引可确保对表中的数据进行排序,而唯一性有助于进行比较。 因为查询优化器不需要执行额外验证处理即可定位和更新重复的行,也不需要执行其他排序操作,所以查询性能得到了提高。
  • 避免将具有任何形式的列存储索引的表作为 MERGE 语句的目标。 与任何 UPDATE 一样,通过更新暂存行存储表,然后执行批量 DELETE 和 INSERT(而不是 UPDATE 或 MERGE)操作,你可能会发现列存储索引的性能更好。

使用示例

使用 MERGE 插入、更新和删除数据

FactBuyingHabits 表用于跟踪每个客户购买特定产品的最后日期。
Purchases 用于记录给定周的购买情况。
现在每周都要从 Purchases 表向 FactBuyingHabits 表中添加特定客户以前从未购买过的产品的行。对于购买以前曾经购买过的产品的客户的行,需更新 FactBuyingHabits 表中的购买日期。可以使用 MERGE 在一条语句中执行这些插入和更新操作。

IF OBJECT_ID (N'dbo.Purchases', N'U') IS NOT NULL 
    DROP TABLE dbo.Purchases;
GO
CREATE TABLE dbo.Purchases (
    ProductID int, CustomerID int, PurchaseDate datetime, 
    CONSTRAINT PK_PurchProdID PRIMARY KEY(ProductID,CustomerID));
GO
INSERT INTO dbo.Purchases VALUES
(707, 11794, '20060821'),
(707, 15160, '20060825'),
(708, 18529, '20060821'),
(711, 11794, '20060821'),
(711, 19585, '20060822'),
(712, 14680, '20060825'),
(712, 21524, '20060825'),
(712, 19072, '20060821'),
(870, 15160, '20060823'),
(870, 11927, '20060824'),
(870, 18749, '20060825');

IF OBJECT_ID (N'dbo.FactBuyingHabits', N'U') IS NOT NULL 
    DROP TABLE dbo.FactBuyingHabits;
GO
CREATE TABLE dbo.FactBuyingHabits (
    ProductID int, CustomerID int, LastPurchaseDate datetime, 
    CONSTRAINT PK_FactProdID PRIMARY KEY(ProductID,CustomerID));
GO

INSERT INTO dbo.FactBuyingHabits VALUES
(707, 11794, '20060814'),
(707, 18178, '20060818'),
(864, 14114, '20060818'),
(866, 13350, '20060818'),
(866, 20201, '20060815'),
(867, 20201, '20060814'),
(869, 19893, '20060815'),
(870, 17151, '20060818'),
(870, 15160, '20060817'),
(871, 21717, '20060817'),
(871, 21163, '20060815'),
(871, 13350, '20060815'),
(873, 23381, '20060815');

这两个表中有两个共有的产品-客户行:分别于本周和上周,客户 11794 购买了产品 707,客户 15160 购买了产品 870。对于这些行,我们可以使用 WHEN MATCHED THEN 子句利用 Purchases 中这些购买记录的日期来更新 FactBuyingHabits。我们可以使用 WHEN NOT MATCHED THEN 子句将所有其他行插入 FactBuyingHabits

MERGE dbo.FactBuyingHabits AS Target
USING (SELECT CustomerID, ProductID, PurchaseDate FROM dbo.Purchases) AS Source
ON (Target.ProductID = Source.ProductID AND Target.CustomerID = Source.CustomerID)
WHEN MATCHED THEN
    UPDATE SET Target.LastPurchaseDate = Source.PurchaseDate
WHEN NOT MATCHED BY TARGET THEN
    INSERT (CustomerID, ProductID, LastPurchaseDate)
    VALUES (Source.CustomerID, Source.ProductID, Source.PurchaseDate)
OUTPUT $action, Inserted.*, Deleted.*;

接口查看:

select * from FactBuyingHabits;

+---------+----------+-----------------------+
|ProductID|CustomerID|LastPurchaseDate       |
+---------+----------+-----------------------+
|707      |11794     |2006-08-21 00:00:00.000|
|707      |15160     |2006-08-25 00:00:00.000|
|707      |18178     |2006-08-18 00:00:00.000|
|708      |18529     |2006-08-21 00:00:00.000|
|711      |11794     |2006-08-21 00:00:00.000|
|711      |19585     |2006-08-22 00:00:00.000|
|712      |14680     |2006-08-25 00:00:00.000|
|712      |19072     |2006-08-21 00:00:00.000|
|712      |21524     |2006-08-25 00:00:00.000|
|864      |14114     |2006-08-18 00:00:00.000|
|866      |13350     |2006-08-18 00:00:00.000|
|866      |20201     |2006-08-15 00:00:00.000|
|867      |20201     |2006-08-14 00:00:00.000|
|869      |19893     |2006-08-15 00:00:00.000|
|870      |11927     |2006-08-24 00:00:00.000|
|870      |15160     |2006-08-23 00:00:00.000|
|870      |17151     |2006-08-18 00:00:00.000|
|870      |18749     |2006-08-25 00:00:00.000|
|871      |13350     |2006-08-15 00:00:00.000|
|871      |21163     |2006-08-15 00:00:00.000|
|871      |21717     |2006-08-17 00:00:00.000|
|873      |23381     |2006-08-15 00:00:00.000|
+---------+----------+-----------------------+

执行 INSERT、UPDATE 和 DELETE 操作

IF OBJECT_ID (N'dbo.Departments', N'U') IS NOT NULL 
    DROP TABLE dbo.Departments;
GO
CREATE TABLE dbo.Departments (DeptID tinyint NOT NULL PRIMARY KEY, DeptName nvarchar(30), 
    Manager nvarchar(50));
GO
INSERT INTO dbo.Departments 
    VALUES 
            (1, 'Human Resources', 'Margheim'),
            (2, 'Sales', 'Byham'), 
            (3, 'Finance', 'Gill'),
            (4, 'Purchasing', 'Barber'),
            (5, 'Manufacturing', 'Brewer');


IF OBJECT_ID (N'dbo.Departments_delta', N'U') IS NOT NULL 
    DROP TABLE dbo.Departments_delta;
GO
CREATE TABLE dbo.Departments_delta (DeptID tinyint NOT NULL PRIMARY KEY, DeptName nvarchar(30), 
    Manager nvarchar(50));
GO
INSERT INTO dbo.Departments_delta VALUES 
    (1, 'Human Resources', 'Margheim'), 
    (2, 'Sales', 'Erickson'),
    (3 , 'Accounting', 'Varkey'),
    (4, 'Purchasing', 'Barber'), 
    (6, 'Production', 'Jones'), 
    (7, 'Customer Relations', 'Smith');
GO

执行以下SQL:

MERGE dbo.Departments AS d
USING dbo.Departments_delta AS dd
ON (d.DeptID = dd.DeptID)
WHEN MATCHED AND d.Manager <> dd.Manager OR d.DeptName <> dd.DeptName
    THEN UPDATE SET d.Manager = dd.Manager, d.DeptName = dd.DeptName
WHEN NOT MATCHED THEN
    INSERT (DeptID, DeptName, Manager)
        VALUES (dd.DeptID, dd.DeptName, dd.Manager)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
OUTPUT $action, 
       inserted.DeptID AS SourceDeptID, inserted.DeptName AS SourceDeptName, 
       inserted.Manager AS SourceManager, 
       deleted.DeptID AS TargetDeptID, deleted.DeptName AS TargetDeptName, 
       deleted.Manager AS TargetManager;

查看结果:

+------+------------------+--------+
|DeptID|DeptName          |Manager |
+------+------------------+--------+
|1     |Human Resources   |Margheim|
|2     |Sales             |Erickson|
|3     |Accounting        |Varkey  |
|4     |Purchasing        |Barber  |
|6     |Production        |Jones   |
|7     |Customer Relations|Smith   |
+------+------------------+--------+

参考资料

MERGE (Transact-SQL)

使用 MERGE 插入、更新和删除数据

公众号推荐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/659557.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Vue】插值表达式 {{ }}

一、引入 插值表达式是一种Vue的模板语法 我们可以用插值表达式渲染出Vue提供的数据 作用&#xff1a;利用表达式进行插值&#xff0c;渲染到页面中 表达式&#xff1a;是可以被求值的代码&#xff0c;JS引擎会将其计算出一个结果 以下的情况都是表达式&#xff1a; money…

网络安全-钓鱼篇-利用cs进行钓鱼

一、环境 自行搭建&#xff0c;kill&#xff0c;Windows10&#xff0c;cs 二、原理 如图所示 三、钓鱼演示 首先第一步&#xff1a;打开System Profiler-分析器功能 选择克隆www.baidu.com页面做钓鱼 之后我们通过包装域名&#xff0c;各种手段让攻击对象访问&#xff1a;h…

硬盘有EFI分区格式化不了,也删不了怎么办,不能读取磁盘

问题&#xff1a;EFI为系统引导分区表明这是一块系统盘&#xff0c;常规操作无法格式化也无法删除&#xff0c;也不能读取 解决&#xff1a; 1.管理员运行cmd 2.输入diskpart 3.输入list disk 查看系统磁盘&#xff0c;并找到你格式化不了的那块磁盘 4.select disk 编号 选择…

动手学操作系统(一、搭建实验环境)

动手学操作系统&#xff08;一、搭建实验环境&#xff09; 文章目录 动手学操作系统&#xff08;一、搭建实验环境&#xff09;1. 在VMware虚拟机中安装ubuntu20.042. 安装Bochs3. 启动计算机Reference &#x1f680; 环境配置 &#x1f680; 笔者的环境使用的是 ubuntu 20.04…

学习sam的过程

一、抓包 我平时都是用花瓶去抓包的&#xff0c;配置也很简单。就是下载软件&#xff0c;然后一步步安装。下载地址&#xff1a;Download a Free Trial of Charles • Charles Web Debugging Proxy 。然后配置手机代理 对于那些走http协议的app是可以的&#xff0c;https的还是…

Linux 系统编程笔记--基本概念(一)

操作系统&#xff1a; 管理计算机硬件和软件资源的计算机程序。 内核&#xff1a; 操作系统的核心&#xff0c;是应用程序连接硬件设备的桥梁。 CPU 可以在两种状态下运行:用户态和内核态&#xff0c;在用户态下运行时&#xff0c;CPU 只能访问用户空间的内存;在内核态运行时&…

kafka-守护启动

文章目录 1、kafka守护启动1.1、先启动zookeeper1.1.1、查看 zookeeper-server-start.sh 的地址1.1.2、查看 zookeeper.properties 的地址 1.2、查看 jps -l1.3、再启动kafka1.3.1、查看 kafka-server-start.sh 地址1.3.2、查看 server.properties 地址 1.4、再次查看 jps -l 1…

【数据分享】2017-2023年全球范围10米精度土地覆盖数据

土地覆盖数据是我们在各项研究中都非常常用的数据&#xff0c;土地覆盖数据的来源也有很多。之前我们分享过欧空局发布的2020年和2021年的10米分辨率的土地覆盖数据,也分享过我国首套1米分辨率的土地覆盖数据&#xff08;均可查看之前的文章获悉详情&#xff09;&#xff01; …

Kunpeng Pro测评使用报告

1. 概述 前段时间&#xff0c;收到两条CSDN的短信&#xff0c;邀请我参加Kunpeng Pro的测评活动。说起来&#xff0c;自己玩过的开发板已经不在少数&#xff0c;而自己作为半导体行业的从业者&#xff0c;手上开发过的芯片也有十几款&#xff0c;小到Arm Cortex-A53&#xff0…

老年人健康管理系统项目部署【linux】

老年人健康管理系统项目部署【linux】 前言版权推荐老年人健康管理系统项目部署购买阿里云服务器开发票连接开放端口 安装软件查看状态1更新yum源2安装jdk83安装mysql4上传Mysql数据5安装redis6安装kakfa7安装nginx8运行命令 命令汇总1更新yum源2Jdk8安装3Mysql安装4Mysql数据5…

SpringBoot——集成Spring Data JPA保存数据

目录 JPA 项目总结 新建一个SpringBoot项目 pom.xml application.properties配置文件 User实体类 UserRepository接口 SpringbootJpaApplicationTests测试类 测试 JPA 项目在运行过程中会产生很多业务数据&#xff0c;一般我们把数据保存起来的这个过程称为数据持久化。…

【C++】牛客——JZ38 字符串的排列

✨题目链接&#xff1a; JZ38 字符串的排列 ✨题目描述 输入一个长度为 n 字符串&#xff0c;打印出该字符串中字符的所有排列&#xff0c;你可以以任意顺序返回这个字符串数组。 例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。 数…

灯下黑”挖出国内知名安全平台某BUF的CSRF漏洞

漏洞复现&#xff1a; 漏洞点在删除文章的地方&#xff0c;首先为了测试先发布一篇文章 发布之后我们可以查看文章&#xff0c;注意url中的一串数字&#xff0c;就是这篇文章的id&#xff0c;如下如&#xff1a; 这里的文章id是“271825”&#xff0c;首先抓一下删除文章的数据…

转行嵌入式,需要自学多久?

那要看你的预期目标是什么。如果你只是为了找到一份工作&#xff0c;那么学习半年左右&#xff0c;掌握基本的开发技能&#xff0c;就可以找到一个初级岗位的工作&#xff0c;工资在 5K 到 10K 左右。不过&#xff0c;运气好的话可能时间更短&#xff0c;运气差的话可能需要一年…

基于PHP+MySQL组合开发的微信小程序分销商城源码系统 分销商城+积分商城+多商户 功能强大 带完整的安装代码包以及搭建教程

系统概述 在当今数字化商业时代&#xff0c;拥有一个强大而多功能的分销商城系统对于企业的发展至关重要。本文将重点介绍基于 PHPMySQL 组合开发的微信小程序分销商城源码系统&#xff0c;它融合了分销商城、积分商城和多商户等功能&#xff0c;不仅功能强大&#xff0c;还提…

小苯的排列构造,小苯的01背包(easy),小苯的01背包(hard)

小苯的排列构造 题目描述 运行代码 #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000050 int i,j,k,n,m,t,a[N],b[N],f[N],l[N]; bool v[N]; int main(){cin>>n;for(i1;i<n;i)cin>>a[i];v[0]1;for(i1;i<n;i){if(a[…

Windows下PostgreSQL数据库的备份与恢复

文章目录 一、备份1.找到PostgreSQL的安装目录下的"bin"目录2.在windows的命令窗口里&#xff0c;使用pg_dump进行备份1.打开命令窗口2.使用pg_dump将数据库备份下来 二、恢复1.找到PostgreSQL的安装目录下的"bin"目录2.在windows的命令窗口里&#xff0c;…

GD32F103系列单片机片上FLASH和ARM介绍

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布&#xff1a; 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转&#xff1a; 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…

移动硬盘未格式化数据恢复及预防策略

随着数字化时代的到来&#xff0c;移动硬盘作为数据存储的重要载体&#xff0c;被广泛应用于个人和企业中。然而&#xff0c;当移动硬盘遭遇“未格式化”的困境时&#xff0c;其中的数据便岌岌可危。本文将深入探讨移动硬盘未格式化的现象、原因、数据恢复方案以及预防措施&…

男士内裤哪种款式舒服?五条实用技巧让你轻松挑选

对于很多男生来说&#xff0c;依然很难挑到真正舒适的内裤。比如卡臀卡裆&#xff0c;走路时不时还得提拉一下&#xff0c;真的很尴尬。又紧又闷的内裤&#xff01;尤其是炎热的夏天&#xff0c;黏糊糊的贼难受&#xff01;到底有没有一款舒适透气男士内裤呢&#xff1f;那今天…