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


152

我有一个Competitions结果表,一方面保存了团队成员的姓名和排名。

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

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

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

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

查询只需要大约5秒钟即可插入大约11,000个名称。

到目前为止,这并不是一个至关重要的应用程序,因此当我收到包含约10,000行的新比赛结果时,我可以考虑每月一次截断Competitors表

但是,如果有新的和现有的竞争对手加入新的结果,最佳实践是什么?我不想截断现有竞争对手表

我只需要对新竞争者执行INSERT语句,如果存在,则什么也不做。


70
不要NVARCHAR(64)列作为主键(和因此而成为集群键)!!首先-这是一个非常宽的密钥-最多128个字节;其次,它的大小可变-再次:不是最佳...这是您可能遇到的最糟糕的选择-您的性能将变得很糟糕,并且表和索引碎片将始终处于99.9%的状态...
marc_s

4
马克说得很对。不要使用name作为您的pk。使用id,最好是int或轻量级的东西。
理查德

6
参见Kimberly Tripp的博客文章介绍了构成良好集群关键的因素:独特,狭窄,静态,不断增长。您cName在四个类别中的三个类别中失败了。...(它不狭窄,可能不是静态的,而且绝对不会越来越多)
marc_s

我看不到在竞争者的名称表中添加INT主键的意义,在该表中所有查询都将位于名称上,例如'WHERE name like'%xxxxx%',因此我始终需要在名称上具有唯一索引。但是,是的,我能看到点没有把它变长..
迪迪埃·利维

3
a)避免碎片,b)如果它是其他表中的外键,则重复的数据大于必需的(这是速度考虑)
JamesRyan 2012年

Answers:


213

语义上,您在问“在不存在的地方插入竞争对手”:

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

2
好吧,这是我在对SO提出qquestion之前要做的事情。但是我的想法的核心是:与一周左右一次从头开始重建名称表的效果如何?(请记住,这只需要几秒钟)
Didier Levy

3
@Didier Levy:效率?当您只能使用差异进行更新时,为什么要截断并重新创建。即:BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN =更多工作。
gbn

@gbn-有没有办法在这里安全地使用if-else逻辑而不是您的答案?我有一个相关的问题。你能帮我吗?stackoverflow.com/questions/21889843/…–
Steam

53

另一种选择是将“结果”表与现有竞争者表保持联接状态,并通过过滤与联接中不匹配的不同记录来查找新竞争者:

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);

1
在这种情况下,合并非常棒,它完全按照其说的进行。
VorobeY1326 2015年

我绝对相信这是正确的方法,与子查询方法相比,它为SQL Server提供了最佳的优化提示。
Mads Nielsen'3

4
MERGE语句仍然有很多问题。只是谷歌的“ SQL合并问题”-许多博客已经详细讨论了这一点。
戴维·威尔逊

为什么在MERGE语句中没有目标,而在INSERT语句中没有目标?由于存在更多差异,因此很难把握等效性。
彼得

32

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

正常化。

您有一张可以模拟比赛的桌子吗?比赛由竞争对手组成?您需要在一个或多个比赛中列出不同的参赛者...

您应该具有以下表格.....

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-似乎没有适合该模型的好的NATURAL KEY,所以我认为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]

而且,当您与新竞争者进行新竞争时,只需检查“竞争者”表中已经存在的竞争者。如果它们已经存在,则您不会为那些竞争对手插入竞争对手,而不会为新竞争对手插入。

然后,将新的竞赛插入竞赛中,最后在竞赛竞争中建立所有链接。


2
假设OP现在具有重整其所有表的结构以获得一个缓存结果的重要性。重写数据库和应用程序,而不是在某个定义的范围内解决问题,而每次事情都不容易解决时,这就是灾难的根源。
杰弗里·韦斯特

1
也许在像我这样的OP案例中,您并不总是有权修改数据库。而且重写/规范化旧数据库并不总是在预算或分配的时间内。
eaglei22

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

