SQLite-UPSERT * not * INSERT 또는 REPLACE


535

http://en.wikipedia.org/wiki/Upsert

SQL Server에 업데이트 저장 프로 시저 삽입

내가 생각하지 못한 SQLite 에서이 작업을 수행하는 영리한 방법이 있습니까?

기본적으로 레코드가 존재하면 4 개 열 중 3 개를 업데이트하고 싶습니다.없는 경우 4 번째 열의 기본 (NUL) 값으로 레코드를 삽입하고 싶습니다.

ID는 기본 키이므로 UPSERT에 대한 레코드는 하나뿐입니다.

(명확하게 업데이트하거나 삽입 해야하는 경우 결정하기 위해 SELECT의 오버 헤드를 피하려고합니다.)

제안?


TABLEite에 대한 SQLite 사이트의 구문을 확인할 수 없습니다. 테스트 할 데모를 만들지 않았지만 지원되지 않는 것 같습니다.

그렇다면 세 개의 열이 있으므로 실제로 다음과 같이 나타납니다.

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

그러나 처음 두 얼룩은 충돌을 일으키지 않으며 ID만이 가능하므로 Blob1을 asusme하고 Blob2는 (원하는대로) 교체하지 않습니다


바인딩 데이터가 완전한 트랜잭션 일 때 SQLite의 UPDATEs 업데이트 될 각 전송 된 행에는 다음이 필요합니다. 리셋 기능을 사용할 수있는 INSERT와 달리 Prepare / Bind / Step / Finalize 문

명령문 객체의 수명은 다음과 같습니다.

  1. sqlite3_prepare_v2 ()를 사용하여 객체를 만듭니다.
  2. sqlite3_bind_ 인터페이스를 사용하여 호스트 매개 변수에 값을 바인드하십시오.
  3. sqlite3_step ()을 호출하여 SQL을 실행하십시오.
  4. sqlite3_reset ()을 사용하여 명령문을 재설정 한 후 2 단계로 돌아가서 반복하십시오.
  5. sqlite3_finalize ()를 사용하여 명령문 오브젝트를 제거하십시오.

업데이트 INSERT에 비해 느린 것으로 생각되지만 기본 키를 사용하여 SELECT와 어떻게 비교합니까?

아마도 select를 사용하여 4 번째 열 (Blob3)을 읽은 다음 REPLACE를 사용하여 원래 4 번째 열을 처음 3 열의 새 데이터와 혼합하는 새 레코드를 작성해야합니까?



3
SQLite는 버전 3.24.0에서 사용할 수 UPSERT
pablo_worker

답변:


854

표에서 3 개의 열을 가정합니다 : ID, NAME, ROLE


BAD : 모든 열을 삽입하거나 ID = 1의 새로운 값으로 바꿉니다

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

BAD : 열 2 개를 삽입하거나 교체합니다. NAME 열은 NULL 또는 기본값으로 설정됩니다.

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

좋은 : SQLite에서 SQLite On 충돌 절 UPSERT 지원을 사용하십시오 ! UPSERT 구문이 SQLite 버전 3.24.0에 추가되었습니다!

UPSERT는 INSERT가 고유성 제약 조건을 위반하는 경우 INSERT가 UPDATE 또는 no-op로 작동하게하는 INSERT에 추가 된 특수 구문입니다. UPSERT는 표준 SQL이 아닙니다. SQLite의 UPSERT는 PostgreSQL에서 설정 한 구문을 따릅니다.

여기에 이미지 설명을 입력하십시오

양호하지만 부드럽습니다. 이렇게하면 2 개의 열이 업데이트됩니다. ID = 1이 존재하면 NAME은 영향을받지 않습니다. ID = 1이 없으면 이름이 기본값 (NULL)이됩니다.

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

열 2 개가 업데이트됩니다. ID = 1이 존재하면 ROLE은 영향을받지 않습니다. ID = 1이 없으면 역할이 기본값 대신 'Benchwarmer'로 설정됩니다.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
화려한 +1! 내장 된 select 절은 필드의 이전 값과 새 값을 결합 / 비교해야하는 경우 기본 ON CONFLICT REPLACE 기능을 재정의 할 수있는 유연성을 제공합니다.
G__

24
계단식 삭제로 다른 행에서 직원을 참조하는 경우 다른 행은 대체하여 삭제됩니다.
돈 레바

