문제

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

삽입 업데이트 저장 proc 에 SQL 서버

은 거기에 어떤 영리한 방법이에 SQLite 는 생각하지 않았습니까?

기본적으로 업데이트하고 싶은 세 가지 열의 경우 기록이 존재하 하지 않는 경우에 존재하는 나를 삽입하려는 기록하는 기본(NUL)값을 네 번째 열에 있습니다.

의 ID 는 주 키 것입니다 그래서 지금 하나의 레코드를 삽입/.

(난을 피하기 위해 노력하고 있는 오버헤드를 선택하기 위해서 determin 업데이트가 필요한지 또는 삽입 분명)

제안이 있습니까?


나는 확인할 수 없는 구문에 SQLite 사이트를 위한 테이블을 만듭니다.내가 건축하지 않은 데모 테스트,하지만 그것은 것을 지원합니다.

었다면,나는 세 가지 열은 그래서 실제로 보면 다음과 같:

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

하지만 처음 두 개의 모양이 발생하지 않을 충돌만 ID 그래서 나는 asusme Blob1 및 Blob2 지 않을 것입 대체(원)


업데이트 SQLite 때 데이터 바인딩은 완전한 트랜잭션을 의미 각각의 전송하는 행 업데이트할 필요합니다:준비/바인딩/단계/마무리 문의 과는 달리에 삽입되는 사용할 수 있는 리셋 기능

인생의 문을 개체용은 다음과 같습니다.

  1. 체를 사용하여 sqlite3_prepare_v2()
  2. 바인드 값을 호스트 매개 변수를 사용하여 sqlite3_bind_ 인터페이스가 있습니다.
  3. 실행하여 SQL 을 호출하여 sqlite3_step()는
  4. 다시 설정을 사용하여 문 sqlite3_reset()다음 다시 2 단계로 반복합니다.
  5. 을 파괴하는 문을 사용하여 개체 sqlite3_finalize().

업데이트 내가 추측하는 느린 비교를 삽입하려면,하지만 비교하는 방법을 사용하여 선택의 기본 키?

아마도 내가 사용해야하는 선택을 읽는 제 4 열(Blob3)를 사용하여 다음을 대체하는 새로운 기록을 혼합하여 원 4 열과 함께 새로운 데이터에 대한 첫 번째는 3 열?

도움이 되었습니까?

해결책

테이블에서 3 개의 열을 가정합니다 .. ID, 이름, 역할


나쁜: 이렇게하면 모든 열을 ID = 1에 대한 새 값으로 삽입하거나 교체합니다.

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

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

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

좋은: 열 중 2 개를 업데이트합니다. ID = 1이 존재하면 이름은 영향을받지 않습니다. 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이 존재하면 역할은 영향을받지 않습니다. ID = 1이 존재하지 않으면 역할은 기본값 대신 '벤치 워머'로 설정됩니다.

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

다른 팁

삽입 또는 교체 아니다 "Upsert"에 해당합니다.

필드 ID, 이름 및 역할을 가진 테이블 직원이 있다고 가정 해보십시오.

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는 기본값으로 대체했습니다.

상류의 예상 출력은 역할을 바꾸고 이름을 유지하는 것입니다.

에릭 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.id 될거야 NULL 그리고 SQLITE는 ID를 자동으로 할당하지만 이미 그러한 행이있는 경우 old.id 실제 가치가 있으며 이것은 재사용됩니다. 바로 내가 원했던 것입니다.

사실 이것은 매우 유연합니다. 어떻게 ts 기둥은 모든면에서 완전히 누락되었습니다. DEFAULT 가치, sqlite는 어쨌든 옳은 일을 할 것이므로 직접 돌볼 필요가 없습니다.

두 열에 열을 포함시킬 수도 있습니다 new 그리고 old 측면을 다음 예를 들어 사용합니다 COALESCE(new.content, old.content) 외부에서 SELECT "새 콘텐츠를 삽입 한 경우 새 콘텐츠를 삽입하고 이전 컨텐츠를 유지하십시오" - 예를 들어 고정 쿼리를 사용하고 자리 표시 자와 새로운 값을 바인딩하는 경우.

일반적으로 업데이트를하고 있다면 ..

  1. 거래를 시작하십시오
  2. 업데이트를 수행하십시오
  3. RowCount를 확인하십시오
  4. 0 인 경우 삽입물을 수행하십시오
  5. 저지르다

당신이 일반적으로 삽입물을하고 있다면

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

이렇게하면 선택을 피하고 SQLITE에서 트랜잭션 적으로 소리가납니다.

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

2018-05-18 스톱 프레스.

SQLITE에서의 상향 지원! Upsert Syntax는 버전 3.24.0 (보류)과 함께 SQLITE에 추가되었습니다!

