【问题标题】:How to Insert/Update if record not exist / exists with SQL Server 2008?如果记录不存在/存在 SQL Server 2008,如何插入/更新?
【发布时间】:2018-10-08 11:34:23
【问题描述】:

我想知道检查记录是否存在然后运行更新语句或检查记录是否不存在然后运行插入语句的最佳方法是什么?我需要if not exists 的原因是因为我在表中插入了帐户信息。在我们必须更新的情况下,我只是在寻找唯一的 ID。在其他情况下,对于插入,我必须确保表中不存在 emailusername。这是我的查询示例:

<cfset var isUser = structKeyExists(FORM, "frmSaveaccount_isuser") ? true : false>
<cfset var isStaff = structKeyExists(FORM, "frmSaveaccount_isstaff") ? true : false>

<cftransaction action="begin">
    <cftry>
         <cfquery name="saveAccount" datasource="#Application.dsn#">
            DECLARE @AccountID UNIQUEIDENTIFIER = CASE WHEN LEN('#FORM.frm_accountid#') <> 0 THEN <cfqueryparam cfsqltype="cf_sql_idstamp" value="#trim(FORM.frm_accountid)#"> ELSE NEWID() END;
            DECLARE @FirstName VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(FORM.frm_firstname)#">;
            DECLARE @LastName VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(FORM.frm_lastname)#">;
            DECLARE @Middle CHAR(1) = <cfqueryparam cfsqltype="cf_sql_char" maxlength="1" value="#FORM.frm_middle#" null="#!len(trim(FORM.frmSaveaccount_middle))#">;
            DECLARE @Email VARCHAR(80) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="80" value="#trim(FORM.frm_email)#">;
            <cfif isUser>
                DECLARE @IsUser BIT = <cfqueryparam cfsqltype="cf_sql_bit" maxlength="1" value="#trim(structKeyExists(FORM, 'frm_isuser')? 1:0)#">;
                DECLARE @ActiveUser BIT = <cfqueryparam cfsqltype="cf_sql_bit" maxlength="1" value="#trim(structKeyExists(FORM, 'frm_activeuser')? 1:0)#">;
                DECLARE @SystemAdmin BIT = <cfqueryparam cfsqltype="cf_sql_bit" maxlength="1" value="#trim(structKeyExists(FORM, 'frm_systemadmin')? 1:0)#">;
                DECLARE @UserName VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(FORM.frm_username)#">;
            </cfif>
            <cfif isStaff>
                DECLARE @IsStaff BIT = <cfqueryparam cfsqltype="cf_sql_bit" maxlength="1" value="#trim(structKeyExists(FORM, 'frm_isstaff')? 1:0)#">;
                DECLARE @ActiveStaff BIT = <cfqueryparam cfsqltype="cf_sql_bit" maxlength="1" value="#trim(structKeyExists(FORM, 'frm_activestaff')? 1:0)#">;
                DECLARE @Position VARCHAR(10) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="10" value="#trim(FORM.frm_positioncode)#" null="#!len(trim(FORM.frm_positioncode))#">;
            </cfif>
            DECLARE @ActionDate DATETIME = CURRENT_TIMESTAMP;
            DECLARE @ActionID UNIQUEIDENTIFIER = <cfqueryparam cfsqltype="cf_sql_idstamp" value="#AccountID#">;

            BEGIN TRAN
                IF EXISTS (SELECT AccountID FROM Accounts WITH (updlock,serializable) WHERE AccountID = @AccountID)
                BEGIN
                    UPDATE Accounts 
                    SET
                        FirstName = @FirstName,
                        LastName = @LastName,
                        Middle = @Middle,
                        Email = @Email,
                        <cfif isUser>
                            IsUser = @IsUser,
                            ActiveUser = @ActiveUser,
                            SystemAdmin = @SystemAdmin,
                            UserName = @UserName,
                        </cfif>
                        <cfif isStaff>
                            IsStaff = @IsStaff,
                            ActiveStaff = @ActiveStaff,
                            Position = @Position,
                        </cfif>
                        ActionDate = @ActionDate,
                        ActionID = @ActionID
                    WHERE AccountID = @AccountID
                    SELECT @AccountID AS RecID
                END
                ELSE
                BEGIN
                    IF NOT EXISTS(SELECT 1 FROM Accounts WHERE Email = @Email <cfif isUser> OR UserName = @UserName</cfif>)
                        INSERT INTO Accounts (
                            AccountID,FirstName,LastName,Middle,Email,
                        <cfif isUser>
                            IsUser,ActiveUser,SystemAdmin,UserName,
                        </cfif>
                        <cfif isStaff>
                            IsStaff,ActiveStaff,Position,
                        </cfif>
                            ActionDate,ActionID
                        ) VALUES (
                            @AccountID,@FirstName,@LastName,@Middle,@Email,
                        <cfif isUser>
                            @IsUser,@ActiveUser,@SystemAdmin,@UserName,
                        </cfif>
                        <cfif isStaff>
                            @IsStaff,@ActiveStaff,@Position,
                        </cfif>
                            @ActionDate,@ActionID
                        )
                        SELECT @AccountID AS RecID
                    END
                    COMMIT TRAN
                </cfquery>
                <cfcatch type="any">
                     <cftransaction action="rollback" />
                     <cfset var fnResults.status = "400">
                     <cfset var fnResults.message = "Error! Please contact your administrator.">
                </cfcatch>
             </cftry>
    </cftransaction>

