在不存在的地方插入值

Posted

技术标签:

【中文标题】在不存在的地方插入值【英文标题】:INSERT VALUES WHERE NOT EXISTS 【发布时间】:2013-08-01 10:27:20 【问题描述】:

好的,所以我正在尝试改进我的 asp 数据输入页面,以确保进入我的数据表的条目是唯一的。

所以在这个表中我有 SoftwareName 和 SoftwareType。我正在尝试获取它,所以如果入口页面发送一个插入查询,其参数与表中的内容相匹配(因此相同的标题和类型),则会引发错误并且未输入数据。

类似这样的:

INSERT INTO tblSoftwareTitles( 
            SoftwareName,  
            SoftwareSystemType) 
            VALUES(@SoftwareName,@SoftwareType) 
            WHERE NOT EXISTS (SELECT SoftwareName 
            FROM tblSoftwareTitles 
            WHERE Softwarename = @SoftwareName 
            AND SoftwareType = @Softwaretype)

因此,这种语法非常适合从一个表中选择列到另一个表中而无需输入重复项,但似乎不想使用参数化的插入查询。谁能帮我解决这个问题?

编辑:

这是我在 ASP 插入方法中使用的代码

    private void ExecuteInsert(string name, string type)

    //Creates a new connection using the HWM string
    using (SqlConnection HWM = new SqlConnection(GetConnectionStringHWM()))
    
        //Creates a sql string with parameters
        string sql = " INSERT INTO tblSoftwareTitles( "
                   + " SoftwareName, " 
                   + " SoftwareSystemType) "
                   + " SELECT "
                   + " @SoftwareName, "
                   + " @SoftwareType "
                   + " WHERE   NOT EXISTS  "
                   + " ( SELECT  1 "
                   + " FROM tblSoftwareTitles "
                   + " WHERE Softwarename = @SoftwareName "
                   + " AND SoftwareSystemType = @Softwaretype); ";         

        //Opens the connection
        HWM.Open();
        try
        
            //Creates a Sql command
            using (SqlCommand addSoftware = new SqlCommand
                CommandType = CommandType.Text,
                Connection = HWM,
                CommandTimeout = 300,
                CommandText = sql)
            
                //adds parameters to the Sql command
                addSoftware.Parameters.Add("@SoftwareName", SqlDbType.NVarChar, 200).Value = name;
                addSoftware.Parameters.Add("@SoftwareType", SqlDbType.Int).Value = type;
                //Executes the Sql
                addSoftware.ExecuteNonQuery();
            
            Alert.Show("Software title saved!");
        
        catch (System.Data.SqlClient.SqlException ex)
        
            string msg = "Insert Error:";
            msg += ex.Message;
            throw new Exception(msg);
        

    

【问题讨论】:

你可能想使用类似INSERT INTO ... SELECT @P1, @P2 WHERE NOT EXISTS ... SQL2008+ : 你可以使用MERGE 语句link 那么使用一些约束怎么样?唯一键? 创建UNIQUE 约束的更好方法。 msdn.microsoft.com/en-us/library/ms190024.aspx @ta.speot.js 嗯,我确实尝试用 SELECT 替换 VALUES,但它仍然会出现语法错误。 【参考方案1】:

您可以使用IF 语句来做到这一点:

IF NOT EXISTS 
    (   SELECT  1
        FROM    tblSoftwareTitles 
        WHERE   Softwarename = @SoftwareName 
        AND     SoftwareSystemType = @Softwaretype
    )
    BEGIN
        INSERT tblSoftwareTitles (SoftwareName, SoftwareSystemType) 
        VALUES (@SoftwareName, @SoftwareType) 
    END;

不用IF 也可以使用SELECT 来实现

INSERT  tblSoftwareTitles (SoftwareName, SoftwareSystemType) 
SELECT  @SoftwareName,@SoftwareType
WHERE   NOT EXISTS 
        (   SELECT  1
            FROM    tblSoftwareTitles 
            WHERE   Softwarename = @SoftwareName 
            AND     SoftwareSystemType = @Softwaretype
        );

