web-dev-qa-db-ja.com

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

チームメンバーの名前とその順位を保持するCompetitions結果テーブルがあります。

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

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

今、私は最初のテーブルに約200,000の結果があり、競合テーブルが空のときこれを実行することができます:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

そして、クエリが約11,000の名前を挿入するのに約5秒しかかかりません。

今のところこれは重要なアプリケーションではないので、Competitorsテーブルを切り捨てる月に一度、私が約10,000行の新しいコンペ結果を受け取るときに考えることができます。

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

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

145
Didier Levy

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

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

もう1つの選択肢は、Resultsテーブルを既存の競合他社テーブルと結合したままにし、結合に一致しない個別のレコードをフィルタリングして新しい競合他社を見つけることです。

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

他の誰かがまだこれを言っていないのかわからない。

ノーマライズ。

競技をモデル化した表がありますか。競争は競合他社で構成されていますか?あなたは一つ以上のコンペティションに参加するライバルのリストが必要です......

次のような表があるはずです.....

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]

そして、あなたが新しい競争相手と新しい競争をするとき、あなたは単に競争相手テーブルの中に既に存在するものをチェックするだけです。それらがすでに存在しているなら、あなたはそれらの競争者のために競争者に挿入しないで、そして新しいもののために挿入をします。

次に、新しい競技会を競技会に挿入し、最後にすべてのリンクを競技会競技会に追加します。

32

テーブルを結合して、Competitorsにまだ存在していないユニークな競合他社のリストを取得する必要があります。

これにより、一意のレコードが挿入されます。

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

一意の名前の選択を待つことができずにこの挿入を迅速に行う必要があるときがあるかもしれません。その場合は、一意の名前を一時テーブルに挿入してから、その一時テーブルを使用して実際のテーブルに挿入できます。一時テーブルに挿入するときにすべての処理が行われるため、これはうまく機能します。したがって、実際のテーブルには影響しません。その後、すべての処理が終了したら、実際のテーブルにすばやく挿入します。トランザクション内で、実際のテーブルに挿入する最後の部分をラップすることもできます。

10
richard

Transact Charlieによって提案されているように、オペレーショナルテーブルを正規化することは良い考えであり、時間の経過とともに多くの頭痛と問題を解決します - のようなものがあります外部システムとの統合をサポートするインターフェーステーブル、および分析処理などをサポートするレポートテーブル。そして、そのような種類のテーブルは必ずしも正規化されている必要はありません - 実際、非常に多くの場合、はるかに便利です。彼らがではないことを実行してください。

この場合、私はあなたの業務用テーブルに対するTransact Charlieの提案が良いものだと思います。

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

CompetitionResultsには、競争の結果に含まれるデータをすべて含める必要があります。このようなインターフェイステーブルの要点は、ExcelシートやCSVファイル、あるいはそのデータを持っている形式から切り捨ててリロードすることをできる限り早く簡単にすることです。

そのインタフェーステーブルは、正規化された一連の操作テーブルの一部と見なされるべきではありません。次に、Richardによって提案されたCompetitionResultsと結合して、存在しない競合者にレコードを挿入できます。そして、そうするものを更新します(例えば、あなたが彼らの電話番号やEメールアドレスのようなあなたが実際に競合他社に関するより多くの情報を持っているなら)。

私が注目する1つのこと - 実際には、競合他社名は、私にはそう思われますが、あなたのデータの中でユニークであることはほとんどありません。たとえば、20万人の競合他社では、2人以上のDavid Smithがいるとします。だから私はあなたが彼らの電話番号やEメールアドレス、あるいはよりユニークである可能性が高い何かのようなあなたが競合他社からより多くの情報を集めることを勧めます。

あなたの業務テーブル、競合他社は、複合自然キーに寄与する各データ項目につき一つの列を持つべきです。たとえば、プライマリメールアドレスの列が1つあるはずです。ただし、インターフェーステーブルには、プライマリメールアドレス用のoldおよびnew値用のスロットが必要です。競合他社でレコードを検索し、その部分を新しい値に更新するために使用します。

したがって、CompetitionResultsにはいくつかの "old"および "new"フィールド(oldEmail、newEmail、oldPhone、newPhoneなど)が必要です。このようにして、CompetitorName、Email、およびPhoneからコンポジットで複合キーを形成できます。

競合結果がある場合は、Excelシートまたは自分が持っているものからCompetitionResultsテーブルを切り捨てて再ロードし、すべての新しい競合企業をCompetitorsテーブルに挿入するための単一の効率的な挿入、および更新のための単一の効率的な更新を実行できます。 CompetitionResultsから既存の競合他社に関するすべての情報。そして、あなたは新しい行をCompetitionCompetitorsテーブルに挿入するために単一の挿入をすることができます。これらのことは、CompetitionResultsテーブルをロードした後に実行できるProcessCompetitionResultsストアドプロシージャで実行できます。

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

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

(非カスケード外部キーは安全上の予防策であると考える人もいますが、私の経験ではそれらは単なる見落としの結果ではなく、おかしなことです。誤ってデータを削除してしまう人たちに対処することが、「確実です」ダイアログやさまざまな種類の定期的なバックアップ、冗長なデータソースなどがある理由です。たとえば、誤って削除してから「ああ、そうするつもりはありませんでした。そして今、私は彼らの競争結果を得ていません!Aaaahh!」と言っています。前者の方がはるかに一般的なので、前者のために準備する最も簡単で最良の方法は、外部キーを単に更新と削除をカスケードにすることです。)

3
Shavais

わかりました、これは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句をビュークエリに追加します。

1
Beervenger

正規化について話す上記の答えは素晴らしいです。しかし、データベースのスキーマや構造にそのまま触れることが許可されていない私のような立場にいると感じたらどうでしょうか。たとえば、DBAは「神々」であり、すべての提案された改訂は/ dev/nullになりますか。

その点で、私はこのように感じています このStack Overflowの投稿でも回答されています 上記のすべてのユーザーがコードサンプルを提供しています。

基礎となるデータベーステーブルを変更することはできないので、 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に関する元の答えのとおり、このコードは ここからコピーされたもの でした。

とにかく私の言いたいことは「ベストプラクティス」であり、理論だけでなく可能なこととできないことがよくあります。

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

がんばろう!

1
Thrawn Wannabe