我想知道这是否是更好的方法,然后拆分为两个单独的查询插入/更新?还有没有更好的方法来检查记录是否存在?

【问题讨论】:

标签: sql sql-server sql-server-2008 coldfusion exists


【解决方案1】:

MERGE 可能类似于

MERGE Accounts tgt
USING ( SELECT 
            AccountID = 42 
          , firstName = 'Ted'
          , userName = 'ted'
          , email = 'ted@logan.com'
) src (AccountID, firstName, userName, email)
  ON tgt.accountID = src.accountID
WHEN MATCHED 
THEN 
  UPDATE
  SET FirstName = src.firstName
WHEN NOT MATCHED
  /* Check if username or email is already used */
  AND (SELECT 1 FROM Accounts WHERE username = src.username OR email = src.email) IS NULL
THEN 
  INSERT ( accountID, firstName, email, userName )
  VALUES ( src.AccountID, src.firstName, src.email, src.username )
OUTPUT $action, inserted.AccountID
;

正如我上面所说,我不确定cfquery 是否可以正确解释MERGE 语句。你必须测试一下。您可能必须将其设为存储过程调用。

OUTPUT 应该返回它的操作类型(INSERT 或 UPDATE)和关联的 AccountID

编辑:我创建了一个 Fiddle 来演示您在这里尝试的一些不同用途。

https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=710ea9d801637c17c88f27cac165a8f5

虽然,老实说,我想得越多,我就越认为MERGE 更适合批量数据更新。上述方法有效,但它是单行。只测试请求的记录是否存在,然后根据需要测试INSERTUPDATE 可能更有效。 MERGE 可能是矫枉过正。

【讨论】:

  • 肖恩很好的例子。非常感谢。在我们运行插入语句之前,有没有办法检查电子邮件或用户名是否存在?这是我要检查的最后一块?
  • 刚刚看到您在下面关于验证 emailusername 在插入之前不存在的进一步评论。这将需要额外的检查。我会看看那个。你有我可以使用的示例数据和架构吗?
  • 您是否认为在函数顶部创建单独的 cfquery 是更好的方法,然后仅基于该查询结果运行插入或更新?还有一个选项可以检查表单值accountid 是否为空。在这种情况下,我们可以使用 IF EXISTS 运行 UPDATE 或使用 IF NOT EXISTS 运行 INSERT。那有意义吗?我不确定在这种情况下是否需要使用 MERGE。
  • ColdFusion 的优点之一是它可以接受任何 T-SQL 字符串。它会很高兴合并。至于运行单独的查询,小问题是,您在 CF 服务器和 SQL Server 上增加了负载。如果只对 SQL Server 进行,可以获得更好的可扩展性
  • "它将采用任何 T-SQL 字符串" 是的。数据库本身确定是否支持特定的 sql 命令。 CF 的工作只是转发 sql 字符串并接收结果。我怀疑肖恩暗示的是 cfquery 是否会正确处理 OUTPUT 的结果。我可以用 CF11 和 SqlServer 2008 确认它。
