MySQL에 '존재하지 않는 경우 삽입'하는 방법?


838

인터넷 검색으로 시작하여 뮤텍스 테이블에 대해 이야기하는 이 기사 를 찾았습니다 .

~ 1400 만 개의 레코드가있는 테이블이 있습니다. 동일한 형식으로 더 많은 데이터를 추가하려면 한 쌍의 쿼리를 사용하지 않고 삽입하려는 레코드가 없는지 확인하는 방법이 있습니까? 빈)?

unique필드에 대한 제약 insert이 이미 존재하는 경우 실패를 보장 합니까?

PHP를 통해 삽입을 발행하면 스크립트가 잠기는 것처럼 단지 제약 조건 이있는 것 같습니다 .



auto_inc 값을 굽지 않는 것에 대한 설명은 stackoverflow.com/questions/44550788/… 를 참조하십시오 .
Rick James

@ RickJames-그것은 흥미로운 q .. 그러나 그것이이 q와 직접적으로 관련되어 있는지 확실하지 않습니다 :)
warren

1
의견에서 언급되었으며 다른 질문은이 질문이 "정확한 복제본"이라고 주장했습니다. 그래서 다른 사람들의 이익을 위해 질문들을 함께 연결하는 것이 좋은 생각이라고 생각했습니다.
Rick James

1
아, 사이드 바를 보지 않을 것입니다.
Rick James

답변:


806

사용하다 INSERT IGNORE INTO table

http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html 참조

INSERT … ON DUPLICATE KEY UPDATE구문 도 있습니다. dev.mysql.com에서 설명을 찾을 수 있습니다.


Google의 웹 캐시에 따라 bogdan.org.ua에서 게시 :

2007 년 10 월 18 일

시작하려면 : 최신 MySQL부터는 제목에 표시된 구문이 불가능합니다. 그러나 기존 기능을 사용하여 예상되는 것을 달성하는 매우 쉬운 방법이 몇 가지 있습니다.

중복 키 업데이트에 INSERT IGNORE, REPLACE 또는 INSERT…

테이블이 있다고 상상해보십시오.

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

이제 Ensembl에서 자동 메타 데이터 가져 오기 스크립트 메타 데이터를 파이프 라인에 가져오고 여러 가지 이유로 파이프 라인이 실행 단계에서 중단 될 수 있다고 상상해보십시오. 따라서 우리는 두 가지를 보장해야합니다.

  1. 파이프 라인을 반복해서 실행해도 데이터베이스가 손상되지 않습니다

  2. '중복 키'오류로 인해 반복 된 실행이 종료되지 않습니다.

방법 1 : REPLACE 사용

매우 간단합니다.

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

레코드가 있으면 덮어 씁니다. 아직 존재하지 않으면 작성됩니다. 그러나이 방법을 사용하는 것은 효율적이지 않습니다. 기존 레코드를 덮어 쓸 필요가 없으며 그냥 건너 뛰는 것이 좋습니다.

방법 2 : INSERT IGNORE 사용하기 매우 간단합니다.

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

여기서 'ensembl_transcript_id'가 데이터베이스에 이미 존재하면 자동으로 건너 뜁니다 (무시). (더 정확하게 말하면, MySQL 참조 매뉴얼에서 인용 한 내용은 다음과 같습니다.“IGNORE 키워드를 사용하는 경우 INSERT 문을 실행하는 동안 발생하는 오류는 대신 경고로 처리됩니다. 예를 들어 IGNORE가 없으면 기존 UNIQUE 인덱스를 복제하는 행 또는 테이블의 PRIMARY KEY 값으로 인해 중복 키 오류가 발생하고 명령문이 중단됩니다.”) 레코드가 아직 없으면 레코드가 작성됩니다.

이 두 번째 방법에는 다른 문제가 발생할 경우 쿼리 중단을 포함하여 여러 가지 잠재적 약점이 있습니다 (설명서 참조). 따라서 이전에 IGNORE 키워드없이 테스트 한 경우 사용해야합니다.

방법 3 : INSERT… ON DUPLICATE KEY UPDATE 사용 :

