存在しない場合のSQL Server挿入のベストプラクティス


152

チームメンバーの名前とそれらのランキングを一方Competitionsで保持する結果テーブルがあります。

一方、私はユニークな競合他社の名前の表を維持する必要があります

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

これで、最初のテーブルに約200,000件の結果が表示されました。競合他社のテーブルが空の場合、これを実行できます。

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

また、クエリは約11,000の名前を挿入するのにわずか5秒しかかかりません。

これまでのところ、これは重要なアプリケーションではないので、月に1回、約10,000行の新しい競争結果を受け取ったら、競合他社テーブルを切り捨てることを検討できます。

しかし、新しい結果が追加されたときに、新しい競合他社と既存の競合他社がある場合のベストプラクティスは何ですか?既存の競合他社のテーブルを切り捨てたくない

新規の競合他社に対してのみINSERTステートメントを実行する必要があり、存在する場合は何もしません。


70
、してくださいません作るNVARCHAR(64)コラムプライマリ(したがって:クラスタリング)キー!! まず第一に、それは非常に広い鍵です-最大128バイト。次に、可変サイズです-再度:最適ではありません...これはあなたが持つことができる最悪の選択についてです-あなたのパフォーマンスは地獄であり、テーブルとインデックスの断片化は常に99.9%です....
marc_s

4
マークは良い点を持っています。あなたのpkとして名前を使わないでください。idを使用します。できればintまたは軽量なものを使用してください。
リチャード

6
優れたクラスタリングキーの特徴についてのKimberly Trippのブログ投稿をご覧ください。あなたは(それはおそらく、静的ではない、狭めていない、それはますます増加していない間違いです).... 3〜4のカテゴリーに失敗したcName
marc_s

INTプライマリキーを競合他社の名前テーブルに追加するポイントがわかりません。すべてのクエリが名前に対して行われます。たとえば、 'WHERE name like'%xxxxx% ''のように、名前に一意のインデックスが常に必要です。しかし、はい、私は...それ可変長作っていない点を見ることができます
ディディエ・レヴィ

3
a)断片化を回避し、b)それが他のテーブルの外部キーである場合、複製されたデータは必要以上に大きくなります(速度の考慮事項)
JamesRyan

回答:


214

意味的には、「まだ存在していない競合他社を挿入する」と尋ねています。

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

2
まあ、これは私がSOに質問をする前に私がやろうと思ったことです。しかし、私の考えの核心は、次のとおりです。これは、週に1度程度、namesテーブルをゼロから再構築することに対してどれほどうまく機能するでしょうか。(これには数秒しかかからないことを思い出してください)
Didier Levy

3
@Didier Levy:効率性?差分のみで更新できるときに切り捨て、再作成する理由。つまり、BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN =より多くの作業が必要です。
gbn 2011年

@gbn-ここであなたの答えの代わりにif-elseロジックを安全に使用する方法はありますか?関連する質問があります。それを手伝ってくれませんか?stackoverflow.com/questions/21889843/...
スチーム

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
この場合、Mergeは素晴らしいです、それはまさにそれが言うことを行います。
VorobeY1326 2015

サブクエリアプローチとは対照的に、これは正しい方法であり、SQL Serverに最適化のための最良のヒントを与えるのは間違いないと私は確信しています。
Mads Nielsen

4
MERGEステートメントには、まだ多くの問題があります。「SQLマージの問題」をググってください-多くのブロガーがこれについて詳しく説明しています。
David Wilson

なぜMERGEステートメントにはAs Targetがあり、INSERTステートメントにはTargetがないのですか?同等性を把握するのを困難にする違いがさらにあります。
Peter

32

他の誰かがまだこれを言っていない理由がわかりません。

ノーマライズ。

競技をモデル化したテーブルがありますか?コンテストは競合他社で構成されていますか?1つ以上のコンペティションに参加する選手の明確なリストが必要です......

次の表が必要です。

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に作成します。


2
この時点でOPがすべてのテーブルを再構築して1つのキャッシュされた結果を取得することができると仮定します。何かが簡単に配置されないたびに、定義されたスコープ内で問題を解決するのではなく、dbとアプリを書き直すことは、災害のレシピです。
Jeffrey Vest 2017年

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に行きますか?

その点では、上記のコードサンプルを提供しているすべてのユーザーに関して、このスタックオーバーフローの投稿でも回答されているように感じます。

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の元の回答に従って、このコードはここからコピーされたことに注意してください

とにかく、私のポイントは、 "ベストプラクティス"は、理論だけでなく、実行できることとできないことに帰着します。

  • インデックス/キーを正規化して生成できる場合-すばらしい!
  • そうでなければ、あなたが私のようなコードハックに頼る手段を持っているなら、うまくいけば上記は助けになるでしょう。

