仅当表中不存在该值时才更新 SQL 列

Posted

技术标签:

【中文标题】仅当表中不存在该值时才更新 SQL 列【英文标题】:Update SQL column only if the value doesn't already exist in the table 【发布时间】:2016-08-26 16:10:06 【问题描述】:

我使用的是 SQL Server 2012,并且我的表的列有逗号分隔值(不好的做法,但现在不可避免)。每次用户尝试添加申请者时,我如何检查其是否已经存在,并且只有在它不存在时才更新(追加)。

JobID        Applicants
----------------------------
J001         a001,a002,a003

例如。如果我尝试为 J001 一次性添加申请人 a003、a004,则只应附加 a004。

我相信合并在这里会有所帮助,但我无法弄清楚如何。

我尝试通过转换为 XML 来拆分传入的值(通过 USP 参数),但不知道现在在存储过程中要做什么。

【问题讨论】:

您能否发布代码以显示“到目前为止”您已经走了多远。它真的会帮助人们想出答案。 非常感谢您的回复,我能够收集点点滴滴并获得我正在寻找的解决方案。创建拆分功能有很大帮助。目前我正在尝试在系统中进行更改以避免连续使用逗号分隔值,可能需要一个小版本,但值得付出努力。 【参考方案1】:

这是您问题的答案。

按照以下步骤操作。

1 .创建以下函数。

