如果不存在SQL Server插入最佳实践

时间:2011-03-13 08:22:41

标签: sql-server insert-update

我有一个Competitions结果表,其中一方面包含团队成员的姓名及其排名。

另一方面,我需要维护唯一竞争对手名称表

CREATE TABLE Competitors (cName nvarchar(64) primary key)

现在我在第一张表中有大约200,000个结果,而当竞争对手表空时 我可以执行此操作:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

查询只需要5秒钟就能插入大约11,000个名字。

到目前为止,这不是一个关键的应用程序,所以我可以考虑每月一次截断竞争对手表,当我收到大约10,000行的新竞争结果时。

但是,当新的和现有的竞争对手加入新的结果时,最佳做法是什么? 我不想截断现有的竞争对手表

我只需要为新竞争对手执行INSERT语句,如果它们存在则不执行任何操作。

8 个答案:

答案 0 :(得分:196)

从语义上讲,您要求“插入竞争对手尚不存在的地方”:

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)

答案 1 :(得分:51)

另一种选择是将您的结果表与您现有的竞争对手表连接起来,并通过筛选与该联接不匹配的不同记录来找到新的竞争对手:

INSERT Competitors (cName)
SELECT  DISTINCT cr.Name
FROM    CompResults cr left join
        Competitors c on cr.Name = c.cName
where   c.cName is null

新语法MERGE还提供了一种紧凑,优雅且高效的方法:

MERGE INTO Competitors AS Target
USING (SELECT DISTINCT Name FROM CompResults) AS Source ON Target.Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Name) VALUES (Source.Name);

答案 2 :(得分:32)

不知道为什么其他人还没说过这个;

NORMALIZE。

你有一张可以模拟比赛的桌子吗?比赛是由竞争对手组成的?在一场或多场比赛中,您需要一份明确的参赛者名单......

你应该有以下表格.....

CREATE TABLE Competitor (
    [CompetitorID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitorName] NVARCHAR(255)
    )

CREATE TABLE Competition (
    [CompetitionID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitionName] NVARCHAR(255)
    )

CREATE TABLE CompetitionCompetitors (
    [CompetitionID] INT
    , [CompetitorID] INT
    , [Score] INT

    , PRIMARY KEY (
        [CompetitionID]
        , [CompetitorID]
        )
    )

对CompetitionCompetitors.CompetitionID和CompetitorID的约束指向其他表。

使用这种表结构 - 你的键都是简单的INTS - 似乎没有一个适合模型的好的自然键,所以我认为SURROGATE KEY非常适合这里。

因此,如果你有这个,那么在特定的比赛中获得不同的竞争者名单,你可以发出这样的查询:

DECLARE @CompetitionName VARCHAR(50) SET @CompetitionName = 'London Marathon'

    SELECT
        p.[CompetitorName] AS [CompetitorName]
    FROM
        Competitor AS p
    WHERE
        EXISTS (
            SELECT 1
            FROM
                CompetitionCompetitor AS cc
                JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]
            WHERE
                cc.[CompetitorID] = p.[CompetitorID]
                AND cc.[CompetitionName] = @CompetitionNAme
        )

如果你想要竞争对手参加每场比赛的得分:

SELECT
    p.[CompetitorName]
    , c.[CompetitionName]
    , cc.[Score]
FROM
    Competitor AS p
    JOIN CompetitionCompetitor AS cc ON cc.[CompetitorID] = p.[CompetitorID]
    JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]

当您与新竞争对手进行新竞争时,您只需检查竞争对手表中已存在的竞争对手。如果它们已经存在,那么您不会为竞争对手插入竞争对手并插入新的竞争对手。

然后你插入新的竞赛比赛,最后你只需在CompetitionCompetitors中建立所有链接。

答案 3 :(得分:10)

您需要将这些表格加在一起,并获取Competitors中尚不存在的唯一竞争对手列表。

这将插入唯一记录。

INSERT Competitors (cName) 
SELECT DISTINCT Name
FROM CompResults cr LEFT JOIN Competitors c ON cr.Name = c.cName
WHERE c.Name IS NULL

可能有一段时间需要快速完成此插入,而无法等待选择唯一名称。在这种情况下,您可以将唯一名称插入临时表,然后使用该临时表插入到您的真实表中。这很有效,因为所有处理都在您插入临时表时发生,因此它不会影响您的真实表。然后,当您完成所有处理后,您可以快速插入真实表格。我甚至可以将最后一部分包装到事务中,在事务中插入真实表。

答案 4 :(得分:3)

按照Transact Charlie的建议规范化操作表是一个好主意,并且会随着时间的推移节省许多麻烦和问题 - 但是有 interface 这样的东西,支持与外部系统的集成,以及报告表,支持分析处理等内容;那些类型的表应该 不一定是标准化的 - 实际上,通常它们更多,更方便和更高效,因为它们不是

在这种情况下,我认为Transact Charlie关于您的操作表的提议是一个很好的建议。

但是我会在Competitors表中为CompetitorName添加一个索引(不一定是唯一的),以便为了集成(从外部源加载数据)支持CompetitorName上的有效连接,我会将一个接口表添加到混合中:竞赛结果。

竞争结果应该包含竞争结果中包含的任何数据。像这样的接口表的要点是尽可能快速简便地从Excel工作表或CSV文件或您拥有该数据的任何形式截断并重新加载它。