幸運を!


はっきりしない場合、これは問題に対する4つの異なるアプローチです。
nasch

3

Transact Charlieによって提案されているように操作テーブルを正規化することは良いアイデアであり、時間の経過とともに多くの頭痛と問題を節約しますが、外部システムとの統合をサポートするインターフェーステーブルや、分析などをサポートするレポートテーブルなどがあります。処理; そして、これらのタイプのテーブルは必ずしも正規化されている必要はありません -実際には、多くの場合、そうでない方はるかに便利で、パフォーマンスが優れています

この場合、Transact Charlieの運用テーブルの提案は良いものだと思います。

ただし、統合(外部ソースからのデータのロード)のためにCompetitorNameでの効率的な結合をサポートするために、インデックス(必ずしも一意ではない)をCompetitorsNameのCompetitorNameに追加し、インターフェイステーブルを組み合わせてCompetitionResultsにします。

CompetitionResultsには、競争結果のデータが含まれている必要があります。このようなインターフェーステーブルのポイントは、ExcelシートやCSVファイル、またはそのデータが含まれている任意のフォームから切り捨てて再読み込みすることを可能な限り迅速かつ簡単にすることです。

そのインターフェース表は、操作表の正規化されたセットの一部と見なされるべきではありません。次に、Richardの提案に従ってCompetitionResultsに参加して、まだ存在していない競合他社にレコードを挿入し、競合しているレコードを更新できます(たとえば、電話番号やメールアドレスなど、競合他社に関する実際の情報がある場合)。

私が気づく点の1つに、実際には、競合他社の名前はあなたのデータで一意である可能性非常に低いようです。たとえば、200,000人の競合他社では、2つ以上のDavid Smithがいる可能性があります。そのため、電話番号やメールアドレスなど、競合他社からより多くの情報を収集することをお勧めします。

運用テーブルである競合他社は、複合自然キーに寄与するデータ項目ごとに1つの列のみを持つ必要があります。たとえば、メインのメールアドレスの列が1つ必要です。しかし、インタフェーステーブルは用のスロットが必要です古い新しい古い値が競合他社のレコードを検索し、新しい値にそれのその部分を更新するために使用することができますように、プライマリメールアドレスの値を。

したがって、CompetitionResultsには、oldEmail、newEmail、oldPhone、newPhoneなどの「古い」フィールドと「新しい」フィールドが必要です。これにより、CompetitorName、Email、およびPhoneから、競合他社の複合キーを形成できます。

次に、いくつかの競争結果が出たら、Excelシートまたは既存のものからCompetitionResultsテーブルを切り捨てて再ロードし、単一の効率的な挿入を実行してすべての新しい競合他社を競合他社のテーブルに挿入し、単一の効率的な更新を更新できます。 CompetitionResultsからの既存の競合他社に関するすべての情報。また、単一の挿入を実行して、新しい行をCompetitionCompetitorsテーブルに挿入できます。これらは、CompetitionResultsテーブルを読み込んだ後に実行できるProcessCompetitionResultsストアドプロシージャで実行できます。

これは、Oracle Applications、SAP、PeopleSoft、およびその他のエンタープライズソフトウェアスイートのランドリーリストを使用して、現実の世界で何度も何度も見たものの初歩的な説明です。

私がする最後のコメントは、SOで以前に行ったコメントです。競合他社が含まれる行をCompetitionCompetitorsに追加する前に、競合他社が競合他社テーブルに存在することを保証する外部キーを作成する場合は、外部キーは、更新と削除をカスケードするように設定されています。こうすることで、競合他社を削除する必要がある場合は削除でき、その競合他社に関連付けられているすべての行が自動的に削除されます。それ以外の場合、デフォルトでは、外部キーを使用すると、競合他社を削除する前に、関連するすべての行をCompetitionCompetitorsから削除する必要があります。

(一部の人々は、カスケードされていない外部キーが安全上の予防策であると考えていますが、私の経験では、それらは単なる見落としの結果ではなく、お尻のひどい痛みであり、一連のmake作業を作成しますDBAの場合。誤ってデータを削除する人に対処することが、「本当によろしいですか」ダイアログや、さまざまなタイプの定期的なバックアップ、冗長なデータソースなどを使用する理由です。データがすべてである競合他社を実際に削除することは、はるかに一般的ですたとえば、誤って削除して「ああ、そうするつもりはありませんでした。そして、今、彼らの競争結果はありません!ああああ!」と言うよりも、めちゃくちゃです。 、それに備える必要がありますが、前者の方がはるかに一般的です。したがって、前者のimoを準備する最も簡単で最適な方法は、外部キーをカスケード更新および削除するだけです。)


1

OK、これは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句を追加します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.