一条merge实现插入或更新

    Oracle 提供了 merge 语句可以通过一条 SQL 语句实现 insert 和 update 操作,但是如果使用不当,会遇到奇怪的问题。为什么明明没有数据,确没有执行 insert 操作,也没有执行 update 操作,好像 merge 语句不工作了。

1. 建表语句

CREATE TABLE TEST_TB1 (
	ID NUMBER(10,0),
	NAME VARCHAR2(100) NOT NULL,
	REMARK VARCHAR2(100),
	CONSTRAINT PK_TEST_TB1 PRIMARY KEY (ID)
);

2. merge 语句1

使用 SQLPlus 连接数据库:

instantclient_11_2/sqlplus.exe test_db/test_db@192.1.1.166:1521/tsdb

# 查询表数据
SQL> select * from TEST_TB1;

no rows selected

merge语句1: 

MERGE INTO TEST_TB1 a
    USING (SELECT zz.id FROM TEST_TB1 zz WHERE zz.id = 1) b
ON (a.id = b.id)
WHEN MATCHED THEN
    UPDATE SET NAME = 'Tom', REMARK = 'Tom update'
WHEN NOT MATCHED THEN 
    INSERT (a.id, a.name, a.remark)
	values (1, 'Tom', 'Tom insert');

执行结果:

0 rows merged.

很奇怪为什么没有执行 insert 操作。

3. 修复后的 merge 语句2

MERGE INTO TEST_TB1 a
    USING ( SELECT ( SELECT zz.ROWID AS rid FROM TEST_TB1 zz WHERE zz.id = 1) AS rid FROM dual) b
ON (a.ROWID = b.rid)
WHEN MATCHED THEN
    UPDATE SET NAME = 'Tom', REMARK = 'Tom update'
WHEN NOT MATCHED THEN
    INSERT (a.id, a.name, a.remark)
  values (1, 'Tom', 'Tom insert');

执行结果:

1 row merged. 

看看数据吧


SQL> select * from TEST_TB1;

        ID
----------
NAME
--------------------------------------------------------------------------------
REMARK
--------------------------------------------------------------------------------
         1
Tom
Tom update

执行成功了!

4. 两个 merge 语句 source table 对比 

merge 语句1 中 source table 的查询结果:

SQL> SELECT zz.id FROM TEST_TB1 zz WHERE zz.id = 2;

no rows selected

 merge 语句2中 source table 的查询结果:

SQL> SELECT ( SELECT zz.ROWID AS rid FROM TEST_TB1 zz WHERE zz.id = 2) AS rid FROM dual;

RID
------------------

5. 并发问题

虽然解决了 merge 语句问题,还是要注意 merge 语句并发问题。

使用 contiperf 进行并发测试:

    <dependency>
      <groupId>org.databene</groupId>
      <artifactId>contiperf</artifactId>
      <version>2.3.4</version>
      <scope>test</scope>
    </dependency>

