programing

PostgreSQL에서 RETURNING with ON CONFRICT를 사용하는 방법은 무엇입니까?

oldcodes 2023. 4. 29. 09:52
반응형

PostgreSQL에서 RETURNING with ON CONFRICT를 사용하는 방법은 무엇입니까?

Postgre에 다음과 같은 UPSERT가 있습니다.SQL 9.5:

INSERT INTO chats ("user", "contact", "name") 
           VALUES ($1, $2, $3), 
                  ($2, $1, NULL) 
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;

충돌이 없으면 다음과 같은 것을 반환합니다.

----------
    | id |
----------
  1 | 50 |
----------
  2 | 51 |
----------

그러나 충돌이 있는 경우에는 행을 반환하지 않습니다.

----------
    | id |
----------

▁the다▁new니싶습을 반품하고 싶습니다.id 충돌이 의 "column"을 id충돌하는 열의 열입니다.
할 수 있을까요?만약 그렇다면, 어떻게?

현재 수락된 답변은 단일 충돌 대상, 적은 충돌, 작은 튜플 및 트리거가 없는 경우에 적합한 것 같습니다.동시성 문제 1(아래 참조)을 무차별적으로 방지합니다.간단한 해결책은 매력적이지만 부작용은 덜 중요할 수 있습니다.

그러나 다른 모든 경우에는 필요 없이 동일한 행을 업데이트하지 마십시오.표면에 차이가 보이지 않더라도 다음과 같은 다양한 부작용이 있습니다.

  • 실행하면 안 되는 트리거를 실행할 수 있습니다.

  • 또한 "무고한" 행을 쓰기 잠급니다. 동시 트랜잭션에 대한 비용이 발생할 수 있습니다.

  • 오래된 행(트랜잭션 타임스탬프)이지만 행이 새 행처럼 보일 수 있습니다.

  • 가장 중요한 것은, Postgre와 함께.SQL의 MVCC 모델 UPDATE에서는 행 데이터가 변경되었는지 여부에 관계없이 모든 대상 행에 대해 새 행 버전을 작성합니다.이는 UPSERT 자체에 대한 성능 페널티, 테이블 블롯, 인덱스 블롯, 테이블의 후속 작업에 대한 성능 페널티를 발생시킵니다.VACUUM비용. 중복되는 것은 거의 없지만 대부분의 사기꾼들에게는 큰 영향을 미칩니다.

게다가, 때때로 그것은 실용적이지 않거나 심지어 사용하는 것이 가능하지 않습니다.ON CONFLICT DO UPDATE설명서:

위해서ON CONFLICT DO UPDATE반드시 제공해야 합니다.

여러 인덱스/제약 조건이 포함된 경우 단일 "충돌 대상"은 불가능합니다.그러나 여러 부분 인덱스에 대한 관련 솔루션은 다음과 같습니다.

이 주제로 돌아가서, 빈 업데이트와 부작용 없이 거의 동일한 결과를 얻을 수 있습니다.다음 솔루션 중 일부는 다음과 함께 작동합니다.ON CONFLICT DO NOTHING발생할 수 있는 모든 충돌을 포착하는 이 바람직하거나 바람직하지 않을 수 있습니다.

동시 쓰기 로드 없음

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, ins AS (
   INSERT INTO chats (usr, contact, name) 
   SELECT * FROM input_rows
   ON CONFLICT (usr, contact) DO NOTHING
   RETURNING id  --, usr, contact              -- return more columns?
   )
SELECT 'i' AS source                           -- 'i' for 'inserted'
     , id  --, usr, contact                    -- return more columns?
FROM   ins
UNION  ALL
SELECT 's' AS source                           -- 's' for 'selected'
     , c.id  --, usr, contact                  -- return more columns?
FROM   input_rows
JOIN   chats c USING (usr, contact);           -- columns of unique index

source열은 이 기능이 작동하는 방식을 보여주는 추가 옵션입니다.실제로 두 경우의 차이를 구별하기 위해 필요할 수도 있습니다(빈 쓰기에 비해 또 다른 이점).

