일반 삽입
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
LEFT [OUTER] JOIN
대신에를 사용하면에서 일치하는 항목이없는 경우의 [INNER] JOIN
행 val
이 삭제되지 않습니다foo
. 대신에 NULL
입력됩니다 foo_id
.
VALUES
하위 쿼리 의 표현식은 @ypercube의 CTE 와 동일 합니다. 공통 테이블 표현식 은 추가 기능을 제공하고 큰 쿼리에서 더 읽기 쉽지만 최적화 장벽으로도 사용됩니다. 따라서 하위 쿼리는 일반적으로 위의 어느 것도 필요하지 않을 때 약간 빠릅니다.
id
열 이름은 광범위한 안티 패턴입니다. 해야 foo_id
하고 bar_id
또는 아무것도 설명. 많은 테이블을 조인 할 때 이름이 id
... 인 여러 열이 생깁니다 .
대신 text
또는 일반을 고려하십시오 . 길이 제한을 실제로 적용해야하는 경우 제약 조건을 추가하십시오 .varchar
varchar(n)
CHECK
명시 적 유형 캐스트를 추가해야 할 수도 있습니다. 때문에 VALUES
식 직접 (같은 표에 부착되지 않는다 INSERT ... VALUES ...
), 유형 도출 될 수없고 기본 데이터 유형은 모든 예에서 작동하지 않을 수있는 타입 선언 명시하지 않고 사용된다. 첫 번째 행에서 충분하면 나머지는 줄을 지어 나옵니다.
INSERT에 FK 행이 동시에 누락되었습니다.
단일 SQL 문foo
에서 존재하지 않는 항목을 즉석 에서 작성하려는 경우 CTE는 중요한 도구입니다.
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
삽입 할 두 개의 새 더미 행에 유의하십시오. 둘 다 자주색foo
인데 아직 존재하지 않습니다 . 첫 번째 진술 의 필요성을 설명하기 위해 두 행 .DISTINCT
INSERT
단계별 설명
첫 번째 CTE sel
는 여러 행의 입력 데이터를 제공합니다. 부질 val
와 VALUES
표현은 소스로 테이블 또는 부질로 대체 될 수있다. 즉시 LEFT JOIN
하는 foo
추가하기 foo_id
위해 기존의 type
행. 다른 모든 행은 foo_id IS NULL
이 방법을 사용합니다.
두 번째 CTE는 고유 한 새 유형 ( )을에 ins
삽입 하고 새로 생성 된 -을 함께 삽입하여 행을 삽입합니다.foo_id IS NULL
foo
foo_id
type
최종 외부 INSERT
는 이제 모든 행에 대해 foo.id를 삽입 할 수 있습니다. 유형이 이미 존재하거나 2 단계에서 삽입되었습니다.
엄밀히 말하면 두 인서트는 "병렬로"발생하지만 이것이 단일 명령문이므로 기본 FOREIGN KEY
제한 조건은 불평하지 않습니다. 참조 무결성은 기본적으로 명령문 끝에 적용됩니다.
Postgres를위한 SQL Fiddle 9.3. (9.1에서 동일하게 작동합니다.)
이러한 쿼리를 여러 개 동시에 실행하면 경쟁 조건 이 매우 작 습니다 . 관련 질문에 대한 자세한 내용은 여기 와 여기 를 참조하십시오 . 실제로 동시로드가 많은 경우에만 발생합니다. 다른 답변으로 광고 된 것과 같은 솔루션 캐싱과 비교할 때, 기회는 매우 작습니다 .
반복 사용 기능
반복 사용을 위해 레코드 배열을 매개 변수로 사용 unnest(param)
하고 VALUES
표현식 대신 사용하는 SQL 함수를 작성합니다 .
또는 레코드 배열의 구문이 너무 복잡하면 쉼표로 구분 된 문자열을 parameter로 사용하십시오 _param
. 예를 들면 다음과 같습니다.
'description1,type1;description2,type2;description3,type3'
그런 다음 이것을 사용 VALUES
하여 위의 문장 에서 표현식 을 대체하십시오 .
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Postgres 9.5의 UPSERT 기능
매개 변수 전달을위한 사용자 정의 행 유형을 작성하십시오. 우리는 그것없이 할 수 있지만 더 간단합니다.
CREATE TYPE foobar AS (description text, type text);
기능:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
요구:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
동시 트랜잭션이있는 환경에 빠르고 강력합니다.
위의 쿼리 외에도이 ...
... 적용 SELECT
또는 INSERT
켜기 foo
: type
FK 테이블에 존재하지 않는 것이 삽입됩니다. 대부분의 유형이 이미 존재한다고 가정합니다. 경쟁 조건을 완전히 확인하고 배제하기 위해 필요한 기존 행이 잠기므로 동시 트랜잭션이 방해를받지 않습니다. 귀하의 경우에 너무 편집증 인 경우 다음을 교체 할 수 있습니다.
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
와
ON CONFLICT(type) DO NOTHING
... 적용 INSERT
또는 UPDATE
(true "UPSERT") on bar
: description
이미 존재하는 type
경우 업데이트됩니다.
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
그러나 type
실제로 변경 되는 경우에만 :
... VARIADIC
매개 변수를 사용하여 잘 알려진 행 유형을 값으로 전달합니다 . 기본 최대 값은 100입니다. 비교:
여러 행을 전달하는 다른 많은 방법이 있습니다 ...
관련 :