PostgreSQLに行が存在するかどうかを最速でチェック


177

テーブルに挿入する必要がある行がたくさんありますが、これらの挿入は常にバッチで行われます。したがって、バッチからの単一の行がテーブルに存在するかどうかを確認したいと思います。それは、それらがすべて挿入されたことがわかっているためです。

したがって、これは主キーのチェックではありませんが、あまり重要ではありません。私は単一の行のみをチェックしたいので、count(*)おそらく良くないので、それは次のようなものですexists私が推測です。

しかし、私はPostgreSQLにかなり慣れていないので、知っている人に尋ねたいと思います。

私のバッチには、次の構造の行が含まれています。

userid | rightid | remaining_count

したがって、テーブルに提供された行が含まれている場合userid、それらはすべてそこに存在することを意味します。


テーブルに行が含まれているか、バッチの行が含まれているかを確認したいですか?
JNK

私のバッチからの行はい。それらはすべて同じフィールドを共有していますが、少し編集します。
Valentin Kuzub、2011

質問を明確にしてください。レコードのバッチを全部追加したいですか、全部追加したくないですか?カウントについて何か特別なことはありますか?(ところで予約語、列名として実用的ではありません)
wildplasser '19

さて、私は実際の状況を少し単純化しようとしていましたが、実際の実装にますます近づいています。それらの行が挿入されると(_dateには別のフィールドがあります)、特定の権限を使用するため、指定されたユーザーの権限を減らし始めます。権限が0になると、その日付に対してそれらのアクションを実行できなくなります。それが本当の話
Valentin Kuzub '19 / 09/19

1
テーブル定義(の関連部分)を表示し、何をするつもりかを伝えてください。
wildplasser '19

回答:


345

TRUE / FALSEを返すには、EXISTSキーワードを使用します。

select exists(select 1 from contact where id=12)

21
これを拡張して、簡単に参照できるように返された列に名前を付けることができます。例えばselect exists(select 1 from contact where id=12) AS "exists"
ローワン

3
これは、期待する方法を拡張しない可能性がある(プログラミング言語に応じて)ときどきNoneではなく常に値(trueまたはfalse)を返すため、より優れています。
isaaclw 2014年

1
この方法を使用してSeq Scanを行っています。私は何か間違ったことをしますか?
FiftiN

2
@ Michael.MIには3,000万行のDBテーブルがあり、PostgresがIndex ScanではなくSeq Scanを使用しているため、使用しexistsたりlimit 1パフォーマンスが大幅に低下したりしています。そしてanalyze、助けにはなりません。
FiftiN 2016

2
@maciekは 'id'が主キーであることを理解してください。そのIDを持つレコードは1つしかないため、「LIMIT 1」は無意味です
StartupGuy

34

単純にどうですか:

select 1 from tbl where userid = 123 limit 1;

どこ 123で、挿入するバッチのユーザーIDです。

上記のクエリは、指定されたユーザーIDのレコードがあるかどうかに応じて、空のセットまたは単一行を返します。

これが遅すぎることが判明した場合は、でインデックスを作成することを検討してくださいtbl.userid

テーブルにバッチからの単一の行が存在する場合でも、すべての行が挿入されていることが確実であるため、行を挿入する必要はありません。

プログラムがバッチの途中で中断された場合でもこれを維持するには、データベーストランザクションを適切に管理することをお勧めします(つまり、バッチ全体が1つのトランザクション内に挿入されます)。


11
それは時々 0または1のカウント値(*)を持つ行を返す、それが常にに保証されますよう「(1 ...を選択リミット1)からSELECT COUNT(*)」プログラム的に簡単になるかもしれない
デヴィッド・オルドリッジ

@DavidAldridge count(*)は依然としてすべての行を読み取る必要があることを意味しますが、limit 1は最初のレコードで停止して戻ります
Imraan

3
@Imraanクエリを誤って解釈したと思います。COUNTネストされた上で作用するSELECT(ので、最大で1行を有するLIMITサブクエリです)。
jpmc26 2014年

9
INSERT INTO target( userid, rightid, count )
  SELECT userid, rightid, count 
  FROM batch
  WHERE NOT EXISTS (
    SELECT * FROM target t2, batch b2
    WHERE t2.userid = b2.userid
    -- ... other keyfields ...
    )       
    ;

ところで、重複した場合にバッチ全体を失敗させたい場合は、(主キー制約がある場合)

INSERT INTO target( userid, rightid, count )
SELECT userid, rightid, count 
FROM batch
    ;

成功するか失敗するかのどちらかです。