【解决方案2】:

在 2008 年之前,您使用的方法几乎就在那里。 SQL Server 中没有“Upsert”可以为您处理,因此您必须自己检查。

2008 年之前 - 通用

IF EXISTS(SELECT [PrimaryKey] FROM [MyTable] WHERE [PrimaryKey] = @PassedInID)
    BEGIN
        UPDATE [MyTable]
        SET [Field1] = @PassedInValue1,
            [Field2] = @PassedInValue2
        WHERE [PrimaryKey] = @PassedInID
    END
ELSE
    BEGIN
        INSERT INTO [MyTable] ([PrimaryKey], [Field1], [Field2])
        VALUES (@PassedInID, @PassedInValue1, @PassedInValue2)
    END

如果您要进行大量更新并多次调用,请传入主键或索引您传入的值。这将避免 SQL Server 加载表数据以了解是否插入更新需要。

但是,如果您多次调用它,最好将所有插入/更新的表和JOIN 在现有表上传递两次,然后执行一次INSERT 和一次UPDATE

2008 年及以后 - 特定 对于 2008 年及以后的版本,您可以使用 MERGE(感谢 @Shawn 指出它是旧的)

MERGE INTO [Accounts] AS target
USING (SELECT @AccountID) AS source ([AccountID])  
    ON (target.[Email] = source.Email AND target.[Username] = @Username)  
    WHEN MATCHED THEN  
        UPDATE SET [FirstName] = @FirstName
                , [LastName] = @LastName
                , [Middle] = @Middle
                , [Email] = @Email
                , [Username] = @Username
WHEN NOT MATCHED THEN  
    INSERT ([AccountID], [FirstName], [LastName], [Middle], [Email], [Username])  
    VALUES (@AccountID, @FirstName, @LastName, @Middle, @Email, @Username)  

一体机 如果必须同时查看邮箱和用户名,可以混用IF NOT EXISTSMERGE

IF NOT EXISTS(SELECT [PrimaryKey] FROM [MyTable] WHERE [Email] = @Email OR [Username] = @Username)
    BEGIN
        MERGE INTO [Accounts] AS target
        USING (SELECT @AccountID) AS source ([AccountID])  
            ON (target.[Email] = source.Email AND target.[Username] = @Username)  
            WHEN MATCHED THEN  
                UPDATE SET [FirstName] = @FirstName
                        , [LastName] = @LastName
                        , [Middle] = @Middle
                        , [Email] = @Email
                        , [Username] = @Username
        WHEN NOT MATCHED THEN  
            INSERT ([AccountID], [FirstName], [LastName], [Middle], [Email], [Username])  
            VALUES (@AccountID, @FirstName, @LastName, @Middle, @Email, @Username)
    END

【讨论】:

  • 插入语句前的IF NOT EXISTS 怎么样?我必须检查emailusername 是否已经存在。
  • 从 SQL 2008 开始,有一个 MERGE 语句专门用于 upsert。 docs.microsoft.com/en-us/sql/t-sql/statements/…
  • @Shawn 你能提供使用MERGE的例子吗?
  • @Shawn - 我没有意识到 MERGE 可以追溯到那么远。谢谢
  • 我明白了。我以前没有得到。您真的需要在 1 次操作中执行此操作吗?我建议在尝试MERGE 之前检查电子邮件和用户名。我已经更新了我的代码,但 Shawn 可能对你所有指定的朋友有更好的答案
【解决方案3】:

(对于 cmets 来说太长了...)

其他答案已经回答了主要问题。这篇文章是为了回答您之前关于存储过程如何简化事情的问题。

虽然使用 cfquery 是有效的,但我个人会改用存储过程。过程更擅长处理复杂的 sql,并且还可以更轻松地利用 sql NULL 和/或默认值来简化逻辑。