246
아파요. SQlite는 UPSERT가 필요합니다.
andig

21
이것이 왜 모든 열을 ID = 1에 대한 새로운 값으로 삽입하거나 대체 할 것인지 설명 할 수 있습니까? 첫 번째 예에서 BAD 로 간주 됩니까? 여기에 표시된 명령은 ID 1, John Foo 및 role CEO 라는 새 레코드를 작성 하거나 ID가 1 인 레코드를 이미있는 경우 해당 데이터로 겹쳐 쓰는 것입니다 ( ID 가 기본 키 라고 가정 ). . 정확히 그렇게된다면 왜 나쁜가요?
또는 매퍼

10
@ Cornelius : 그것은 분명하지만 첫 번째 예에서는 그렇지 않습니다. 첫 번째 예는 모든 열을 강제로 설정하기위한 것으로, 레코드 삽입 또는 교체 여부에 관계없이 정확하게 발생합니다. 그렇다면 왜 나쁜 것으로 간주됩니까? 링크 된 답변은 두 번째 예제 와 같이 열의 하위 집합을 지정할 때 왜 나쁜 일이 발생할 수 있는지 나타냅니다 . 모든 열의 값을 지정하는 동안 첫 번째 예제 에서 발생하는 나쁜 영향에 대해 자세히 설명하지는 않습니다 . INSERT OR REPLACE
또는 매퍼

134

INSERT OR REPLACE는 "UPSERT"와 동일 하지 않습니다 .

id, name 및 role 필드가있는 Employee 테이블이 있다고 가정하십시오.

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

붐, 직원 번호 1의 이름을 잃어 버렸습니다. SQLite가 기본 값으로 바꿨습니다.

UPSERT의 예상 결과는 역할을 변경하고 이름을 유지하는 것입니다.


21
나에게서 -1 나는 두렵다. 예, 허용 된 답변이 틀렸지 만 귀하의 답변이 문제를 지적하는 것도 답변이 아닙니다. 실제 답변 은 Embedded 절을 사용한 Eric B의 영리한 솔루션 을 참조하십시오 coalesce((select ..), 'new value'). 에릭의 대답은 여기에 더 많은 표가 필요하다고 생각합니다.

22
과연. 에릭은 최고의 답변이며 더 많은 표를받을 자격이 있습니다. 즉, 문제를 지적함으로써 좋은 답변을 찾는 데 약간 기여했다고 생각합니다 (Eric의 답변은 나중에 왔으며 내 답변의 샘플 SQL 테이블을 기반으로합니다). 따라서 내가 -1을받을 자격이
있는지는 모르겠지만, 신경 쓰지

6
위의 -1을 오프셋하려면 +1 lol. 이 스레드에 대한 흥미로운 타임 라인. 분명히 당신의 대답은 에릭의 일주일 전에 있었지만, 당신은 질문을받은 지 거의 2 년 후에 질문에 모두 대답했습니다. 정교하지만 Eric에게는 +1.
Rich


2
@QED 아니오, delete + insert (바꾸기)는 예를 들어 자체 트리거가있는 2 개의 dml 문이기 때문에. 1 개의 update 문과 동일하지 않습니다.
Sebas

109

기존 행에서 하나 또는 두 개의 열만 보존하려는 경우 Eric B의 대답 은 정상입니다. 많은 열을 유지하려면 너무 번거로워집니다.

다음은 양쪽의 열에 맞게 확장 할 수있는 방법입니다. 이를 설명하기 위해 다음 스키마를 가정합니다.

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

특히 이는 name행의 자연 키입니다. id외래 키에만 사용되므로 새 행을 삽입 할 때 SQLite가 ID 값 자체를 선택하는 것이 중요합니다. 그러나의 기존 행을 업데이트 할 때 name이전 ID 값을 계속 유지하고 싶습니다 (분명히!).

나는 UPSERT다음과 같은 구성 으로 사실 을 달성합니다 .

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

이 쿼리의 정확한 형식은 약간 다를 수 있습니다. 핵심은 INSERT SELECT기존 행을 새 값에 결합하기 위해 왼쪽 외부 결합을 사용하는 것입니다 .

여기서 행이 이전에 존재하지 않으면 old.idwill이 NULL있고 SQLite는 자동으로 ID를 할당하지만 이미 그러한 행 old.id이 있으면 실제 값을 가지므로 재사용됩니다. 정확히 내가 원하는 것입니다.