これにより、各行がチェックされます。彼は単一のチェックをしたいと考えています。
JNK、2011

1
いいえ、単一のチェックを行います。サブクエリは無相関です。一致するペアが1つ見つかると、救済されます。
wildplasser '19

そうです、私はそれが外側のクエリを参照していると思いました。あなたへの+1
JNK、2011

ところで、クエリはトランザクション内にあるため、重複したIDが挿入されても何も起こらないので、サブクエリは省略できます。
wildplasser '19

うーん、わかりません。権利が挿入された後、count列を減らし始めます。(画像の一部の詳細のみ)行が既に存在し、サブクエリが省略されている場合、重複する一意のキーがスローされるとエラーになると思いますか?(その一意のキーの
ユーザー

1
select true from tablename where condition limit 1;

これはpostgresが外部キーをチェックするために使用するクエリだと思います。

あなたの場合、あなたもこれを一度に行うことができます:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);

1

@MikeMが指摘したように。

select exists(select 1 from contact where id=12)

インデックスの接触で、それは通常、1ミリ秒に時間コストを削減することができます。

CREATE INDEX index_contact on contact(id);

0
SELECT 1 FROM user_right where userid = ? LIMIT 1

結果セットに行が含まれている場合は、挿入する必要はありません。それ以外の場合は、レコードを挿入してください。


束に100行が含まれている場合、100行が返されます、それでいいと思いますか?
Valentin Kuzub、2011

1行に制限できます。パフォーマンスが向上するはずです。そのために@aixからの編集された回答を見てください。
Fabian Barney

0

あなたがperformaceについて考えるなら、あなたはこのような関数で「PERFORM」を使うことができるかもしれません:

 PERFORM 1 FROM skytf.test_2 WHERE id=i LIMIT 1;
  IF FOUND THEN
      RAISE NOTICE ' found record id=%', i;  
  ELSE
      RAISE NOTICE ' not found record id=%', i;  
 END IF;

私と一緒に動作しません:パフォーマンスの近くで構文エラーが発生します
Simon

1
これはSQLではなくpl / pgsqlであるため、SQLとして実行しようとすると「PERFORM」の構文エラー
Mark K Cowan

-1

私は特にあなたの文章に対処するための別の考えを提案したいと思います:「私はので、バッチから単一の行がテーブルに存在するかどうかを確認したいので、私は彼らのすべてを知ってい挿入します。」

あなたは「バッチ」に挿入することで物事を効率的にしていますが、その後、存在チェックを実行して一度に1つのレコードをチェックしていますか?これは私には直観に反するようです。したがって、「挿入は常にバッチで行われると言った場合、1つの挿入ステートメントで複数のレコードを挿入していることになります。PostgresがACIDに準拠していることを理解する必要があります。1つのinsertステートメントで複数のレコード(データのバッチ)を挿入する場合、いくつかのレコードが挿入されたかどうかを確認する必要はありません。ステートメントは成功するか、失敗します。すべてのレコードが挿入されるか、何も挿入されません。

一方、C#コードがループなどで単に「個別の」setステートメントを実行している場合、これが「バッチ」であるということを頭に入れておくと、実際には「バッチ」と記述すべきではありません。挿入は常にバッチで行われます。」「バッチ」と呼ばれるもののその部分が実際には挿入されない可能性があるため、チェックの必要性を感じるという事実は、これが事実であることを強く示唆しています。この場合、より根本的な問題があります。1つの挿入で複数のレコードを実際に挿入するようにパラダイムを変更し、個々のレコードがそれを作成したかどうかを確認する必要はありません。

この例を考えてみましょう:

CREATE TABLE temp_test (
    id SERIAL PRIMARY KEY,
    sometext TEXT,
    userid INT,
    somethingtomakeitfail INT unique
)
-- insert a batch of 3 rows
;;
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 1, 1),
('bar', 2, 2),
('baz', 3, 3)
;;
-- inspect the data of what we inserted
SELECT * FROM temp_test
;;
-- this entire statement will fail .. no need to check which one made it
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 2, 4),
('bar', 2, 5),
('baz', 3, 3)  -- <<--(deliberately simulate a failure)
;;
-- check it ... everything is the same from the last successful insert ..
-- no need to check which records from the 2nd insert may have made it in
SELECT * FROM temp_test

これは実際、Postgresqlだけでなく、ACID準拠のDBのパラダイムです。言い換えれば、「バッチ」の概念を修正して、最初に行ごとのチェックを行わなくても済むようにした方がよいでしょう。

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