这两种方法都容易受到race condition 的影响,因此虽然我仍会使用上述方法之一进行插入,但您可以使用唯一约束来保护重复插入:

CREATE UNIQUE NONCLUSTERED INDEX UQ_tblSoftwareTitles_Softwarename_SoftwareSystemType
    ON tblSoftwareTitles (SoftwareName, SoftwareSystemType);

Example on SQL-Fiddle


附录

在 SQL Server 2008 或更高版本中,您可以使用 MERGEHOLDLOCK 来消除竞争条件的可能性(这仍然不能替代唯一约束)。

MERGE tblSoftwareTitles WITH (HOLDLOCK) AS t
USING (VALUES (@SoftwareName, @SoftwareType)) AS s (SoftwareName, SoftwareSystemType) 
    ON s.Softwarename = t.SoftwareName 
    AND s.SoftwareSystemType = t.SoftwareSystemType
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (SoftwareName, SoftwareSystemType) 
    VALUES (s.SoftwareName, s.SoftwareSystemType);

Example of Merge on SQL Fiddle

【讨论】:

那是不是像一个复合约束?他们两个必须相等才能抛出错误消息? 我认为我使用了错误的列名(SoftwareType 而不是SoftwareSystemType),我编辑了答案并添加了示例数据的链接。 好的,这是一个不错的网站。你的代码很棒,但我认为我试图通过一个 asp 页面执行它是问题所在。 看来我可能需要使用存储过程 我看不出为什么这不能通过和 asp 页面工作,即使使用 IF 的解决方案不能正常工作。 Working demo on SQL Fiddle,如果您发布 asp 代码,它可能会提供有关它为什么不起作用的洞察力。【参考方案2】:

这不是答案。我只是想表明IF NOT EXISTS(...) INSERT 方法不安全。您必须先执行Session #1,然后再执行Session #2。在v #2 之后,您会看到如果没有UNIQUE 索引,您可能会得到重复的对(SoftwareName,SoftwareSystemType)。会话 #1 的延迟用于为您提供足够的时间来执行第二个脚本(会话 #2)。您可以减少这种延迟。

会话 #1(SSMS > 新查询 > F5(执行))

CREATE DATABASE DemoEXISTS;
GO
USE DemoEXISTS;
GO
CREATE TABLE dbo.Software(
    SoftwareID INT PRIMARY KEY,
    SoftwareName NCHAR(400) NOT NULL,  
    SoftwareSystemType NVARCHAR(50) NOT NULL
);
GO

INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
VALUES (1,'Dynamics AX 2009','ERP');
INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
VALUES (2,'Dynamics NAV 2009','SCM');
INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
VALUES (3,'Dynamics CRM 2011','CRM');
INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
VALUES (4,'Dynamics CRM 2013','CRM');
INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
VALUES (5,'Dynamics CRM 2015','CRM');
GO
/*
CREATE UNIQUE INDEX IUN_Software_SoftwareName_SoftareSystemType
ON dbo.Software(SoftwareName,SoftwareSystemType);
GO
*/

-- Session #1
BEGIN TRANSACTION;
    UPDATE  dbo.Software
    SET     SoftwareName='Dynamics CRM',
            SoftwareSystemType='CRM'    
    WHERE   SoftwareID=5;

    WAITFOR DELAY '00:00:15' -- 15 seconds delay; you have less than 15 seconds to switch SSMS window to session #2

    UPDATE  dbo.Software
    SET     SoftwareName='Dynamics AX',
            SoftwareSystemType='ERP'
    WHERE   SoftwareID=1;
COMMIT
--ROLLBACK
PRINT 'Session #1 results:';
SELECT *
FROM dbo.Software;

会话 #2(SSMS > 新查询 > F5(执行))

USE DemoEXISTS;
GO
-- Session #2
DECLARE 
    @SoftwareName NVARCHAR(100),  
    @SoftwareSystemType NVARCHAR(50);
SELECT
    @SoftwareName=N'Dynamics AX',
    @SoftwareSystemType=N'ERP';