실제로 이것은 매우 유연합니다. ts열이 모든 측면에서 완전히 누락 된 방법에 유의하십시오 . DEFAULT값 이 있기 때문에 SQLite는 어떤 경우에도 올바른 작업을 수행하므로 직접 처리 할 필요가 없습니다.

또한 양쪽에 열을 포함 할 수 있습니다 newold예를 들어, 사용 후 측면과 COALESCE(new.content, old.content)외부에서 SELECT당신이 고정 된 쿼리를 사용하고 새로운 바인딩하는 경우 예 - "그렇지 않으면 기존 내용을 유지, 어떤이 있다면 새로운 내용 삽입"말을 자리 표시 자와 함께 값.


13
+1, 잘 작동하지만 속도를 높이기 위해 WHERE name = "about"제약 조건을 추가하십시오 SELECT ... AS old. 1m + 행이 있으면 매우 느립니다.

좋은 지적, 댓글에 +1. 그런 WHERE절을 추가 하면 처음 에이 접근법을 생각해 냈을 때 회피하려고했던 쿼리에 일종의 중복성이 필요 하기 때문에 대답 에서 제외 할 것입니다. 항상 그렇듯이 성능이 필요할 때 비정규 화 –이 경우 쿼리 구조.
아리스토텔레스 Pagaltzis

4
원하는 경우 아리스토텔레스의 예를 다음과 같이 단순화 할 수 있습니다. INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
jcox

4
ON DELETE교체 (즉, 업데이트)를 수행 할 때 불필요하게 트리거를 트리거 하지 않습니까?
Karakuri

3
확실히 트리거를 트리거 ON DELETE합니다. 불필요하게 대부분의 사용자에게는 불필요하고 원치 않을 수도 있지만 모든 사용자에게 해당되는 것은 아닙니다. 마찬가지로 외래 키가있는 행을 해당 행에 계단식으로 삭제한다는 사실도 마찬가지입니다. 아마도 많은 사용자에게 문제가 될 것입니다. 불행히도 SQLite는 실제 UPSERT에 더 가깝습니다. (저장은 그것을 날조하기위한 INSTEAD OF UPDATE트리거 것 같아요.)
아리스토텔레스 Pagaltzis

86

일반적으로 업데이트를 수행하는 경우 ..

  1. 거래 시작
  2. 업데이트를
  3. 행 개수 확인
  4. 0이면 삽입하십시오
  5. 범하다

일반적으로 인서트를하고 있다면

  1. 거래 시작
  2. 삽입을 시도하십시오
  3. 기본 키 위반 오류 확인
  4. 오류가 발생하면 업데이트를 수행하십시오
  5. 범하다

이렇게하면 선택을 피하고 Sqlite에서 거래 적으로 건전합니다.


감사합니다. 방금 @ stackoverflow.com/questions/2717590/…을 물었습니다 . 나에게서 +1! =)
Alix Axel

4
세 번째 단계에서 sqlite3_changes ()를 사용하여 행 수를 확인하려는 경우 수정을 위해 여러 스레드의 DB 핸들을 사용하지 않아야합니다.
Linulin

3
다음과 같은 말은 아니지만 같은 효과를 낼 수는 없습니다. 1) id = 'x'인 id 형식 테이블을 선택하십시오.
Florin

20
정말 INSERT 또는 UPDATE 언어의 일부 바랍니다
플로린

이것은 나에게 가장 잘 작동하여 REPLACE INTO를 시도하거나 항상 삽입 및 업데이트가 주로 삽입 대신 업데이트를 수행했을 때 성능을 저하시킵니다.
PherricOxide

83

이 답변은 업데이트되었으므로 아래 주석은 더 이상 적용되지 않습니다.

2018-05-18 스톱 프레스.

SQLite에서 UPSERT 지원! UPSERT 구문이 버전 3.24.0 (보류 중) 인 SQLite에 추가되었습니다!

UPSERT는 INSERT가 고유성 제약 조건을 위반하는 경우 INSERT가 UPDATE 또는 no-op로 작동하게하는 INSERT에 추가 된 특수 구문입니다. UPSERT는 표준 SQL이 아닙니다. SQLite의 UPSERT는 PostgreSQL에서 설정 한 구문을 따릅니다.

