SQL Server-レコードを挿入し、一意であることを確認する方法
-
06-07-2019 - |
質問
アイテムがまだ存在しない場合にのみ、単一のテーブルにレコードを挿入する最良の方法を見つけようとしています。この場合のKEYはNVARCHAR(400)フィールドです。この例では、Oxford English Dictionaryの word の名前のふりをして/ fav辞書をここに挿入します。また、Wordフィールドを主キーにする必要があると思います。 (テーブルには一意の識別子PKもあります)。
だから..私はテーブルに追加する必要があるこれらの単語を取得するかもしれません...
eg。
- 猫
- 犬
- Foo
- バー
- PewPew
- etc ...
だから伝統的に、私は次のようにします(擬似コード)
SELECT WordID FROM Words WHERE Word = @Word
IF WordID IS NULL OR WordID <= 0
INSERT INTO Words VALUES (@Word)
ie。単語が存在しない場合は挿入します。
今..私が心配している問題は、ヒットがたくさんあるということです。そのため、SELECTとINSERTの間に別のプロセスから単語が挿入される可能性があります。制約エラー? (つまり、 Race Condition )。
その後、次のことができると思いました...
INSERT INTO Words (Word)
SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)
基本的には、存在しない単語を挿入します。
悪い構文はさておき、これがテーブルをロックダウンするためにそれが悪いのか良いのかはわかりません(もしそうなら)、大量の読み取りと書き込みが大量に行われるテーブルではパフォーマンスが良くありません。
だから-あなたは、SQLの達人は何を考え/何をしますか?
スローされたエラーに対して、単純な挿入と「キャッチ」を望んでいました。
解決
あなたの解決策:
INSERT INTO Words (Word)
SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)
...取得するのとほぼ同じくらいです。これに単純化できます:
INSERT INTO Words (Word)
SELECT @Word
WHERE NOT EXISTS (SELECT * FROM Words WHERE Word = @Word)
... EXISTSは実際にはレコードを返す必要がないため、クエリオプティマイザーは要求したフィールドを確認しません。
ただし、これはINSERT中にテーブル全体をロックするため、これは特にパフォーマンスがよくありません。ただし、一意のインデックス(主キーである必要はありません)をWordに追加する場合は、関連するページをロックするだけで済みます。
最良のオプションは、予想される負荷をシミュレートし、SQL Server Profilerでパフォーマンスを確認することです。他のフィールドと同様に、時期尚早な最適化は悪いことです。許容可能なパフォーマンスメトリックを定義し、他の作業を行う前に測定します。
それでも十分なパフォーマンスが得られない場合は、データウェアハウジングの分野で役立つテクニックがたくさんあります。
他のヒント
これに対するより良い(または少なくともより速い)答えを見つけたと思います。 次のようなインデックスを作成します。
CREATE UNIQUE NONCLUSTERED INDEX [IndexTableUniqueRows] ON [dbo].[table]
(
[Col1] ASC,
[Col2] ASC,
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = ON, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
一意性を定義するすべての列を含めます。重要な部分はIGNORE_DUP_KEY = ONです。これにより、一意でない挿入が警告に変わります。 SSISはこれらの警告を無視しますが、fastloadも使用できます。
MS SQL Serverを使用している場合、一意にする必要があるテーブルの列に一意のインデックスを作成できます(文書化されたこちら):
CREATE UNIQUE [ CLUSTERED | NONCLUSTERED ] INDEX <index_name>
ON Words ( word [ ASC | DESC ])
ケースに応じて、 Clustered
または NonClustered
を指定します。また、(より高速なシークを可能にするために)ソートする場合は、ソート順序に ASC
または DESC
を指定します。
インデックスアーキテクチャの詳細については、こちらをご覧ください。
それ以外の場合は、文書化されたUNIQUE CONSTRAINTS を使用できます。 >こちら:
ALTER TABLE Words
ADD CONSTRAINT UniqueWord
UNIQUE (Word);
同様の問題があり、これが解決方法です
insert into Words
( selectWord , Fixword)
SELECT word,'theFixword'
FROM OldWordsTable
WHERE
(
(word LIKE 'junk%') OR
(word LIKE 'orSomthing')
)
and word not in
(
SELECT selectWord FROM words WHERE selectWord = word
)
一意の制約は確かに正しい方法の1つですが、挿入ロジックにもこれを使用できます。 http://www.sqlteam.com/ article / application-locks-or-mutexes-in-sql-server-2005
基本的に、下のテーブルにはロックを設定しないため、読み取りを心配しません 存在チェックは正常に実行されます。
これは、SQLコードのmutexです。
MS SQLの詳細について話すことはできませんが、SQLの主キーの1つのポイントは、一意性を確保することです。したがって、一般的なSQL用語の定義では、主キーはテーブルに固有の1つ以上のフィールドです。この動作を強制するさまざまな方法がありますが(古いエントリを新しいエントリに置き換えるか、新しいエントリを拒否するか)、MS SQLの両方にこの動作を強制するメカニズムがなく、そうでない場合は驚くでしょう新しいエントリを拒否します。主キーをWordフィールドに設定し、それが機能する必要があることを確認してください。
ただし、MySQLプログラミングとデータベースクラスに関する知識からこれがすべて否認されるので、MS SQLの複雑さについてはおpびします。
declare @Error int
begin transaction
INSERT INTO Words (Word) values(@word)
set @Error = @@ERROR
if @Error <> 0 --if error is raised
begin
goto LogError
end
commit transaction
goto ProcEnd
LogError:
rollback transaction