세 번째 옵션은 INSERT … ON DUPLICATE KEY UPDATE 구문 을 사용 하는 것이며, UPDATE 부분에서는 0 + 0 계산과 같이 의미없는 (빈) 작업을 수행하지 않습니다 (Geoffray는이 작업을 무시하기 위해 MySQL 최적화 엔진에 id = id 할당을 제안합니다). 이 방법의 장점은 중복 키 이벤트 만 무시하고 다른 오류로 인해 중단된다는 것입니다.

마지막 공지 :이 게시물은 Xaprb에서 영감을 받았습니다. 또한 유연한 SQL 쿼리 작성에 대한 다른 게시물을 참조하는 것이 좋습니다.


3
스크립트 속도를 높이기 위해 "지연"과 함께 사용할 수 있습니까?
워렌

3
예, 삽입 지연은 작업 속도를 높일 수 있습니다. 그것을보십시오
knittl


10
INSERT … ON DUPLICATE KEY UPDATEauto_increment과 데이터를 보존하면서 행을 삭제하지 않기 때문에 더 좋습니다 .
redolent

15
모두에게 알리기 위해. INSERT … ON DUPLICATE KEY UPDATE메소드를 사용하면 삽입 실패로 AUTO_INCREMENT 컬럼이 증가합니다. 아마도 실패하지 않았지만 업데이트 되었기 때문일 것입니다.
not2qubit

216

해결책:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

설명:

가장 안쪽 쿼리

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

애즈 사용 WHERE NOT EXISTS데이터를 삽입하여 이미 행이 존재한다면 -condition 검출한다. 이러한 종류의 행이 발견되면 쿼리가 중지 될 수 있으므로 LIMIT 1(마이크로 최적화, 생략 될 수 있음)

중간 쿼리

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

