162

我有一个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 语句,如果他们存在,什么也不做。

4

8 回答 8

234

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

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
于 2011-03-13T09:21:10.067 回答
60

另一种选择是将您的结果表与您现有的竞争对手表左连接,并通过过滤在连接中不匹配的不同记录来查找新的竞争对手:

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);
于 2011-03-13T12:21:30.790 回答
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 的约束指向其他表。

使用这种表结构——你的键都是简单的整数——似乎没有一个适合模型的好的 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]

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

然后在 Competition 中插入新的 Competition,最后在 CompetitionCompetitors 中创建所有链接。

于 2011-03-13T10:06:14.633 回答
12

您需要将这些表连接在一起,并获得一个不存在的独特竞争对手的列表Competitors

这将插入唯一记录。

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

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

于 2011-03-13T08:35:38.447 回答
5

上面谈论规范化的答案很棒!但是,如果您发现自己处于像我这样的位置,不允许您按原样接触数据库架构或结构,该怎么办?例如,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

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

请注意,根据 Stack Overflow 上的原始答案,此代码是从此处复制的

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

  • 如果您能够规范化和生成索引/键——太好了!
  • 如果没有,并且您可以像我一样使用代码技巧,希望以上内容有所帮助。

祝你好运!

于 2017-10-24T02:10:28.100 回答
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 表,并运行一个有效的插入以将所有新竞争对手插入到竞争对手表中,并运行一个有效的更新来更新CompetitionResults 中有关现有竞争对手的所有信息。并且您可以执行一次插入以将新行插入到 CompetitionCompetitors 表中。这些事情可以在 ProcessCompetitionResults 存储过程中完成,该过程可以在加载 CompetitionResults 表后执行。

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

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

(有些人认为非级联外键是一种很好的安全预防措施,但我的经验是,它们只是屁股上的一个非常痛苦的东西,而且往往不仅仅是疏忽的结果,而且它们会产生一堆麻烦对于 DBA。处理人们不小心删除东西的原因是为什么你有诸如“你确定”对话框和各种类型的定期备份和冗余数据源之类的东西。实际上想要删除竞争对手的情况要普遍得多,因为竞争对手的数据就是全部例如搞砸了,比不小心删除一个然后说“哦不!我不是故意的!现在我没有他们的比赛结果!啊啊啊!”后者当然很常见,所以,你确实需要为此做好准备,但前者更为常见,所以为前者做准备的最简单和最好的方法,imo,就是让外键级联更新和删除。)

于 2016-06-30T16:59:43.840 回答
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 子句添加到视图查询。

于 2018-05-24T14:04:46.177 回答
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
)
于 2020-09-09T17:19:08.957 回答