여기에 이미지 설명을 입력하십시오

대안 적으로 :

이 작업을 수행하는 또 다른 완전히 다른 방법은 다음과 같습니다. 내 응용 프로그램에서 메모리에서 행을 만들 때 내 메모리 rowID를 long.MaxValue로 설정했습니다. (MaxValue는 ID로 사용되지 않으며 오래 살지 못할 것입니다 .... rowID가 그 값이 아닌 경우 이미 데이터베이스에 있어야하므로 MaxValue 인 경우 UPDATE가 필요하며 삽입이 필요합니다. 앱에서 rowID를 추적 할 수있는 경우에만 유용합니다.


5
아멘. 단순한 것보다 복잡한 것이 좋습니다. 이것은 허용 된 답변보다 약간 단순하다고 느낍니다.
kkurian

4
INSERT INTO ...을 할 수 없다고 생각했습니다. 이것은 sqlite3의 구문 오류입니다
Charlie Martin

5
@CharlieMartin이 맞습니다. 이 구문은 OP가 요청한 SQLite에는 유효하지 않습니다. WHERE절은 추가 할 수 없습니다 INSERT: 문 SQLite는-삽입 ...
colm.anseo

4
이 답변은 많은 시간을 허비 시켰습니다. 질문은 SQLITE에 관한 것이며, INSERT WHERE가 sqite에서 지원되지 않았다는 것을 몰랐 습니다. 귀하의 답변에서 sqlite에 대해 유효하지 않다는 점에 유의하십시오
Miquel

3
@ colminator 및 기타 : 귀하의 제안을 사용하여 SQL 문을 수정했습니다.
F Lekschas

61

나는 이것이 오래된 스레드라는 것을 알고 있지만 늦게 sqlite3에서 작업하고 있으며 매개 변수가있는 쿼리를 동적으로 생성하는 데 필요한이 방법을 생각해 냈습니다.

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

여전히 업데이트에 where 절이있는 2 개의 쿼리이지만 트릭을 수행하는 것 같습니다. 또한 sqlite가 changes () 호출이 0보다 큰 경우 update 문을 완전히 최적화 할 수 있다는 비전을 가지고 있습니다. 그것이 실제로인지 아닌지는 내 지식을 넘어서는 것이지만 사람은 꿈을 꿀 수 없습니다. ;)

보너스 포인트의 경우이 행을 추가하면 새로 삽입 된 행이든 기존 행이든 행의 ID를 반환합니다.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. 정확히 일부 SQL Server TSQL 코드에서 변환을 대체하려고했습니다. 감사. SQL 내가 대체하려고했던 것은 같았다Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB

14

다음은 실제로 여러 상황에서 다르게 작동하는 INSERT 또는 REPLACE 대신 UPSERT (UPDATE 또는 INSERT) 솔루션입니다.

다음과 같이 작동합니다.
1. 동일한 ID를 가진 레코드가 존재하면 업데이트를 시도하십시오.
2. 업데이트가 행을 변경하지 않은 경우 ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)) 레코드를 삽입하십시오.

따라서 기존 레코드가 업데이트되거나 삽입이 수행됩니다.

중요한 세부 사항은 changes () SQL 함수를 사용하여 업데이트 명령문이 기존 레코드에 도달했는지 확인하고 레코드에 도달하지 않은 경우에만 insert 문을 수행하는 것입니다.

