web-dev-qa-db-ja.com

挿入または選択後にIDを返す

電子メールの値がテーブルに存在しない場合に電子メールを挿入し、行のemail_idを返す関数を作成したいと思います。これどうやってするの?
また、メールが挿入されておらず、DBに既に存在する場合、IDを返すにはどうすればよいですか?別のSELECTを実行する必要がありますか?

BEGIN;
  LOCK TABLE mailing_list IN SHARE ROW EXCLUSIVE MODE;
  INSERT INTO mailing_list (email)
  SELECT 'email'
   WHERE NOT EXISTS (
     SELECT * FROM mailing_list WHERE email='email'
   );
COMMIT;

returning idを追加しようとしましたが、機能しません。私は得ました:

クエリには結果データの宛先がありません

Sqlfiddle

2
RockNinja

@ a_horseすでに説明済み 表示されたエラーメッセージを回避する方法。

以下は、これまで言及してきた関連バージョンの簡単なバリアントです。

CREATE OR REPLACE FUNCTION f_email_insel(_email text, OUT email_id int) AS
$func$
BEGIN

LOOP
   BEGIN  -- start inner block inside loop to handle possible exception

   SELECT INTO email_id  m.email_id FROM mailing_list m WHERE m.email = _email;

   IF NOT FOUND THEN
      INSERT INTO mailing_list (email) VALUES (_email)
      RETURNING mailing_list.email_id INTO email_id;
   END IF;

   EXCEPTION WHEN UNIQUE_VIOLATION THEN     -- inserted in concurrent session.
      RAISE NOTICE 'It actually happened!'; -- hardly ever happens
   END;

   EXIT WHEN email_id IS NOT NULL;          -- else keep looping
END LOOP;

END
$func$ LANGUAGE plpgsql;

SQLフィドル

考えられる競合状態を処理するために必要なループは次のとおりです。同時トランザクションがemailSELECTの間に同じINSERT値を書き込むと、一意の違反が発生します-ここで適切に処理されます。これは、明らかにUNIQUEに対するUNIQUE制約(またはemailインデックス)を想定しています。

CTEの代替oneSQLステートメントとして実行されます。したがって、ここではオーバーヘッドはわずかに小さくなっていますが(単純なクエリ)、競合状態の時間枠はわずかに大きくなっています。特に、行がすでに頻繁に存在する場合、これは少し高速です。
あちこちで詳細な説明を読み、ユースケースに最適なアプローチを選択してください。

3

返す値をどこかに保存する必要があります。

CREATE OR REPLACE FUNCTION f_get(ikey text)
  returns integer
  AS
$func$
DECLARE 
  l_id integer;
BEGIN
  LOCK TABLE foo IN SHARE ROW EXCLUSIVE MODE;
  INSERT INTO foo (type)
  SELECT ikey
   WHERE NOT EXISTS (
     SELECT * FROM foo WHERE type=ikey
   )
   returning id into l_id; --< store the returned ID in local variable
   return l_id; --< return this variable
END
$func$ LANGUAGE plpgsql;

元の関数とOUTパラメータを使用してそれを行うこともできます。その場合は、returning id into out_key;