CREATE FUNCTION SplitString
(    
      @Input NVARCHAR(MAX),
      @Character CHAR(1)
)
RETURNS @Output TABLE (
      Item NVARCHAR(1000)
)
AS
BEGIN
      DECLARE @StartIndex INT, @EndIndex INT

      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO

    创建样本输入表#T

    SELECT 'J001' as JobID, CONVERT(VARCHAR(50),'a001,a002,a003') as Applicants INTO #T
    

    下面的脚本将执行您的更新。

           ;with cte_1
           as
         (SELECT item  FROM #T
            cross apply (select item from dbo.SplitString(Applicants, ','))X
          WHERE JobID='J001')
         UPDATE t
    
         set t.Applicants= t.Applicants+','+ STUFF(( SELECT ','+b.Item  
    
                      FROM dbo.SplitString('a001,a002,a004,a005', ',') b
                        LEFT JOIN  cte_1 a ON b.Item=a.Item 
                        WHERE a.Item IS NULL  FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, '')
        FROM #T t WHERE t.JobID='J001'
    

【讨论】:

【参考方案2】:

您已经知道这是一个非常糟糕的数据结构,所以我们不必深入探讨。当你被这样的结构卡住时,你可以使用字符串操作,但它们并不是特别有效;

我的建议是一次添加一个:

update t
    set Applicants = (case when Applicants is not null
                           then Applicants + ',' + cast(@ApplicantId as varchar(255)
                           else cast(@ApplicantId)
                      end)
    where JobId = @JobId and
          ',' + Applicants + ',' not like '%,' + cast(@ApplicantId as varchar(255)) + '%,';

没有必要通过尝试将一个逗号分隔列表用于更新值和存储值来使您的问题更加复杂。

而且,让我再说一遍,以这种方式存储值在 SQL 中是一个非常非常糟糕的主意,并且只有在其他人(不知道更好的情况)强迫这种情况时才应该支持。

【讨论】:

【参考方案3】:

好的,所以你知道它不好 Gordon 已经指出它不好,他给了你一种方法来一次更新 1 行但是如果你正在处理一个真正应该代表表格的分隔列表,我的猜测是您不想一次只更新 1 条记录...

首先,如果你有这样的数据,首先要给自己一个好的表值函数来拆分分隔字符串!如果你想要一个多字符分隔符,这是我刚刚写的不太灵活的一个,但给了你和想法。

CREATE FUNCTION dbo.SplitDelimitedString
(
    @DelimitedString VARCHAR(8000)
    ,@Delimitor CHAR(1)
)
RETURNS 
@ReturnTable TABLE (Id INT,String VARCHAR(8000))
AS
BEGIN
    IF CHARINDEX(@Delimitor,@DelimitedString) = 0
    BEGIN
       INSERT INTO @ReturnTable (Id,String)
       VALUES (1,@DelimitedString)

       RETURN
    END

    ;WITH cteStrings AS (
       SELECT
          1 AS Id
          ,LEFT(@DelimitedString,CHARINDEX(@Delimitor,@DelimitedString) - 1) as String
          ,RIGHT(@DelimitedString,LEN(@DelimitedString) - CHARINDEX(@Delimitor,@DelimitedString))
             + CASE WHEN RIGHT(@DelimitedString,1) <> @Delimitor THEN @Delimitor ELSE '' END as RemainingStrings
       UNION ALL
       SELECT
          Id + 1 as Id
          ,LEFT(RemainingStrings,CHARINDEX(@Delimitor,RemainingStrings)-1) as String
          ,RIGHT(RemainingStrings,LEN(RemainingStrings) - CHARINDEX(@Delimitor,RemainingStrings)) as RemainingStrings
       FROM
          cteStrings
       WHERE
          CHARINDEX(@Delimitor,RemainingStrings) > 0
    )

    INSERT INTO @ReturnTable (Id, String)
    SELECT
       Id
       ,String
    FROM
       cteStrings

    RETURN 
END
GO

接下来请确保您知道如何重新连接您拆分的字符串,这里有很多示例和技术,我将使用 STUFF 和 FOR XML PATH 技术。拆分要更新的字符串后,您也可以使用相同的函数通过交叉应用拆分表,或者使用 Gordon 的 LIKE 技术。

这是一个使用戈登技术的例子

DECLARE @TableA AS TABLE (JobId CHAR(4), Applicants VARCHAR(100))
INSERT INTO @TableA (JobId, Applicants) VALUES ('J001','a001,a002,a003')
DECLARE @AddApplicants VARCHAR(100) = 'a003,a004,a005,a005'
DECLARE @JobId CHAR(5) = 'J001'

SET @AddApplicants = STUFF(
       (SELECT DISTINCT ',' + String
       FROM
          dbo.SplitDelimitedString(@AddApplicants,',') a
          LEFT JOIN @TableA t
          ON t.JobId = @JobId
          AND t.Applicants LIKE '%,' + a.String + '%'
       WHERE
          t.JobId IS NULL
       FOR XML PATH(''))
    ,1,1,'')

UPDATE @TableA
    SET Applicants = CASE WHEN LEN(Applicants) > 0 THEN Applicants + ',' ELSE '' END + @AddApplicants
WHERE
    JobId = @JobId

SELECT *
FROM
    @TableA

如果您更愿意使用 CROSS APPLY,您可以这样做:

SET @AddApplicants = STUFF(
       (SELECT DISTINCT ',' + a.String
       FROM
          dbo.SplitDelimitedString(@AddApplicants,',') a
          LEFT JOIN (
             SELECT DISTINCT a.String
             FROM
                @TableA t
                CROSS APPLY dbo.SplitDelimitedString(t.Applicants,',') a
             WHERE
                t.JobId = @JobId
          ) t
          ON a.String = t.String
       WHERE
          t.String IS NULL
       FOR XML PATH(''))
    ,1,1,'')

【讨论】:

【参考方案4】:

如何拆分 a003,a004 并用逗号搜索每个字符串(例如'a003'||',')到带有条件的申请人字段中?

希望对你有用。

【讨论】:

更多的是评论而不是解决方案。这种方法是可行的,但如何实现呢?您应该改进您的答案以对此进行扩展。

以上是关于仅当表中不存在该值时才更新 SQL 列的主要内容,如果未能解决你的问题,请参考以下文章

仅当表存在时才删除具有名称的表:<table_name>_(sysdate-1)

仅当连接明确时才更新行

仅当记录不存在时才将 SQL 插入表中[重复]

仅当两个表中都存在员工时才从临时表中更新员工 ID

Access97:如果不存在则插入

MongoDB + Mongoose:仅当给定键不存在或具有虚假值时才设置