한 가지 언급 할 사항은 changes () 함수는 하위 수준 트리거 ( http://sqlite.org/lang_corefunc.html#changes 참조 ) 가 수행 한 변경 사항을 반환하지 않으므로 반드시 고려해야합니다.

다음은 SQL입니다 ...

테스트 업데이트 :

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

테스트 인서트 :

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
이것은 Eric보다 나에게 더 나은 해결책 인 것 같습니다. 그러나 INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;작동해야합니다.
bkausbk

감사합니다. 또한 WebSQL (Cordova 및 SQLite 플러그인 사용)에서도 작동합니다.
Zappescu

11

버전 3.24.0부터 UPSERT는 SQLite에서 지원됩니다.

로부터 문서 :

UPSERT는 INSERT가 고유성 제약 조건을 위반하는 경우 INSERT가 UPDATE 또는 no-op로 작동하게하는 INSERT에 추가 된 특수 구문입니다. UPSERT는 표준 SQL이 아닙니다. SQLite의 UPSERT는 PostgreSQL에서 설정 한 구문을 따릅니다. UPSERT 구문이 버전 3.24.0 (보류 중) 인 SQLite에 추가되었습니다.

UPSERT는 특수한 ON CONFLICT 절이 뒤 따르는 일반적인 INSERT 문입니다.

여기에 이미지 설명을 입력하십시오

이미지 출처 : https://www.sqlite.org/images/syntax/upsert-clause.gif


8

실제로 SQLite에서 upsert를 수행 할 수 있으며 익숙한 것과 약간 다릅니다. 다음과 같이 보일 것입니다 :

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

Aristotle의 답변 을 확장 하면 더미 '싱글 톤'테이블 (단일 행이있는 자신의 창작 테이블)에서 선택할 수 있습니다. 이것은 일부 중복을 피합니다.

또한 MySQL과 SQLite에서 예제를 이식 가능하게 유지하고 처음으로 열을 설정할 수있는 방법의 예로 'date_added'열을 사용했습니다.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

내가 아는 가장 좋은 방법은 업데이트를 수행 한 다음 삽입하는 것입니다. "선택 오버 헤드"가 필요하지만 기본 키를 검색 할 때 끔찍한 부담은 없습니다.

테이블 및 필드 이름으로 아래 명령문을 수정하여 원하는 것을 수행 할 수 있어야합니다.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

7
복잡한 방법.
frast

데이터베이스 엔진에 두 번 요청해야하기 때문에 좋은 생각이 아닙니다.
Ricardo da Rocha Vitor

3

누군가가 Cordova에서 SQLite에 대한 솔루션을 읽으려면 위의 @david 답변 덕분 에이 일반적인 js 메소드를 얻었습니다.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

따라서 먼저이 함수를 사용하여 열 이름을 선택하십시오.

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

그런 다음 프로그래밍 방식으로 트랜잭션을 빌드하십시오.

"값"은 이전에 빌드해야하는 배열이며 테이블에 삽입하거나 업데이트하려는 행을 나타냅니다.

"원격"은 원격 서버와 동기화하기 때문에 참조로 사용한 ID입니다.

SQLite Cordova 플러그인 사용에 대해서는 공식 링크 를 참조하십시오


2

이 방법은이 질문에 대한 답변의 몇 가지 다른 방법을 리믹스하고 CTE (공통 테이블 식)의 사용을 통합합니다. 쿼리를 소개하고 내가 한 일을 왜했는지 설명하겠습니다.

직원 300이 있으면 직원 300의 성을 DAVIS로 변경하고 싶습니다. 그렇지 않으면 새 직원을 추가합니다.

테이블 이름 : 직원 열 : id, first_name, last_name

쿼리는 다음과 같습니다.

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

기본적으로 CTE를 사용하여 select 문을 사용하여 기본값을 결정하는 횟수를 줄였습니다. 이것이 CTE이므로 테이블에서 원하는 열을 선택하기 만하면 INSERT 문에서이를 사용합니다.

이제 COALESCE 함수에서 널을 대체하여 사용하려는 기본값을 값으로 설정하십시오.


2

다음 아리스토텔레스 Pagaltzis 과의 아이디어 COALESCE에서 에릭 B의 답변을 여기에 단지 몇 열을 업데이트하거나 존재하지 않는 경우 전체 행을 삽입 할 수있는 upsert 옵션입니다.

이 경우 제목과 내용을 업데이트하여 기존의 다른 값을 유지하고 이름을 찾을 수없는 경우 제공된 값을 삽입해야합니다.

자동 증가로 가정 id할 때 NOTE 는 NULL이어야합니다 INSERT. 생성 된 기본 키인 경우 COALESCE에도 사용할 수 있습니다 ( Aristotle Pagaltzis comment 참조 ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

따라서 일반적인 규칙은 이전 값을 유지하려면을 사용 COALESCE하고 값을 업데이트하려는 경우을 사용하는 것입니다.new.fieldname


COALESCE(old.id, new.id)자동 증가 키에는 분명히 잘못되었습니다. 그리고“값이없는 곳을 제외하고 대부분의 행을 변경하지 마십시오.”누군가가 실제로 사용하는 유스 케이스처럼 들리지만, 사람들이 UPSERT를 수행하는 방법을 찾을 때 찾고 있다고 생각하지 않습니다.
아리스토텔레스 Pagaltzis

@AristotlePagaltzis는 내가 틀렸다면 사과합니다. 자동 증가를 사용하지 않습니다. 이 쿼리에서 찾고있는 것은 소수의 col 만 업데이트하거나 존재하지 않는 경우 제공된 값으로 전체 행을 삽입하는 것 입니다. 쿼리를 가지고 놀고 있는데 삽입 할 때 old테이블에서 선택한 열이에 NULL제공된 값이 아닌에 삽입 할 때 쿼리를 수행 할 수 없었 습니다 new. 이것이 사용하는 이유 COALESCE입니다. 당신이 autoincrements와 솔루션으로 날 지점 수 있다면 나는이 쿼리를 테스트하고 경우에 작동하는 것 같다 봤는데, SQLite는 전문가가 아니에요, 나는 감사합니다 것
미켈

2
자동 증가 키의 경우 SQLite가 대신 사용 가능한 다음 값을 삽입하도록 지시하므로 키로 삽입 하려고NULL 합니다.
아리스토텔레스 Pagaltzis

나는 당신이하고있는 일을해서는 안된다고 말하는 것이 아닙니다 (필요한 경우 필요합니다). 여기서는 그것이 여기서 질문에 대한 대답이 아니라는 것입니다. UPSERT는 일반적으로 테이블에 저장하려는 행이 있고 값을 넣을 일치하는 행이 있는지 또는 값을 새 행으로 삽입해야하는지 알 수 없음을 의미합니다. 사용 사례는 이미 일치하는 행이있는 경우 새 행에서 대부분의 값무시 하려는 것 입니다. 괜찮습니다. 그것은 단지 질문 된 질문이 아닙니다.
아리스토텔레스 Pagaltzis

1

나는 이것이 당신이 찾고있는 것이라고 생각합니다 : ON CONFLICT clause .

다음과 같이 테이블을 정의하면 :

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

이제 이미 존재하는 ID로 INSERT를 수행하면 SQLite는 자동으로 INSERT 대신 UPDATE를 수행합니다.

Hth ...


6
나는 이것이 작동하지 않는다고 생각한다, 그것은 insert 서술문에서 빠진 열을 지울 것이다
Sam Saffron

3
@Mosor : 나에게서 -1, 죄송합니다. 이것은 REPLACE진술 을 발행하는 것과 같습니다 .
Alix Axel

7
기본 키가 이미 존재하는 경우 삭제 후 삽입을 수행하므로 -1입니다.
frast

0

두 가지 작업 으로이 작업을 수행하지 않아도됩니다.

단계 :

1) "삽입 또는 무시"로 새 항목 추가

2) "UPDATE"로 기존 항목 업데이트