결승전JOIN chats연결된 데이터 수정 CTE에서 새로 삽입된 행이 아직 기본 테이블에 표시되지 않기 때문에 작동합니다. (같은 SQL 문의 모든 부분은 기본 테이블의 동일한 스냅샷을 봅니다.)

VALUES식은 자유롭게 서 있습니다(직접 연결되지 않음).INSERTPostgres는 대상 열에서 데이터 형식을 파생할 수 없으며 명시적 형식 캐스트를 추가해야 할 수 있습니다.설명서:

VALUES는 에사됨에서 됩니다.INSERT값은 모두 자동으로 해당 대상 열의 데이터 유형에 강제 적용됩니다.다른 컨텍스트에서 사용되는 경우 올바른 데이터 유형을 지정해야 할 수 있습니다.모든 항목이 따옴표로 묶인 리터럴 상수인 경우 첫 번째 항목을 강제로 지정하면 모든 항목에 대해 가정된 유형을 결정하기에 충분합니다.

CTE의 오버헤드와 추가로 인해, (부작용은 계산하지 않음) 쿼리 자체는 소수의 듀프들에게 약간 더 비쌀 수 있습니다.SELECT(완벽한 인덱스가 정의상 존재하기 때문에 저렴해야 합니다. - 고유한 제약 조건이 인덱스와 함께 구현됩니다.

많은 중복 항목의 경우 훨씬 더 빠를 수 있습니다.추가 쓰기의 효과적인 비용은 여러 요인에 따라 달라집니다.

하지만 어떤 경우에도 부작용과 숨겨진 비용이 적습니다.그것은 아마도 전체적으로 더 저렴할 것 같아요.

충돌 테스트 전에 기본값이 입력되므로 첨부된 시퀀스는 계속 진행됩니다.

CTE 정보:

동시 쓰기 로드 사용

기본 트랜잭션 분리를 가정합니다.관련:

경주 조건을 방어하는 최선의 전략은 정확한 요구사항, 테이블 및 UPSERT의 행 수와 크기, 동시 트랜잭션 수, 충돌 가능성, 가용 리소스 및 기타 요인에 따라 달라집니다.

동시성 문제 1

동시 트랜잭션이 현재 UPSERT를 시도하는 행에 쓴 경우, 다른 트랜잭션이 완료될 때까지 기다려야 합니다.

가 다른트션다로 ROLLBACK 오류, 즉 (는또모오류, 자동즉)ROLLBACK의 거래는 될 수 있습니다.), 당신의 거래는 정상적으로 진행됩니다.경미한 부작용: 순차적 숫자의 차이.하지만 누락된 행은 없습니다.

정상적으로 이거나 명시적인 경우)COMMIT), 당신의INSERT는 충돌을 감지합니다.".UNIQUE/ 및 index / 제약조건은 absolute입니다.DO NOTHING따라서 행을 반환하지 않습니다. (또한 아래의 동시성 이슈 2에서 설명한 것처럼 행이 보이지 않기 때문에 행을 잠글 수 없습니다.)SELECT쿼리 시작 시 동일한 스냅샷이 표시되며 아직 보이지 않는 행을 반환할 수도 없습니다.

이러한 행이 기본 테이블에 있더라도 결과 집합에 없습니다!

정도면 괜찮을 것 같습니다.특히 예제와 같이 행을 반환하지 않고 행이 있다는 것을 알고 만족하는 경우에는 더욱 그렇습니다.그것이 충분하지 않다면, 다양한 방법이 있습니다.

출력의 행 수를 확인하고 입력의 행 수와 일치하지 않으면 문을 반복할 수 있습니다.희귀한 경우에 충분할 수도 있습니다.요점은 새 쿼리(같은 트랜잭션에 있을 수 있음)를 시작하는 것입니다. 그러면 새로 커밋된 행이 표시됩니다.

또는 동일한 쿼리 내에서 누락된 결과 확인하고 알렉소니의 답변에서 입증된 무차별 대입 전략으로 해당 행을 덮어씁니다.

WITH input_rows(usr, contact, name) AS ( ... )  -- see above
, ins AS (
   INSERT INTO chats AS c (usr, contact, name) 
   SELECT * FROM input_rows
   ON     CONFLICT (usr, contact) DO NOTHING
   RETURNING id, usr, contact                   -- we need unique columns for later join
   )