merge 语句配置

    <update id="insertByMerge" parameterType="com.ybst.note.db.comm.entity.TestTb1">
        MERGE INTO TEST_TB1 a
		USING ( SELECT ( SELECT zz.ROWID AS rid FROM TEST_TB1 zz WHERE zz.id = #{id}) AS rid
	            FROM dual) b
	    ON (a.ROWID = b.rid)
	    WHEN MATCHED THEN
        UPDATE
        SET
        <if test="name != null and name != ''">
            NAME =#{name},
        </if>
        <if test="remark != null and remark != ''">
            REMARK =#{remark}
        </if>
        WHEN NOT MATCHED THEN INSERT (a.id, a.name, a.remark)
        values (#{id}, #{name}, #{remark})
    </update>

测试类:

import com.ybst.note.db.comm.dao.testpj.ITestTb1Dao;
import com.ybst.note.db.comm.entity.testpj.TestTb1;
import com.ybst.note.testpj.test.annotation.TargetDataSource;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestTb1Test {
    private static Logger logger = LoggerFactory.getLogger(TestTb1Test.class);

    @Rule
    public ContiPerfRule contiPerfRule = new ContiPerfRule();

    @Autowired
    private volatile ITestTb1Dao testTb1Dao;

    private int timesRecords = 0;

    @TargetDataSource(name = "testpj")
    @PerfTest(invocations = 10*10, threads = 10)  // 10个线程, 执行 100次, 每个执行10次
    @Test
    public void testInsertByMerge() {
        TestTb1 testTb1 = new TestTb1();
        testTb1.setId(1L);
        testTb1.setName("zhangsan");
        testTb1.setRemark("zhangsan info");

        testTb1Dao.insertByMerge(testTb1);

        timesRecords++;
        logger.info("执行第 {} 次", timesRecords);
    }

    @Before
    public void setup() {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id", 1L);
        testTb1Dao.deleteByMap(paramMap);
    }
}

执行单元测试后,抛出的异常信息:

SQL> SELECT zz.id FROM TEST_TB1 zz WHERE zz.id = 2;

no rows selected

2022-07-29 15:12:26.874  INFO 248984 --- [       Thread-8] c.y.n.testpj.test.dao.TestTb1Test    : 执行第 74 次
samples: 74
max:     91334
average: 8659.378378378378
median:  19

org.databene.contiperf.PerfTestExecutionError: org.springframework.dao.DuplicateKeyException: 
### Error updating database.  Cause: java.sql.SQLException: ORA-00001: 违反唯一约束条件 (test_db.PK_TEST_TB1)

6. 小结

  • 使用 merge 时 source table 无数据,会导致 merge 无法正常工作;
  • merge 并发问题也需要注意。
<think>首先,用户的问题是:“我想使用一条语句实现查询与更新操作 请问SQL 一条语句实现查询并更新数据”。这涉及到SQL中如何用一条语句同时执行查询和更新操作。 从系统级指令来看,我需要: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用:引用[1]、[2]、[3]是关于SQL查询条件、MERGE语句和批量更新的。 用户提供的引用: - 引用[1]:关于WHERE子句指定查询条件。 - 引用[2]:关于MERGE INTO语句,用于插入更新记录(UPSERT操作)。 - 引用[3]:关于使用UPDATE语句进行批量更新。 用户的问题是用一条SQL语句实现查询和更新。在SQL中,查询通常使用SELECT,更新使用UPDATE、INSERT、DELETE等。但用户想在一个语句中完成查询和更新。 常见的解决方案: 1. **使用子查询在UPDATE语句中**:例如,在UPDATE的SET子句中使用子查询来获取值。 2. **使用MERGE语句(在支持它的数据库如Oracle中)**:MERGE可以基于查询结果进行更新插入。 3. **使用CTE(Common Table Expressions)临时表**:但这可能不是一条语句。 4. **在SELECT语句中执行更新?**:这通常不可能,因为SELECT是只读的;但有些数据库有扩展。 从引用[2]中,MERGE INTO语句可以用于基于查询结果进行插入更新。引用[3]展示了在UPDATE中使用子查询进行批量更新。 用户的需求是“查询并更新数据”,可能意味着: - 基于查询结果更新数据。 - 者,在一个操作中执行查询逻辑和更新。 在SQL中,标准的UPDATE语句可以包含子查询来引用其他表的数据,从而实现基于查询的更新。类似地,INSERT ... SELECT 可以基于查询插入数据。 例如: - **UPDATE with subquery**:`UPDATE table1 SET column1 = (SELECT column2 FROM table2 WHERE condition) WHERE condition;` - **MERGE**:用于更复杂的操作,如UPSERT。 由于用户提到了“查询并更新”,我认为MERGE语句UPDATE with subquery是合适的。 回答结构: 1. **解释需求**:简要重述用户问题。 2. **提供解决方案**:分步骤解释。 3. **给出示例**:使用代码块。 4. **注意事项**:提到数据库兼容性等。 5. **生成相关问题**:在最后添加相关问题。 生成相关问题:指令要求“生成相关问题”,我需要基于内容创建几个相关问题。 使用中文回答。 现在,构建回答: - **开头**:解释SQL中一条语句实现查询和更新是可能的,常用方法。 - **方法1:UPDATE with subquery**:使用子查询在UPDATE中。 - **方法2:MERGE语句**:如果数据库支持。 - **示例**:基于引用提供例子。 - **结尾**:生成相关问题。 引用标识:在引用内容末尾添加[^1]、[^2]等。引用[1]、[2]、[3]是用户提供的,所以我需要在回答中自然地引用它们。 例如: - 当提到查询条件时,引用[1]。 - 当提到MERGE时,引用[2]。 - 当提到批量更新时,引用[3]。 用户问题焦点是“一条语句实现查询并更新”,所以引用[2]的MERGE是最直接的。 最终回答: - 介绍:是的,SQL中可以用一条语句实现查询和更新操作。 - 方法1:使用UPDATE语句结合子查询。 - 方法2:使用MERGE语句(如Oracle)。 - 代码示例。 - 注意事项:数据库支持。 - 相关问题。</think>### SQL 一条语句实现查询并更新数据 是的,在 SQL 中,您可以使用一条语句同时实现查询和更新操作。这通常通过结合子查询专用语法(如 `MERGE`)来实现。核心思路是:在更新操作中嵌入查询逻辑,基于查询结果动态更新数据。下面我将逐步解释两种常用方法,并提供详细示例。需要注意的是,具体语法可能因数据库系统而异(如 Oracle、MySQL、SQL Server 等),以下示例基于通用 SQL 标准,并参考了您提供的引用内容。 --- ### 方法 1: 使用 UPDATE 语句结合子查询(适用于简单查询更新) 这种方法在 `UPDATE` 语句的 `SET` 子句中使用子查询来获取查询结果,并直接更新目标表。它适用于基于其他表的数据更新当前表。 **步骤分解:** 1. **定义更新目标**:指定要更新的表。 2. **嵌入子查询**:在 `SET` 子句中,使用子查询(`SELECT` 语句)获取需要的数据。 3. **添加条件**:通过 `WHERE` 子句指定查询条件,确保只更新符合条件的记录[^1]。 **示例场景:** 假设有两个表: - `employees` 表:包含 `id`, `salary`。 - `salary_updates` 表:包含 `id`, `new_salary`。 您需要根据 `salary_updates` 表中的新薪资,更新 `employees` 表中对应员工的薪资。 **SQL 语句:** ```sql UPDATE employees e SET e.salary = ( SELECT s.new_salary FROM salary_updates s WHERE s.id = e.id -- 查询条件:基于 id 匹配 ) WHERE EXISTS ( SELECT 1 FROM salary_updates s WHERE s.id = e.id -- 只更新存在匹配记录的员工 ); ``` **解释:** - 子查询 `(SELECT s.new_salary FROM salary_updates s WHERE s.id = e.id)` 实现了查询操作:获取每个员工的 new_salary。 - `SET e.salary = ...` 基于查询结果更新 salary。 - `WHERE EXISTS ...` 确保只更新在 `salary_updates` 中有匹配记录的员工,避免空值错误。 - 这相当于在一条语句中完成了查询(从 salary_updates 获取数据)和更新(修改 employees 表)[^3]。 **注意事项:** - 如果子查询返回多行,会报错。确保子查询为每个记录返回唯一值(例如,通过唯一键约束)。 - 在 MySQL 中,语法类似;在 SQL Server 中,可能需要使用 `FROM` 子句显式连接表。 --- ### 方法 2: 使用 MERGE 语句(适用于插入更新,UPSERT 操作) `MERGE` 语句(也称为 UPSERT)更强大,它基于查询结果决定是更新现有记录还是插入新记录。这在需要“存在则更新,不存在则插入”的场景中特别有用[^2]。 **步骤分解:** 1. **指定目标表**:使用 `MERGE INTO` 定义要更新的表。 2. **定义源查询**:通过 `USING` 子句指定一个查询(如子查询临时结果集),作为数据来源。 3. **设置匹配条件**:在 `ON` 子句中定义如何匹配源和目标记录。 4. **执行更新插入**:使用 `WHEN MATCHED THEN UPDATE` 更新匹配记录, `WHEN NOT MATCHED THEN INSERT` 插入新记录。 **示例场景:** 参考您的引用[2],假设表 `objectTable` 包含 `ID` 和 `Name`。您需要:如果 ID="123456" 存在,则更新 Name;如果不存在,则插入新记录。 **SQL 语句:** ```sql MERGE INTO objectTable t USING ( SELECT '123456' AS ID, '张三' AS Name -- 源查询:这里硬编码值,但可以是动态查询 ) s ON (s.ID = t.ID) -- 匹配条件:基于 ID WHEN MATCHED THEN UPDATE SET t.Name = s.Name -- 更新操作 WHEN NOT MATCHED THEN INSERT (ID, Name) VALUES (s.ID, s.Name); -- 插入操作 ``` **解释:** - 子查询 `USING (SELECT ...)` 实现了查询操作:定义了源数据(这里硬编码,但可以是复杂查询)。 - `ON (s.ID = t.ID)` 指定匹配逻辑。 - 整个语句在一条命令中完成了查询(检查 ID 是否存在)和更新/插入操作[^2]。 **注意事项:** - `MERGE` 在 Oracle 和 SQL Server 中广泛支持,但 MySQL 不支持(需用 `INSERT ... ON DUPLICATE KEY UPDATE` 替代)。 - 确保 `ON` 条件正确,否则可能导致意外更新插入。 --- ### 关键注意事项 1. **数据库兼容性**: - `UPDATE with subquery` 几乎在所有 SQL 数据库(如 MySQL、PostgreSQL、SQL Server)中可用。 - `MERGE` 主要在 Oracle、SQL Server 和 PostgreSQL 中支持;MySQL 需用替代语法。 2. **性能优化**:对于大数据量,在子查询 `USING` 子句中添加索引以提高效率[^3]。 3. **事务安全**:这些操作是原子的(成功完全回滚),但建议在测试环境验证。 4. **错误处理**:子查询返回多行时会报错,需确保数据唯一性。 通过以上方法,您可以用一条 SQL 语句高效实现查询和更新。如果您提供具体的数据库系统(如 MySQL Oracle),我可以给出更针对性的示例。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值