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

我有一个Competitions结果表,一方面持有团队成员的名字和他们的排名。

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

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

现在我在第一张表中有大约20万的结果, 当竞争者表是空的,我可以执行这个:

 INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults 

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

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

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

我只需要为新的竞争对手执行INSERT语句,如果它们存在,什么也不做。

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

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

另一个select是将您的结果表格与现有的竞争对手表格留在一起,并通过筛选不匹配连接的不同logging来查找新的竞争对手:

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

不知道为什么其他人还没有这样说,

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 – 似乎没有一个很好的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] 

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

然后你插入新的竞争竞争,最后你只是在CompetitionCompetitors的所有链接。

您将需要一起参加表格,并获得竞争对手中不存在的独特竞争对手名单。

这将插入唯一的logging。

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

可能会有一段时间,这个插入需要快速完成,而不能等待select唯一的名称。 在这种情况下,您可以将唯一名称插入临时表中,然后使用该临时表插入到您的真实表中。 这很好,因为所有的处理都是在你插入临时表的时候发生的,所以不会影响你的真实表。 然后,当你完成所有的处理,你做一个快速插入到真正的表。 我甚至可能把最后一部分,你插入真正的表,在一个事务中。

按照Transact Charlie的build议,规范化操作表是一个不错的主意,随着时间的推移将会节省许多麻烦和问题 – 但也有支持与外部系统集成的接口表和支持分析处理; 这些types的表格不一定要标准化 – 事实上, 对于他们来说,这样做往往是非常方便和高效的

在这种情况下,我认为查理交易所对您的业务表的build议是一个很好的build议。

但是我会在竞争对手表中为竞争者名称添加一个索引(不一定是唯一的),以支持竞争者名称上的高效连接,以便集成(从外部数据源加载数据)。我将一个接口表放入混合:CompetitionResults。

比赛结果应该包含你的比赛结果中包含的任何数据。 像这样的接口表的一点是尽可能快捷地从Excel工作表或CSV文件或其他任何forms的数据中截取和重新加载它。

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

有一点我会注意到 – 实际上,在我看来,竞争对手名称在数据中不太可能是唯一的 。 例如,在20万竞争者中,你可能拥有2个或更多的大卫·史密斯(David Smiths)。 所以我build议你从竞争对手那里收集更多的信息,比如他们的电话号码或者电子邮件地址,或者更有可能是独一无二的东西。

您的操作表(竞争对手)应该为每个有助于复合自然键的数据项添加一列。 例如,它应该有一个主电子邮件地址列。 但是接口表应该有一个用于主电子邮件地址的旧值和值的插槽,以便可以使用旧值在竞争对手中查找logging,并将该值更新为新值。

所以CompetitionResults应该有一些“旧”和“新”字段 – oldEmail,newEmail,oldPhone,newPhone等。这样你就可以在Competitor,CompetitorName,Email和Phone中组成一个组合键。

然后当你有一些比赛结果时,你可以截断并重新加载你的Excel表格或你的Excel表格,然后运行一个高效的插入来插入竞争对手表中的所有新的竞争者,所有关于竞争结果中现有竞争对手的信息。 你可以做一个单一的插入到CompetitionCompetitors表中插入新的行。 这些事情可以在ProcessCompetitionResults存储过程中完成,可以在加载CompetitionResults表之后执行。

这是对我在Oracle应用软件,SAP,PeopleSoft和其他企业软件套件清单中所看到的现实世界的一个简单的描述。

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

(有些人认为非级联的外键是一个很好的安全防范措施,但是我的经验是,他们只是一个令人毛骨悚然的痛苦,往往不是简单的疏忽导致的,而是创造了一堆工作对于DBA来说,处理意外删除的东西就是为什么你有“你确定”的对话框和各种types的定期备份和冗余数据源,实际上想要删除一个竞争对手的数据是非常普遍的,搞砸了,比起意外地删掉一个然后去“哦,不,我不是那个意思,现在我没有他们的比赛结果啊!啊!”后者肯定是很普遍的,所以,你需要为它做好准备,但是前者更为普遍,所以为前者准备最简单,最好的方法就是使外键级联更新和删除。)

上面谈到正常化的答案很棒! 但是如果你发现自己处于像我这样的地位,你不允许触摸数据库模式或架构, 例如,数据库pipe理员是'神',所有build议的修订版本都转到/ dev / null?

在这方面,我感觉这个Stack Overflow的post也回答了所有上面的代码示例。

我重写了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 

上面的代码使用了不同的领域,但你得到了各种技术的一般要点。

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

无论如何,我的观点是“最佳实践”,往往归结为你能做和不能做的理论。

  • 如果你能够正常化和生成索引/键 – 太棒了!
  • 如果没有,你有像我这样的黑客手段,希望上面的帮助。

祝你好运!