삽입 할 값을 나타냅니다. DUAL모든 Oracle 데이터베이스에 기본적으로 존재하는 특수한 행, 하나의 열 테이블을 나타냅니다 ( https://en.wikipedia.org/wiki/DUAL_table 참조 ). MySQL-Server 버전 5.7.26에서 생략 할 때 유효한 쿼리가 FROM DUAL있지만 5.5.60과 같은 이전 버전에는 FROM정보 가 필요한 것 같습니다 . WHERE NOT EXISTS가장 안쪽 쿼리가 일치하는 데이터를 찾은 경우 중간 쿼리 를 사용 하여 빈 결과 집합을 반환합니다.

외부 쿼리

INSERT INTO `table` (`value1`, `value2`) 

중간 쿼리에서 데이터가 반환되면 데이터를 삽입합니다.


4
이것을 사용하는 방법에 대한 더 많은 정보를 줄 수 있습니까?
Alex V

36
테이블에는 고유 키가 존재하지 않는 (경우에 변형 적합 INSERT IGNOREINSERT ON DUPLICATE KEY고유 키 제약 조건을 필요로)
rabudde

2
"from table"대신 2 행에서 "dual"을 사용하는 경우 "limit 1"절이 필요하지 않습니다.
Rich

6
어떤 경우 stuff for value1stuff for value2동일하다? 이것은 던질 것입니다Duplicate column name
Robin

1
하위 쿼리 SELECT 1대신에 훨씬 선호 합니다 SELECT *. 이것이 인덱스에 의해 만족 될 가능성이 훨씬 높습니다.
Arth

58

중복 키 업데이트 또는 insert ignore 는 MySQL에서 실행 가능한 솔루션 일 수 있습니다.


mysql.com을 기반으로 하는 중복 키 업데이트 업데이트의 예

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

mysql.com을 기반으로 한 insert ignore 예제

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

또는:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

또는:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

24

예외가 허용되는 경우 간단한 제한 조건이 작업을 수행해야합니다. 예 :

  • 대리하지 않은 경우 기본 키
  • 열에 대한 고유 제한
  • 다중 열 고유 제한 조건

죄송합니다. 이것은 매우 간단합니다. 당신이 우리와 공유하는 링크에 맞지 않는 것 같습니다. ;-(

그러나 나는 이것이 당신의 필요를 채우는 것처럼 보이기 때문에 결코이 대답을하지 않습니다. (그렇지 않으면 요구 사항 업데이트가 트리거 될 수 있으며, 이는 "좋은 것"이기도합니다).

편집 됨 : 삽입으로 인해 데이터베이스 고유 제한 조건이 중단되면 데이터베이스 레벨에서 드라이버에 의해 릴레이되는 예외가 발생합니다. 실패하면 스크립트가 확실히 중지됩니다. PHP에서 그러한 경우를 해결할 수 있어야합니다 ...


1
질문에 설명을 추가했습니다. 답변이 계속 적용됩니까?
워렌

2
나는 그것을 믿는다. 고유 한 제한 조건으로 인해 잘못된 삽입이 실패합니다. 참고 : 코드 에서이 실패를 처리해야하지만 이것은 표준입니다.
KLE September

1
지금은 내가 받아 들인 솔루션을 고수 할 것입니다. 그러나 앱이 커질 때 INSERT 실패 등을 처리 할 것입니다.
warren September

3
INSERT IGNORE기본적으로 스크립트가 중단되지 않도록 모든 오류를 경고로 변경합니다. 그런 다음 명령을 사용하여 모든 경고를 볼 수 있습니다 SHOW WARNINGS. 그리고 또 다른 중요한 참고 사항 : UNIQUE 제약 조건은 NULL 값으로 작동하지 않습니다. row1 (1, NULL) 및 row2 (1, NULL)는 모두 기본 키와 같은 다른 제한 조건이 깨지지 않는 한 삽입됩니다. 불행한 사람.
Simon East

18

다음은 지정된 모든 열 값이 테이블에 존재하지 않는 경우에만 행을 삽입하는 PHP 함수입니다.

  • 열 중 하나가 다르면 행이 추가됩니다.

  • 테이블이 비어 있으면 행이 추가됩니다.

  • 지정된 모든 열에 지정된 값이있는 행이 있으면 행이 추가되지 않습니다.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }

사용법 예 :

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>

5
삽입 량이 많은 경우 꽤 비쌉니다.
Эџad Дьdulяңмaи

특정 검진을 추가해야하는 경우에는 사실이지만 효율적입니다
Charles Forest

1
경고 : mysql_* 확장은 PHP 5.5.0부터 더 이상 사용되지 않으며 PHP 7.0.0부터 제거되었습니다. 대신 mysqli 또는 PDO_MySQL 확장을 사용해야합니다. MySQL API 를 선택하는 동안 추가 도움이 필요 하면 MySQL API 개요 도 참조하십시오 .
Dharman

17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

레코드가 있으면 덮어 씁니다. 아직 존재하지 않으면 작성됩니다.


10
REPLACE행을 삭제 한 다음 업데이트 대신 삽입 할 수 있습니다. 부작용은 제약 조건으로 인해 다른 객체가 삭제되고 트리거가 삭제 될 수 있다는 것입니다.
xmedeko

1
MySQL 매뉴얼에서 : "REPLACE는 테이블에 PRIMARY KEY 또는 UNIQUE 인덱스가있는 경우에만 의미가 있습니다. 그렇지 않으면 새 행이 다른 행을 복제하는지 여부를 판별하는 데 사용할 인덱스가 없으므로 INSERT와 같습니다."
BurninLeo

16

다음을 시도하십시오 :

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END

5
시험해보십시오.이 답변은 OP 및 수천 명의 미래 연구원을 교육하는 데 거의 도움이되지 않기 때문에 StackOverflow에서 가치가 낮습니다. 솔루션의 작동 방식과 좋은 이유를 포함하도록이 답변을 편집하십시오.
mickmackusa

1
일치하는 필드가 키가 아닌 경우 완벽한 솔루션 ..!
레오

6

또는로 UNIQUE확인할 수 있는 색인 이있는 경우이 문제를 해결하는 방법을 다루는 몇 가지 답변이 있습니다 . 항상 그런 것은 아니며 길이 제한 (1000 바이트)이 있으므로이를 변경하지 못할 수도 있습니다. 예를 들어 WordPress ( ) 에서 메타 데이터로 작업해야했습니다 .ON DUPLICATE KEYINSERT IGNOREUNIQUEwp_postmeta

마침내 두 가지 쿼리로 해결했습니다.

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

쿼리 1은 UPDATE해당 데이터 세트가없는 경우 영향을 미치지 않는 일반 쿼리입니다. 쿼리 2는에 INSERT의존하는입니다 NOT EXISTS. 즉 INSERT, 데이터 집합이 존재하지 않을 때만 실행됩니다.


2

주목할만한 점은 INSERT IGNORE가 명령문이 성공했는지 여부에 관계없이 여전히 기본 키를 증가시킬 것이라는 점입니다.

이로 인해 기본 키에 차이가 발생하여 프로그래머가 정신적으로 불안정해질 수 있습니다. 또는 응용 프로그램이 제대로 설계되지 않았고 완벽한 증분 기본 키에 의존하는 경우 문제가 될 수 있습니다.

조사 innodb_autoinc_lock_mode = 0(서버 설정, 그리고 약간의 성능 저하와 함께 제공) 또는 확인 쿼리는 (또한 성능 저하 및 추가 코드와 함께 제공되는) 실패하지 않음을 만들기 위해 먼저 SELECT를 사용합니다.


왜 "기본 키에 틈이 생겨"잠재적으로 "프로그래머가 정신적으로 불안정하게"됩니까? 예를 들어 레코드를 삭제할 때마다 기본 키에서 간격이 항상 발생합니다.
warren

SELECT패배로 시작 하면 큰 배치를 넘겨주고 INSERT복제에 대해 걱정하고 싶지 않은 전체 목적을 무효화합니다 .
warren

2

알려진 기본 키없이 업데이트 또는 삽입

이미 고유 키 또는 기본 키가있는 경우 다른 하나가 응답 INSERT INTO ... ON DUPLICATE KEY UPDATE ...하거나 REPLACE INTO ...제대로 작동해야합니다 (존재하는 경우 삭제로 대체 한 다음 삽입하여 기존 값을 부분적으로 업데이트하지 않음).

그러나 some_column_id및 의 값이 있으면 some_type조합이 고유 한 것으로 알려져 있습니다. some_value존재하는 경우 업데이트 하거나 존재하지 않는 경우 삽입 하려고 합니다. 그리고 트랜잭션을 사용하지 않기 위해 단 하나의 쿼리로 수행하려고합니다. 이것은 해결책 일 수 있습니다.

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

기본적으로 쿼리는 다음과 같이 실행됩니다 (보이는 것보다 덜 복잡함).

  • WHERE절 일치 를 통해 기존 행을 선택하십시오 .
  • s열 값이 명시 적으로 제공 되는 잠재적 인 새 행 (table )이있는 결합 (s.id가 NULL이므로 새 자동 증분 식별자를 생성 함)
  • 기존의 로우가 발견되면, 테이블의 잠재적 인 새로운 행은 s(표 1에 LIMIT 때문에 폐기된다 t), 그것은 항상 트리거 ON DUPLICATE KEY되는 것 열.UPDATEsome_value
  • 기존 행을 찾을 수없는 경우 잠재적 새 행이 삽입됩니다 (table에서 제공 한대로 s).

참고 : 관계형 데이터베이스의 모든 테이블에는 최소한 기본 자동 증분 id열이 있어야합니다 . 이것이 없으면 첫눈에 필요하지 않더라도 추가하십시오. 이 "트릭"에 반드시 필요합니다.


다른 여러 응답자들이 INSERT INTO ... SELECT FROM형식 을 제안했습니다 . 왜 그랬어?
워렌

2
@warren 내 대답을 읽지 못했거나 이해하지 못하거나 제대로 설명하지 않았습니다. 어쨌든 다음 사항을 강조하겠습니다. 이것은 일반적인 INSERT INTO... SELECT FROM...해결책 이 아닙니다 . 동일한 답변에 대한 링크를 참조하십시오. 찾을 수 있으면이 답변을 삭제합니다. 그렇지 않으면 내 답변을 찬성합니다 (거래?). 연결하려는 답변이 1 개의 쿼리 (업데이트 + 삽입 용) 만 사용하고 트랜잭션은 사용하지 않으며 고유 한 것으로 알려진 모든 열 조합을 타겟팅 할 수 있는지 확인하십시오 (따라서 별도의 열은 고유해야 함).
Yeti
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.