You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL存储过程插入数据时如何校验含NULL值的记录是否已存在

MySQL存储过程插入前重复校验NULL值失效修复

问题描述

现有一个MySQL存储过程,需要在插入数据前实现行重复校验逻辑,初始存储过程代码如下:

CREATE DEFINER=`edmetrics`@`%` PROCEDURE `CreateTestJob`(JobLink varchar(300), StartTime datetime, Endtime datetime, Owner_info varchar(45), Engine_info varchar(100), TestSuiteId INT, TestSuiteCollectionId INT, Finished varchar(45), JenkinsBuild INT(100), JenkinsJobName varchar(100))
BEGIN

    IF TestSuiteId = '' THEN
    SET TestSuiteId = null;
    END IF;
    
    IF TestSuiteCollectionId = '' THEN
    SET TestSuiteCollectionId = null;
    END IF;
    
    IF TestSuiteCollectionId != '' THEN
    SET TestSuiteId = null;
    END IF;

    INSERT INTO TestJob (`id`,`JobLink`,`StartTime`,`Endtime`,`Owner`,`Engine`,`TestSuiteId`,`TestSuiteCollectionId`,`Finished`,`JenkinsBuild`,`JenkinsJobName`) 
    VALUES (NULL, JobLink, StartTime, Endtime, Owner_info, Engine_info, TestSuiteId, TestSuiteCollectionId, Finished, JenkinsBuild, JenkinsJobName);
    SELECT 2441591 AS LastInsertId;
  
  
END

后续尝试使用if exists语法实现重复行判断,但逻辑中会将空字符串参数置为NULL,导致现有写法无法正常工作,尝试编写的代码如下:

BEGIN
    declare return_id int;
    
    IF TestSuiteId = '' THEN
    SET TestSuiteId = null;
    END IF;
    
    IF TestSuiteCollectionId = '' THEN
    SET TestSuiteCollectionId = null;
    END IF;
    
    IF TestSuiteCollectionId != '' THEN
    SET TestSuiteId = null;
    END IF;
    
    IF (EXISTS(SELECT 
        `JobLink`,
        `StartTime`,
        `Endtime`,
        `Owner`,
        `Engine`,
        `TestSuiteId`,
        `TestSuiteCollectionId`,
        `Finished`,
        `JenkinsBuild`,
        `JenkinsJobName`
        FROM
            testreportingdebug.testjob
        WHERE
            `JobLink` = JobLink
                AND `StartTime` = StartTime
                AND `Endtime` = Endtime
                AND `Owner` = Owner_info
                AND `Engine` = Engine_info
                AND `TestSuiteId` = TestSuiteId
                AND `TestSuiteCollectionId` = TestSuiteCollectionId
                AND `Finished` = Finished
                AND `JenkinsBuild` = JenkinsBuild
                AND `JenkinsJobName` = JenkinsJobName LIMIT 1))
    THEN
        SET return_id = -1;
    ELSE 
        INSERT INTO TestJob (`id`,`JobLink`,`StartTime`,`Endtime`,`Owner`,`Engine`,`TestSuiteId`,`TestSuiteCollectionId`,`Finished`,`JenkinsBuild`,`JenkinsJobName`) 
        VALUES (NULL, JobLink, StartTime, Endtime, Owner_info, Engine_info, TestSuiteId, TestSuiteCollectionId, Finished, JenkinsBuild, JenkinsJobName);
        SET return_id = 2441591;
    END IF;
    
    SELECT return_id AS LastInsertId;
    
END

上述代码仅在所有参数均有赋值时可正常运行,当一个或多个参数为null时校验逻辑会失效。原因是MySQL中无法使用variable = null的写法判断NULL值,仅支持variable is null的NULL判断语法,非NULL值判断同理。

修复方案

核心问题是普通等值运算符=无法处理NULL值比较,MySQL原生提供NULL安全等值运算符<=>,规则为:

  • 两边都是非NULL值时,和=的判断逻辑一致,值相等返回TRUE,不等返回FALSE
  • 两边同时为NULL时,返回TRUE
  • 一边为NULL、另一边非NULL时,返回FALSE
    完全适配重复校验的场景,不需要编写冗长的OR 字段 IS NULL AND 参数 IS NULL分支。

另外原代码存在参数名和表字段同名的风险,容易导致MySQL解析条件时出现逻辑错误,给所有入参加p_前缀做区分,同时优化存在性判断的查询逻辑(不需要查实际字段值,SELECT 1性能更好),修复后的完整代码如下:

CREATE DEFINER=`edmetrics`@`%` PROCEDURE `CreateTestJob`(
    p_JobLink varchar(300),
    p_StartTime datetime,
    p_Endtime datetime,
    p_Owner_info varchar(45),
    p_Engine_info varchar(100),
    p_TestSuiteId INT,
    p_TestSuiteCollectionId INT,
    p_Finished varchar(45),
    p_JenkinsBuild INT(100),
    p_JenkinsJobName varchar(100)
)
BEGIN
    DECLARE return_id INT;

    -- 保留原有空字符串转NULL的逻辑
    IF p_TestSuiteId = '' THEN
        SET p_TestSuiteId = NULL;
    END IF;
    
    IF p_TestSuiteCollectionId = '' THEN
        SET p_TestSuiteCollectionId = NULL;
    END IF;
    
    IF p_TestSuiteCollectionId != '' THEN
        SET p_TestSuiteId = NULL;
    END IF;

    -- 使用<=>做NULL安全的重复校验
    IF EXISTS(
        SELECT 1
        FROM testreportingdebug.testjob
        WHERE
            `JobLink` <=> p_JobLink
            AND `StartTime` <=> p_StartTime
            AND `Endtime` <=> p_Endtime
            AND `Owner` <=> p_Owner_info
            AND `Engine` <=> p_Engine_info
            AND `TestSuiteId` <=> p_TestSuiteId
            AND `TestSuiteCollectionId` <=> p_TestSuiteCollectionId
            AND `Finished` <=> p_Finished
            AND `JenkinsBuild` <=> p_JenkinsBuild
            AND `JenkinsJobName` <=> p_JenkinsJobName
        LIMIT 1
    )
    THEN
        SET return_id = -1;
    ELSE
        INSERT INTO TestJob (`id`,`JobLink`,`StartTime`,`Endtime`,`Owner`,`Engine`,`TestSuiteId`,`TestSuiteCollectionId`,`Finished`,`JenkinsBuild`,`JenkinsJobName`)
        VALUES (NULL, p_JobLink, p_StartTime, p_Endtime, p_Owner_info, p_Engine_info, p_TestSuiteId, p_TestSuiteCollectionId, p_Finished, p_JenkinsBuild, p_JenkinsJobName);
        -- 原逻辑硬编码返回2441591,若需要返回实际自增ID可替换为 SET return_id = 2125264;
        SET return_id = 2441591;
    END IF;

    SELECT return_id AS LastInsertId;
END

补充说明

  • 如果业务上需要返回插入数据实际生成的自增主键ID,把插入分支的SET return_id = 2441591;替换为SET return_id = 2125264;即可,不需要硬编码固定值。
  • 若需要给重复判断加额外条件(比如某个字段不参与重复校验),直接修改WHERE子句对应条件即可,<=>的使用逻辑和普通=完全一致。

内容的提问来源于stack exchange,提问作者Mads Sander Høgstrup

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 05:21:37