, sel AS (
   SELECT 'i'::"char" AS source                 -- 'i' for 'inserted'
        , id, usr, contact
   FROM   ins
   UNION  ALL
   SELECT 's'::"char" AS source                 -- 's' for 'selected'
        , c.id, usr, contact
   FROM   input_rows
   JOIN   chats c USING (usr, contact)
   )
, ups AS (                                      -- RARE corner case
   INSERT INTO chats AS c (usr, contact, name)  -- another UPSERT, not just UPDATE
   SELECT i.*
   FROM   input_rows i
   LEFT   JOIN sel   s USING (usr, contact)     -- columns of unique index
   WHERE  s.usr IS NULL                         -- missing!
   ON     CONFLICT (usr, contact) DO UPDATE     -- we've asked nicely the 1st time ...
   SET    name = c.name                         -- ... this time we overwrite with old value
   -- SET name = EXCLUDED.name                  -- alternatively overwrite with *new* value
   RETURNING 'u'::"char" AS source              -- 'u' for updated
           , id  --, usr, contact               -- return more columns?
   )
SELECT source, id FROM sel
UNION  ALL
TABLE  ups;

, 로 한 를 더 합니다.ups우리가 완전한 결과 집합을 반환하기 전에.그 마지막 CTE는 대부분 아무것도 하지 않을 것입니다.반환된 결과에서 행이 누락된 경우에만 폭력을 사용합니다.

아직 오버헤드가 더 있습니다.기존 행과의 충돌이 많을수록 단순한 접근 방식을 능가할 가능성이 높아집니다.

한 가지 부작용: 두 번째 UPSERT는 행을 순서대로 쓰지 않으므로 동일한 행에 쓰는 트랜잭션이 3개 이상 중복될 경우 교착 상태가 발생할 가능성이 있습니다(아래 참조).문제가 발생하면 위에서 언급한 전체 설명을 반복하는 것과 같은 다른 해결책이 필요합니다.

동시성 이슈 2

동시 트랜잭션이 영향을 받는 행의 관련 열에 쓸 수 있고 발견한 행이 동일한 트랜잭션의 나중 단계에 여전히 있는지 확인해야 하는 경우 CTE에서 기존 행을 저렴하게 잠글있습니다.ins 않으면 잠금이 됨)와 함께 사용합니다.

...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE  -- never executed, but still locks the row
...

그리고 잠금 절을 추가합니다.

이렇게 하면 모든 잠금이 해제될 때까지 경쟁적인 쓰기 작업이 트랜잭션이 끝날 때까지 기다립니다.간단히 말해봐요.

자세한 내용 및 설명:

교착 상태?

행을 일관된 순서로 삽입하여 교착 상태를 방지합니다.참조:

데이터 유형 및 캐스트

데이터 유형의 템플릿으로 사용되는 기존 테이블...

실행형 데이터의 첫 번째 에 VALUES표현이 불편할 수 있습니다.거기에는 여러 가지 방법이 있습니다.기존 관계(테이블, 뷰 등)를 행 템플릿으로 사용할 수 있습니다.대상 표는 사용 사례에 대한 명확한 선택입니다.입력 데이터는 다음과 같이 자동으로 적절한 유형으로 강제됩니다.VALUESINSERT:

WITH input_rows AS (
  (SELECT usr, contact, name FROM chats LIMIT 0)  -- only copies column names and types
   UNION ALL
   VALUES
      ('foo1', 'bar1', 'bob1')  -- no type casts here
    , ('foo2', 'bar2', 'bob2')
   )
   ...

일부 데이터 유형에는 사용할 수 없습니다.참조:

및 이름

이는 모든 데이터 유형에도 적용됩니다.

표의 모든 (앞에 있는) 열에 삽입하는 동안 열 이름을 생략할 수 있습니다. 가표chatsUPSERT에 되어 있습니다. UPSERT는 3개의 열을 사용합니다.

WITH input_rows AS (
   SELECT * FROM (
      VALUES
      ((NULL::chats).*)         -- copies whole row definition
      ('foo1', 'bar1', 'bob1')  -- no type casts needed
    , ('foo2', 'bar2', 'bob2')
      ) sub
   OFFSET 1
   )
   ...