Upsert는 인서트가 고유성 제약 조건을 위반하는 경우 삽입물을 업데이트 또는 NO-OP로 작동하게하는 삽입에 대한 특수 구문 추가입니다. 상향은 표준 SQL이 아닙니다. sqlite의 Upsert는 PostgreSQL에 의해 설정된 구문을 따릅니다.

enter image description here

대안으로 :

이 작업을 수행하는 또 다른 방법은 다음과 같습니다. 응용 프로그램에서 메모리로드를 길게 설정합니다 .MAXVALUE 메모리에서 행을 만들 때. (MaxValue는 신분증으로 사용되지 않습니다. 이것은 앱에서 Rowids를 추적 할 수있는 경우에만 유용합니다.

나는 이것이 오래된 스레드라는 것을 알고 있지만 늦게 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보다 큰 경우 SQLITE가 업데이트 문을 완전히 최적화 할 수 있다는 비전을 가지고 있습니다. 그것이 실제로 그것이 내 지식을 넘어서는 지 여부는 남자가 꿈을 꿀 수 없습니까? ;)

보너스 포인트의 경우 새로 삽입 된 행 또는 기존 행이든 행의 ID를 반환하는이 줄을 추가 할 수 있습니다.

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

다음은 인서트 또는 교체 대신 업체 (업데이트 또는 삽입) 인 솔루션이 있습니다 (많은 상황에서 다르게 작동).

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

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

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

언급해야 할 한 가지는 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;

당신은 실제로 SQLITE에서 화장을 할 수 있습니다. 그것은 당신이 익숙한 것과 조금 다르게 보입니다. 그것은 다음과 같은 것처럼 보일 것입니다.

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

확장 아리스토텔레스의 대답 더미 '싱글 톤'테이블 (단일 행이있는 자신의 작품 테이블) 중에서 선택할 수 있습니다. 이것은 약간의 복제를 피합니다.

또한 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";

버전 3.24.0에서 시작하여 SQLITE는 지원됩니다.

로부터 선적 서류 비치:

Upsert는 인서트가 고유성 제약 조건을 위반하는 경우 삽입물을 업데이트 또는 NO-OP로 작동하게하는 삽입에 대한 특수 구문 추가입니다. 상향은 표준 SQL이 아닙니다. sqlite의 Upsert는 PostgreSQL에 의해 설정된 구문을 따릅니다. Upsert Syntax는 버전 3.24.0 (보류)과 함께 SQLITE에 추가되었습니다.

Upsert는 Special on Conflict 조항이 뒤 따르는 일반 삽입 문입니다.

enter image description here

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

내가 아는 가장 좋은 방법은 업데이트를 수행하고 인서트를 수행하는 것입니다. "선택된 오버 헤드"가 필요하지만 기본 키를 검색하기 때문에 빠른 부담은 아닙니다.

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

--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
                );

누군가 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);
    });
});
}

그런 다음 프로그래밍 방식으로 거래를 구축하십시오.

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

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

SQLITE Cordova 플러그인을 사용하려면 공무원을 참조하십시오. 링크

나는 이것이 당신이 찾고있는 것일 수 있다고 생각합니다. 충돌 조항에.

테이블을 다음과 같이 정의하는 경우 :

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

이제 이미 존재하는 ID가있는 삽입을 수행하면 SQLITE는 삽입 대신 자동 업데이트를합니다.

HTH ...

이 방법 리믹스 등의 몇 가지 다른 방법에 대답에서 이 질문을 위해 사용합니 CTE(공용 테이블 표현).게 될 것입니다 그러면 쿼리는 이유를 설명했 무엇을 했습니다.

을 변경하고 싶은 마지막 이름에 대한 직원이 300 데이비스가 있다면 직원이 300.그렇지 않으면 추가 할 것입니다,새로운 직원입니다.

테이블 이름:직원 열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 문은 사용한다.

지금 결정할 수 있습니다 무엇을 기본값으로 사용할을 대체해서 null,에서 병합 기능으로 무엇이 값을 해야 합니다.

수행원 아리스토텔레스 Pagaltzis 그리고 아이디어 COALESCE ~에서 에릭 B의 대답, 여기에는 몇 가지 열만 업데이트하거나 존재하지 않는 경우 전체 행을 삽입하는 업시 트 옵션입니다.

이 경우 제목과 콘텐츠를 업데이트해야한다고 상상해보십시오. 이름을 찾을 수없는 경우 기존 및 제공된 값을 삽입 할 때 다른 이전 값을 유지해야한다고 상상해보십시오.

노트 id 언제 INSERT 자동화 된 것으로 간주됩니다. 그것이 생성 된 기본 키라면 COALESCE 또한 사용할 수 있습니다 (참조 아리스토텔레스 Pagaltzis 의견).

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

방금이 스레드를 읽고이 "Upsert"ing에 대한 것이 쉽지 않다는 사실에 실망한 결과, 나는 더 조사했습니다 ...

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

사용하는 대신: INSERT INTO

사용: INSERT OR REPLACE INTO

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

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;
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top