PRINT 'Session #2 results:';
IF NOT EXISTS(SELECT *
    FROM dbo.Software s
    WHERE s.SoftwareName=@SoftwareName 
    AND s.SoftwareSystemType=@SoftwareSystemType)
BEGIN
    PRINT 'Session #2: INSERT';

    INSERT INTO dbo.Software(SoftwareID,SoftwareName,SoftwareSystemType)
    VALUES (6,@SoftwareName,@SoftwareSystemType);
END 
PRINT 'Session #2: FINISH';
SELECT  * 
FROM    dbo.Software;

结果:

Session #1 results:
SoftwareID  SoftwareName      SoftwareSystemType
----------- ----------------- ------------------
1           Dynamics AX       ERP
2           Dynamics NAV 2009 SCM
3           Dynamics CRM 2011 CRM
4           Dynamics CRM 2013 CRM
5           Dynamics CRM      CRM

Session #2 results:
Session #2: INSERT
Session #2: FINISH
SoftwareID  SoftwareName      SoftwareSystemType
----------- ----------------- ------------------
1           Dynamics AX       ERP <-- duplicate (row updated by session #1)
2           Dynamics NAV 2009 SCM
3           Dynamics CRM 2011 CRM
4           Dynamics CRM 2013 CRM
5           Dynamics CRM      CRM
6           Dynamics AX       ERP <-- duplicate (row inserted by session #2)

【讨论】:

我明白你在说什么,它可能是正确的。该系统只会让一个用户执行偶尔的查询。如果他设法打开多个会话并尝试一次执行多个插入,那将是一个问题,但我认为他不会。【参考方案3】:

这个问题有一个很好的解决方案,可以使用Sql的Merge关键字

Merge MyTargetTable hba
USING (SELECT Id = 8, Name = 'Product Listing Message') temp 
ON temp.Id = hba.Id
WHEN NOT matched THEN 
INSERT (Id, Name) VALUES (temp.Id, temp.Name);

你可以在关注之前检查一下,下面是示例

IF OBJECT_ID ('dbo.TargetTable') IS NOT NULL
    DROP TABLE dbo.TargetTable
GO

CREATE TABLE dbo.TargetTable
    (
    Id   INT NOT NULL,
    Name VARCHAR (255) NOT NULL,
    CONSTRAINT PK_TargetTable PRIMARY KEY (Id)
    )
GO



INSERT INTO dbo.TargetTable (Name)
VALUES ('Unknown')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('Mapping')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('Update')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('Message')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('Switch')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('Unmatched')
GO

INSERT INTO dbo.TargetTable (Name)
VALUES ('ProductMessage')
GO


Merge MyTargetTable hba
USING (SELECT Id = 8, Name = 'Listing Message') temp 
ON temp.Id = hba.Id
WHEN NOT matched THEN 
INSERT (Id, Name) VALUES (temp.Id, temp.Name);

【讨论】:

【参考方案4】:

更多评论链接以供建议进一步阅读...一篇非常好的博客文章,它对完成此任务的各种方法进行了基准测试can be found here。

他们使用了一些技巧:“在不存在的地方插入”、“合并”语句、“插入除外”和典型的“左连接”,以查看哪种方式最快可以完成此任务。

每种技术使用的示例代码如下(直接从他们的页面复制/粘贴):

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

对于追求速度的人来说,这是一本好书!在 SQL 2014 上,Insert-Except 方法被证明是 5000 万或更多记录的最快方法。

【讨论】:

喜欢看到可用于解决一个问题的不同方法。感谢您发布各种 sn-ps。【参考方案5】:

忽略重复的唯一约束不是解决方案吗?

INSERT IGNORE INTO tblSoftwareTitles...

【讨论】:

问题被标记为SqlServer,不是mysql,插入忽略SqlServer中不存在。

以上是关于在不存在的地方插入值的主要内容,如果未能解决你的问题,请参考以下文章

MySQL - 在不存在的地方插入 [重复]

在不存在的地方插入值

在不存在但线程安全的地方插入(我不想重复)

Oracle-在不存在的地方插入select max

MySQL:如果值存在,则对行进行更新,如果值不存在,则插入 [重复]

如果值不存在,则插入连接表