다음과 같은 예약된 단어를 사용하지 마십시오."user"총입니다그것은 장전된 총입니다.따옴표 없이 소문자로 묶지 않은 합법적인 식별자를 사용합니다.을 로대했니다습으로 했습니다.usr.

저도 똑같은 문제가 있었는데, 업데이트할 것이 없는데도 '아무것도 하지 않는다'가 아닌 '업데이트를 한다'를 사용하여 해결했습니다.당신의 경우는 다음과 같습니다.

INSERT INTO chats ("user", "contact", "name") 
       VALUES ($1, $2, $3), 
              ($2, $1, NULL) 
ON CONFLICT("user", "contact") 
DO UPDATE SET 
    name=EXCLUDED.name 
RETURNING id;

이 쿼리는 방금 삽입되었거나 이전에 존재했던 행에 관계없이 모든 행을 반환합니다.

WITH e AS(
    INSERT INTO chats ("user", "contact", "name") 
           VALUES ($1, $2, $3), 
                  ($2, $1, NULL) 
    ON CONFLICT("user", "contact") DO NOTHING
    RETURNING id
)
SELECT * FROM e
UNION
    SELECT id FROM chats WHERE user=$1, contact=$2;

목적요를 사용하는 ON CONFLICT DO NOTHING스로우 오류를 방지하기 위한 것이지만 행 반환은 발생하지 않습니다.그래서 우리는 다른 것이 필요합니다.SELECT기존 ID를 가져옵니다.

시도 반환하지 두 SQL은 반환되지 않습니다. 그러면 두 번째가SELECT행을 . 가 두 개 . 기존행가져다가 합니다. 성공적으로 삽입되면 동일한 레코드가 두 개 있을 것입니다. 그러면 필요합니다.UNION결과를 병합합니다.

업하트, 그것의 확장이 되는 것.INSERT 조건 시 두 할 수 있습니다.DO NOTHING또는DO UPDATE.

INSERT INTO upsert_table VALUES (2, 6, 'upserted')
   ON CONFLICT DO NOTHING RETURNING *;

 id | sub_id | status
----+--------+--------
 (0 rows)

튜플이 삽입되지 않았기 때문에 아무 것도 반환하지 않습니다.자 이제DO UPDATE충돌이 발생한 튜플에서 작업을 수행할 수 있습니다.먼저 충돌이 있음을 정의하는 데 사용할 제약 조건을 정의하는 것이 중요합니다.

INSERT INTO upsert_table VALUES (2, 2, 'inserted')
   ON CONFLICT ON CONSTRAINT upsert_table_sub_id_key
   DO UPDATE SET status = 'upserted' RETURNING *;

 id | sub_id |  status
----+--------+----------
  2 |      2 | upserted
(1 row)

단일 항목을 삽입하는 경우 ID를 반환할 때 병합을 사용합니다.

WITH new_chats AS (
    INSERT INTO chats ("user", "contact", "name")
    VALUES ($1, $2, $3)
    ON CONFLICT("user", "contact") DO NOTHING
    RETURNING id
) SELECT COALESCE(
    (SELECT id FROM new_chats),
    (SELECT id FROM chats WHERE user = $1 AND contact = $2)
);

항목을 할 때는 로 여항을삽는경값우임넣수있다에 을 넣을 수.WITH나중에 참조하십시오.

WITH chats_values("user", "contact", "name") AS (
    VALUES ($1, $2, $3),
           ($4, $5, $6)
), new_chats AS (
    INSERT INTO chats ("user", "contact", "name")
    SELECT * FROM chat_values
    ON CONFLICT("user", "contact") DO NOTHING
    RETURNING id
) SELECT id
    FROM new_chats
   UNION
  SELECT chats.id
    FROM chats, chats_values
   WHERE chats.user = chats_values.user
     AND chats.contact = chats_values.contact;

참고: Erwin의 의견에 따르면, 응용 프로그램이 동일한 데이터를 동시에 '업서트'하려고 할 가능성이 낮습니다(삽입을 시도하는 두 명의 작업자)<unique_field> = 1이와 동시에), 그리고 이러한 데이터는 아직 테이블에 존재하지 않습니다. '업셋'을 실행하기 전에 트랜잭션의 격리 수준을 변경해야 합니다.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