두 단계에 대한 입력은 새로운 또는 업데이트 가능한 항목의 동일한 모음입니다. 변경이 필요없는 기존 항목과 잘 작동합니다. 그것들은 업데이트되지만 동일한 데이터를 가지므로 결과는 변하지 않습니다.

물론, 느리게 등 비효율적입니다. 네.

SQL을 작성하고 유지 관리하고 이해하기가 쉽습니까? 명확히.

고려해야 할 절충점입니다. 작은 upserts에 효과적입니다. 코드 유지 관리 효율성을 저하시키지 않는 사람들에게 적합합니다.


INSERT OR REPLACE는이 게시물의 내용이 아닙니다. INSERT OR REPLACE는 테이블에 새로운 ID를 가진 새로운 행을 생성합니다. 그것은 업데이트가 아닙니다.
sapbucket

-2

이 글을 읽고이 "UPSERT"작업을하기가 쉽지 않다는 것에 실망한 후, 더 조사했습니다 ...

실제로 SQLITE에서 직접 쉽고 쉽게 수행 할 수 있습니다.

사용하는 대신: INSERT INTO

사용하다: INSERT OR REPLACE INTO

이것은 당신이 원하는 것을 정확하게합니다!


21
-1 INSERT OR REPLACEUPSERT입니다. 이유는 gregschlom의 "답변" 을 참조하십시오 . Eric B의 솔루션은 실제로 작동하며 약간의 공감대가 필요합니다.

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

만약 COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

그렇지 않으면 COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

이것은 너무 복잡합니다. SQL은 한 번의 쿼리로 이것을 잘 처리 할 수 ​​있습니다
Robin Kanters
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.