上面讨论规范化的答案很棒!但是,如果您发现自己处在像我这样的位置上,而又不允许您触摸数据库的架构或结构,该怎么办?例如,DBA是“神”,所有建议的修订版都放在/ dev / null?

在这方面,对于以上提供代码示例的所有用户,我都认为此问题已通过Stack Overflow发布得到了回答

我正在重新插入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

上面的代码使用的字段与您拥有的字段不同,但是您可以使用各种技术来获得基本要点。

请注意,按照堆栈溢出的原始答案,此代码是从这里复制的

无论如何,我的观点是“最佳实践”通常归结为理论上可以做和不能做的事情。

  • 如果您能够规范化并生成索引/键-太好了!
  • 如果不是这样,并且您可以使用像我这样的代码黑客,希望以上内容对您有所帮助。

祝好运!


如果不清楚,这是解决问题的四种不同方法,因此选择一种。
nasch

3

按照Transact Charlie的建议对操作表进行规范化是一个好主意,随着时间的流逝,它将避免许多麻烦和问题-但是有些事情包括界面,它可以支持与外部系统集成的表和支持分析等功能的报告表。加工 而且这些类型的表不必一定要规范化 -实际上,通常情况下,它们要不规范得多很多,而且更加方便和高效

在这种情况下,我认为Transact Charlie对您的手术台的建议是一个很好的建议。

但是我会为Competitors表中的CompetitorName添加一个索引(不一定是唯一的),以支持CompetitorName上的有效联接,以实现集成(从外部源加载数据)的目的,并且我会将接口表放入其中:CompetitionResults。

CompetitionResults应该包含比赛结果中包含的所有数据。像这样的接口表的要点是使它尽可能快和容易地从Excel工作表或CSV文件或包含该数据的任何形式中截断并重新加载。

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

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

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

因此,CompetitionResults应该具有一些“旧”和“新”字段-oldEmail,newEmail,oldPhone,newPhone等。这样,您就可以在Competitors中通过CompetitorName,Email和Phone形成一个复合密钥。

然后,当您获得一些比赛结果时,您可以截断并从excel工作表或任何现有文件中重新加载您的CompetitionResults表,并运行一个有效的插入操作以将所有新的竞争对手插入到Competitors表中,并进行单个有效的更新以进行更新来自CompetitionResults的有关现有竞争对手的所有信息。您也可以执行一次插入操作,以将新行插入CompetitionCompetitors表中。这些事情可以在ProcessCompetitionResults存储过程中完成,可以在加载CompetitionResults表之后执行。

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

我最后要发表的评论是我之前在SO上发表的评论:如果您创建一个外键,以确保在Competitors表中存在一个竞争对手,那么您可以在其中将包含该竞争对手的行添加到CompetitionCompetitors之前,请确保外键设置为级联更新和删除。这样,如果您需要删除竞争对手,则可以这样做,并且与该竞争对手关联的所有行都将被自动删除。否则,默认情况下,外键要求您先删除CompetitionCompetitors中的所有相关行,然后再删除竞争对手。

(有些人认为非级联外键是一种很好的安全预防措施,但我的经验是,它们只是屁股上的一种令人痛苦的痛苦,而不仅仅是由于疏忽造成的,而且还创造了一些制造工作对于DBA来说,处理人员意外删除内容是为什么您拥有诸如“您确定”对话框之类的东西以及各种类型的常规备份和冗余数据源的原因。实际上,要删除一个竞争对手(其数据全部是例如,搞砸了,那就是不小心删除了一个,然后选择“哦,不!我不是故意要这样做的!现在我没有他们的比赛结果!A!”后者肯定足够普遍,因此,您确实需要为此做好准备,但是前者更为常见,因此,为前者(imo)做准备的最简单,最好的方法就是使外键级联更新和删除。)


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子句添加到视图查询中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.