이 경우 두 트랜잭션 중 하나가 중단됩니다.응용 프로그램에서 이 경우가 자주 발생하는 경우 두 개의 개별 쿼리만 수행하는 것이 좋습니다. 그렇지 않으면 오류를 처리하고 쿼리를 다시 수행하는 것이 더 쉽고 빠릅니다.

단일 행을 뒤집는 것만 원하는 경우

그런 다음 단순성을 사용하여 작업을 상당히 단순화할 수 있습니다.EXISTS선택사항:

WITH
  extant AS (
    SELECT id FROM chats WHERE ("user", "contact") = ($1, $2)
  ),
  inserted AS (
    INSERT INTO chats ("user", "contact", "name")
    SELECT $1, $2, $3
    WHERE NOT EXISTS (SELECT FROM extant)
    RETURNING id
  )
SELECT id FROM inserted
UNION ALL
SELECT id FROM extant

가 없기 에.ON CONFLICT절에는 업데이트가 없으며 필요한 경우에만 삽입됩니다.따라서 불필요한 업데이트, 불필요한 쓰기 잠금, 불필요한 시퀀스 증가가 없습니다.깁스도 필요 없습니다.

에 있는 이었다면, 쓰기잠사기사면다었능이의례용금이,를 할 수 .SELECT FOR UPDATE에 시대에extant표현.

새 할 단계의 플래그 열을 할 수 .UNION:

SELECT id, TRUE AS inserted FROM inserted
UNION ALL
SELECT id, FALSE FROM extant

위의 어윈의 대답을 기반으로 하여 (대단한 대답이지만, 그것이 없었다면 결코 여기에 오지 못했을 것이다!), 이것이 제가 결국 여기에 도달한 이유입니다.몇 가지 추가적인 잠재적 문제를 해결합니다. 즉, 다음과 같은 작업을 수행하여 중복(그렇지 않으면 오류가 발생할 수 있음)을 허용합니다.select distinct입력 세트에서 반환된 ID가 동일한 순서와 중복 허용을 포함하여 입력 세트와 정확히 일치하는지 확인합니다.

추가적으로, 그리고 나에게 중요한 한 부분은, 그것은 그것을 사용하여 불필요한 시퀀스 진보의 수를 크게 줄였습니다.new_rowsCTE는 이미 없는 것만 삽입하려고 합니다.동시 쓰기의 가능성을 고려하면 축소된 집합에서 일부 충돌이 발생하지만 이후 단계에서는 이를 처리합니다.의 경우,의 차이는할 때,과 함께, 은 대분의경우부만, 차않지되지가큰문충, 을 사용하는 를 만들 수 있습니다.int 는또.bigint신분증으로

크고 못생겼음에도 불구하고, 그것은 매우 잘 수행합니다.저는 수백만 번의 혼란, 높은 동시성, 높은 충돌 횟수로 광범위하게 테스트했습니다.바위처럼 단단한.

함수로 패키지화했지만, 원하는 것이 아니라면 순수 SQL로 변환하는 방법을 쉽게 확인할 수 있을 것입니다.샘플 데이터도 간단한 것으로 변경했습니다.

CREATE TABLE foo
(
  bar varchar PRIMARY KEY,
  id  serial
);
CREATE TYPE ids_type AS (id integer);
CREATE TYPE bars_type AS (bar varchar);

CREATE OR REPLACE FUNCTION upsert_foobars(_vals bars_type[])
  RETURNS SETOF ids_type AS