不要让一堆cfif/cfelse 语句散布在整个SQL 中,而是创建一个包含所有必需变量的存储过程。 Assign whatever default values you want 用于 可选 参数。 NULL 通常是一个不错的选择,因为它可以很容易地检测省略的参数并用ISNULL()COALESCE() 替换不同的值,但是.. 这一切都取决于您应用的业务逻辑。

存储过程签名

CREATE PROCEDURE [dbo].[YourProcedureNameHere]
    @AccountID UNIQUEIDENTIFIER
    , @FirstName VARCHAR(50)
    , @LastName VARCHAR(50)
    , @Middle CHAR(1)
    , @Email VARCHAR(80)
    , @IsUser BIT = NULL
    , @ActiveUser BIT = NULL
    , @SystemAdmin BIT = NULL
    , @UserName VARCHAR(50) = NULL 
    , @IsStaff BIT = NULL
    , @ActiveStaff BIT = NULL
    , @Position VARCHAR(10) = NULL 
AS 
BEGIN
      ... your sql logic ....
END
GO

然后从 CF 调用该过程,使用单个 cfif 有条件地为“用户”或“员工”传入适当的变量。无论哪组变量被省略(用户或员工设置),都将在存储过程中分配默认值。

关于 CF 代码的其他一些建议

  • 就像@Shawn 所说,不需要大多数三元运算符。像structKeyExists() 这样的函数已经返回一个布尔值。不需要做任何额外的事情,比如 trim() 等。将结果与 CF_SQL_BIT 列一起使用。它是自动转换的。

  • 事务用于多个语句。由于 UPDATE 和 INSERT 是原子的,因此事务语句实际上并没有做任何事情。至少没有默认事务级别。

  • 由于代码似乎包含在 cffunction 中,因此不要直接使用 form 范围。无论函数需要什么值,都应该使用arguments 范围声明并传递给函数。

可能还有其他方法可以简化事情,但这应该会给您一个良好的开端。

示例过程调用

<cfstoredproc procedure="YourProcedureNameHere" datasource="#yourDSN#">
    <cfprocparam type="in" dbvarname="@AccountID" cfsqltype="cf_sql_char" value="#arguments.frm_accountid#" ....>
    ... more params ...
    <cfprocparam type="in" dbvarname="@Email" cfsqltype="cf_sql_varchar" value="#arguments.frm_email#">

    <!--- User --->
    <cfif structKeyExists(ARGUMENTS, "frmSaveaccount_isuser")>
        <cfprocparam type="in" dbvarname="@IsUser" cfsqltype="cf_sql_bit" value="1">
        <cfprocparam type="in" dbvarname="@ActiveUser" cfsqltype="cf_sql_bit" value="#structKeyExists(arguments, 'frm_activeuser')#">
        ... more params ... 
    </cfif>

    <!--- Staff --->
    <cfif structKeyExists(ARGUMENTS, "frmSaveaccount_isstaff")>
        <cfprocparam type="in" dbvarname="@IsStaff" cfsqltype="cf_sql_bit" value="1">
        <cfprocparam type="in" dbvarname="@ActiveStaff" cfsqltype="cf_sql_bit" value="#structKeyExists(arguments, 'frm_activeuser')#">
        ... more params ... 
    </cfif>


</cfstoredproc>

【讨论】:

  • 我猜这个存储过程的例子应该在cffunction里面?
  • &lt;cfstoredproc&gt; 调用替换了 OP 中的 cfset 和 cfquery。
  • 存储过程是使用CREATE PROCEDURE 语句在数据库本身中创建的。在 SSMS 或您用来直接与数据库交互的任何工具中运行它。
  • 并且不要忘记将 SQL 中的存储过程的EXECUTE 权限授予您的 CF 用户(或为您的 cfquery 运行的任何用户)。
  • “存储过程是在数据库本身中创建的......” .. 显然应该包含您决定的任何 SQL,MERGE 或 UPDATE / INSERT。
猜你喜欢
  • 1970-01-01
  • 2012-08-08
  • 2022-06-10
  • 2011-07-01
  • 2016-01-13
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 2018-04-02
相关资源
最近更新 更多