该接口表不应被视为规范化操作表集的一部分。然后您可以按照Richard的建议加入CompetitionResults,将记录插入到不存在的竞争对手中,并更新那些(例如,如果您确实有关于竞争对手的更多信息,如他们的电话号码或电子邮件地址)。

我要注意的一件事 - 实际上,竞争对手名称,在我看来,不太可能在您的数据中独一无二。例如,在200,000名竞争对手中,你可能拥有2个或更多David Smiths。因此,我建议您从竞争对手那里收集更多信息,例如他们的电话号码或电子邮件地址,或者更有可能是独特的信息。

您的操作表,竞争对手,每个数据项应该只有一列有助于复合自然键;例如,它应该有一列主电子邮件地址。但是接口表应该有一个用于主电子邮件地址的值的插槽,以便旧值可用于在竞争对手中查找记录并更新那部分是新的价值。

所以竞争结果应该有一些" old"和"新"字段 - oldEmail,newEmail,oldPhone,newPhone等。这样你就可以在竞争对手中形成一个复合键,来自CompetitorName,Email和Phone。

然后,当你有一些比赛结果时,你可以从excel表或你拥有的任何东西截断并重新加载你的CompetitionResults表,并运行一个有效的插入,将所有新的竞争对手插入到竞争者表中,并且单一,高效更新以从CompetitionResults更新有关现有竞争对手的所有信息。您可以执行单个插入操作以将新行插入到CompetitionCompetitors表中。这些事情可以在ProcessCompetitionResults存储过程中完成,该过程可以在加载CompetitionResults表后执行。

这是对我在Oracle应用程序,SAP,PeopleSoft和其他企业软件套件清单中在现实世界中一遍又一遍地做过的一些基本描述。

我做的最后一个评论是我之前在SO上做的一个:如果你创建了一个外键,确保竞争对手表中存在竞争对手,然后你可以在竞争对手中添加一行对于CompetitionCompetitors,确保将外键设置为级联更新和删除。这样,如果您需要删除竞争对手,您就可以执行此操作,并且与该竞争对手关联的所有行都将自动删除。否则,默认情况下,外键将要求您从CompetitionCompetitors中删除所有相关行,然后才能删除竞争对手。

(有些人认为非级联外键是一种很好的安全预防措施,但我的经验是,他们只是一个非常痛苦的屁股,往往不仅仅是疏忽造成的,他们创造了为DBA提供了大量工作。处理人们意外删除的东西就是为什么你会有这样的事情,你确定"对话框和各种类型的常规备份和冗余数据源。'到目前为止,实际上想要删除一个竞争对手,其数据全部搞砸了,而不是意外删除一个竞争对手,然后去"哦,不是!我不是故意这样做。 !现在我没有他们的竞争结果!Aaaahh!"后者当然足够普遍,所以,你需要做好准备,但前者更为常见,所以最简单的准备前者的最好方法是imo,就是只做外键级联更新和删除。)

答案 5 :(得分:1)

上面谈到正常化的答案很棒!但是,如果您发现自己处于像我这样的位置,而您不允许触及数据库架构或结构,那该怎么办呢?例如,DBA是'众神',所有建议的修订都是/ dev / null?

在这方面,对于上面给出代码示例的所有用户,我觉得这个has been answered with this Stack Overflow posting too

我正在重新发布INSERT VALUES WHERE NOT EXISTS中的代码,这对我帮助最大,因为我无法更改任何基础数据库表:

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

上面的代码使用的字段与您拥有的字段不同,但您可以通过各种技术获得一般要点。

请注意,根据Stack Overflow上的原始答案,此代码为copied from here

无论如何,我的观点是“最佳实践”通常归结为你能做什么,不能做什么和理论一样。

  • 如果你能够规范化并生成索引/键 - 太棒了!
  • 如果没有,你就有了像我一样的代码黑客,希望是 以上帮助。
祝你好运!

答案 6 :(得分:1)

好的,这是在7年前提出的,但我认为这里最好的解决方案是完全放弃新表,只是将其作为自定义视图。这样你不会复制数据,不用担心唯一数据,也不会触及实际的数据库结构。像这样:

CREATE VIEW vw_competitions
  AS
  SELECT
   Id int
   CompetitionName nvarchar(75)
   CompetitionType nvarchar(50)
   OtherField1 int
   OtherField2 nvarchar(64)  --add the fields you want viewed from the Competition table
  FROM Competitions
GO

这里可以添加其他项目,例如其他表上的连接,WHERE子句等。这很可能是解决此问题的最优雅方法,因为您现在可以只查询视图:

SELECT *
FROM vw_competitions

...并将任何WHERE,IN或EXISTS子句添加到视图查询中。

答案 7 :(得分:0)

此外,如果您要插入多个列,并想检查它们是否存在,请使用以下代码

Insert Into [Competitors] (cName, cCity, cState)
Select cName, cCity, cState from 
(
    select new.* from 
    (
        select distinct cName, cCity, cState 
        from [Competitors] s, [City] c, [State] s
    ) new
    left join 
    (   
        select distinct cName, cCity, cState 
        from [Competitors] s
    ) existing
    on new.cName = existing.cName and new.City = existing.City and new.State = existing.State
    where existing.Name is null  or existing.City is null or existing.State is null
)