$$
BEGIN
  RETURN QUERY
    WITH
      all_rows AS (
        SELECT bar, ordinality
        FROM UNNEST(_vals) WITH ORDINALITY
      ),
      dist_rows AS (
        SELECT DISTINCT bar
        FROM all_rows
      ),
      new_rows AS (
        SELECT d.bar
        FROM dist_rows d
             LEFT JOIN foo f USING (bar)
        WHERE f.bar IS NULL
      ),
      ins AS (
        INSERT INTO foo (bar)
          SELECT bar
          FROM new_rows
          ORDER BY bar
          ON CONFLICT DO NOTHING
          RETURNING bar, id
      ),
      sel AS (
        SELECT bar, id
        FROM ins
        UNION ALL
        SELECT f.bar, f.id
        FROM dist_rows
             JOIN foo f USING (bar)
      ),
      ups AS (
        INSERT INTO foo AS f (bar)
          SELECT d.bar
          FROM dist_rows d
               LEFT JOIN sel s USING (bar)
          WHERE s.bar IS NULL
          ORDER BY bar
          ON CONFLICT ON CONSTRAINT foo_pkey DO UPDATE
            SET bar = f.bar
          RETURNING bar, id
      ),
      fin AS (
        SELECT bar, id
        FROM sel
        UNION ALL
        TABLE ups
      )
    SELECT f.id
    FROM all_rows a
         JOIN fin f USING (bar)
    ORDER BY a.ordinality;
END
$$ LANGUAGE plpgsql;

가장 단순하고 성능이 뛰어난 솔루션은

BEGIN;

INSERT INTO chats ("user", contact, name) 
    VALUES ($1, $2, $3), ($2, $1, NULL) 
ON CONFLICT ("user", contact) DO UPDATE
  SET name = excluded.name
  WHERE false
RETURNING id;

SELECT id
FROM chats
WHERE (user, contact) IN (($1, $2), ($2, $1));

COMMIT;

DO UPDATE WHERE false는 다른 트랜잭션에서 행을 삭제할 수 없도록 하기 때문에 행을 잠그지만 업데이트하지 않습니다. 이는 버그가 아닌 기능입니다.

일부 주석은 업데이트된 행과 생성된 행을 구분하려고 합니다.

때는 이경우, 다을추니다가합음을 추가하면 .txid_current() = xmin AS created선발된 사람에게

저는 Erwin Brandstetter의 놀라운 답변을 수정했습니다. 이 답변은 시퀀스를 늘리지 않고 행도 잠그지 않습니다.저는 비교적 Postgre가 처음입니다.SQL, 이 방법에 대한 단점이 있으면 언제든지 알려주시기 바랍니다.

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, new_rows AS (
   SELECT 
     c.usr
     , c.contact
     , c.name
     , r.id IS NOT NULL as row_exists
   FROM input_rows AS r
   LEFT JOIN chats AS c ON r.usr=c.usr AND r.contact=c.contact
   )
INSERT INTO chats (usr, contact, name)
SELECT usr, contact, name
FROM new_rows
WHERE NOT row_exists
RETURNING id, usr, contact, name

가 다음과 같이 합니다.chats에 조건이 .(usr, contact).

업데이트: 스파타에서 제안된 수정사항이 추가되었습니다(아래).감사합니다!

그러나 Revin과 코멘트에 따른 또 다른 업데이트:

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, new_rows AS (
   INSERT INTO chats (usr, contact, name)
   SELECT 
     c.usr
     , c.contact
     , c.name
   FROM input_rows AS r
   LEFT JOIN chats AS c ON r.usr=c.usr AND r.contact=c.contact
   WHERE r.id IS NULL
   RETURNING id, usr, contact, name
   )
SELECT id, usr, contact, name, 'new' as row_type
FROM new_rows
UNION ALL
SELECT id, usr, contact, name, 'update' as row_type
FROM input_rows AS ir
INNER JOIN chats AS c ON ir.usr=c.usr AND ir.contact=c.contact

위의 내용을 테스트하지 않았지만 새로 삽입한 행이 여러 번 반환되는 것을 발견할 경우 다음 중 하나를 변경할 수 있습니다.UNION ALLUNION또는 (더 나은) 첫 번째 쿼리를 모두 제거합니다.

필드를 현재 값으로 업데이트합니다.이 경우 충돌하는 행의 데이터는 전혀 변경되지 않고 다음을 반환합니다.id빈 집합 대신 충돌하는 행의.

INSERT INTO chats c ("user", "contact", "name") 
       VALUES ($1, $2, $3), 
              ($2, $1, NULL) 
ON CONFLICT("user", "contact") DO UPDATE
SET user = c.user
RETURNING id

언급URL : https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql

반응형