سؤال

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

إدراج تحديث proc المخزنة في SQL Server

هل هناك طريقة ذكية للقيام بذلك في SQLite أنني لم أفكر ؟

في الأساس كنت ترغب في تحديث ثلاثة من أصل أربعة أعمدة إذا كان السجل موجودا ، إذا لم يكن موجودا كنت ترغب في إدراج السجل الافتراضي (NUL) قيمة العمود الرابع.

الهوية هي المفتاح الأساسي لذلك سيكون هناك فقط من أي وقت مضى أن سجل واحد إلى UPSERT.

(أنا أحاول تجنب النفقات العامة من تحديد من أجل determin إذا كنت بحاجة إلى تحديث أو إدراج الواضح)

اقتراحات ؟


لا يمكنني تأكيد أن الجملة على SQLite موقع إنشاء الجدول.لم بنيت تجريبي لاختبار لكنه لا يبدو أن تكون معتمدة..

إذا كان لدي ثلاثة أعمدة لذلك سوف تبدو فعلا مثل:

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

ولكن الأولين النقط لن يسبب الصراع, فقط معرف أن لذا 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, NAME, دور


السيئة: هذا سيتم إدراج أو استبدال جميع الأعمدة مع القيم الجديدة 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 لا وجود لها دور سيتم تعيين 'Benchwarmer' بدلا من القيمة الافتراضية.

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

نصائح أخرى

إدراج أو استبدال لا أي ما يعادل "UPSERT".

أقول لدي الموظف الجدول مع حقول معرف واسم دور:

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.سكليتي استبداله مع قيمة افتراضية.

المخرجات المتوقعة من UPSERT تغيير دور للحفاظ على اسم.

إريك ب الجواب هو موافق إذا كنت ترغب في الحفاظ على واحد فقط أو ربما عمودين من الصف الحالي.إذا كنت ترغب في الحفاظ على الكثير من الأعمدة ، فإنه يحصل مرهقة جدا سريع.

هنا هو النهج الذي سوف مقياس جيد أي كمية من الأعمدة على جانبي.لتوضيح ذلك سوف نفترض ما يلي المخطط:

 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 اختيار معرف قيمة نفسه عند إدراج صف جديد.ولكن عند تحديث صف موجود على name, أريد أن تستمر الهوية القديمة القيمة (الواضح!).

لقد تحقيق صحيح 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 ثم تعيين معرف تلقائيا ، ولكن إذا كان هناك بالفعل مثل صف واحد ، old.id سيكون لها قيمة فعلية و هذا سيتم استخدامها.والذي هو بالضبط ما أردت.

في الواقع هذا هو مرن جدا.لاحظ كيف ts عمود مفقود تماما من جميع الجوانب – لأنه يحتوي على DEFAULT قيمة, سكليتي فقط سوف تفعل الشيء الصحيح في أي حال, لذا لا يجب أن تأخذ الرعاية من ذلك بنفسي.

يمكنك أيضا تضمين عمود على حد سواء new و old الجانبين ومن ثم استخدام مثل COALESCE(new.content, old.content) في الخارجي SELECT يقول "إدراج محتوى جديد إذا كان هناك أي خلاف على ذلك المحتوى القديم" – على سبيل المثال ، إذا كنت تستخدم ثابتة الاستعلام ملزمة القيم الجديدة مع النائبة.

إذا كنت عادة القيام التحديثات أنا ..

  1. تبدأ الصفقة
  2. هل التحديث
  3. التحقق من rowcount
  4. إذا كان لا إدراج 0
  5. ارتكاب

إذا كنت عادة القيام إدراج أنا

  1. تبدأ الصفقة
  2. محاولة إدراج
  3. تحقق من مفتاح أساسي انتهاك خطأ
  4. إذا كان لدينا خطأ هل التحديث
  5. ارتكاب

بهذه الطريقة يمكنك تجنب اختيار وأنت transactionally الصوت على Sqlite.

هذا الجواب قد يكون تحديث و حتى التعليقات أدناه لم تعد تنطبق.

2018-05-18 تتوقف الصحافة.

UPSERT الدعم في SQLite! UPSERT جملة أضيف إلى SQLite مع النسخة 3.24.0 (انتظار) !

UPSERT هو بناء جملة خاص بالإضافة إلى إدراج الذي يسبب إدراج تتصرف كتحديث أو عدم المرجع إذا كان إدراج تنتهك تفرد القيد.UPSERT ليس SQL القياسية.UPSERT في SQLite يلي الجملة التي وضعتها كيو.

enter image description here

بدلا من ذلك:

آخر مختلف تماما طريقة للقيام بذلك هو:في طلبي أنا وضعت بلدي في الذاكرة rowID أن تكون طويلة.MaxValue عند إنشاء الصف في الذاكرة.(MaxValue لن يتم استخدام معرف سوف لن تعيش طويلا بما فيه الكفاية....ثم إذا rowID ليست تلك القيمة ثم يجب أن يكون بالفعل في قاعدة البيانات لذا يحتاج تحديث إذا كان 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>; 

انها لا تزال 2 الاستفسارات مع جملة where على التحديث ولكن يبدو أن تفعل خدعة.أنا أيضا لدي هذه الرؤية في رأسي سكليتي يمكن تحسين بعيدا التحديث بيان تماما إذا الدعوة إلى تغييرات() أكبر من الصفر.ما إذا كان أو لم يكن في الواقع هل هذا هو أبعد حد علمي ، ولكن يمكن للإنسان أن يحلم أليس كذلك ؟ ;)

للحصول على نقاط المكافأة يمكنك إلحاق هذا الخط والتي ترجع لك معرف الصف سواء كان ذلك حديثا إدراج صف أو صف موجود.

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

هنا هو الحل الذي حقا هو UPSERT (تحديث أو إدراج) بدلا من إدراج أو استبدال (التي تعمل بشكل مختلف في كثير من الحالات).

وهي تعمل مثل هذا:
1.محاولة تحديث إذا سجل مع نفس رقم موجود.
2.إذا كان التحديث لم يتغير أي الصفوف (NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)) ، ثم أدخل الرقم القياسي.

لذا إما موجود كان سجل تحديث أو إدراج سوف يتم تنفيذها.

التفاصيل المهم هو استخدام التغييرات() SQL وظيفة للتحقق إذا كان التحديث بيان ضرب أي السجلات الموجودة فقط أداء insert إذا لم تصل أي سجل.

شيء واحد أن نذكر أن التغييرات() وظيفة لا عودة التغييرات التي يقوم بها أقل مستوى المشغلات (انظر 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;

يمكنك في الواقع لا 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

التوسع في أرسطو الإجابة يمكنك الاختيار من دمية 'المفرد' الجدول (جدول خلق الخاصة بك مع صف واحد).هذا يتجنب بعض الازدواجية.

لقد أبقت أيضا على سبيل المثال المحمولة عبر 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 UPSERT معتمد من قبل سكليتي.

من الوثائق:

UPSERT هو بناء جملة خاص بالإضافة إلى إدراج الذي يسبب إدراج تتصرف كتحديث أو عدم المرجع إذا كان إدراج تنتهك تفرد القيد.UPSERT ليس SQL القياسية.UPSERT في SQLite يلي الجملة التي وضعتها كيو. UPSERT جملة أضيف إلى SQLite مع النسخة 3.24.0 (انتظار).

وهو UPSERT غير عادية INSERT التي تليها خاص على الصراع شرط

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

إذا كان شخص ما يريد أن يقرأ لي حل SQLite في قرطبة, حصلت على هذا عامة شبيبة الأسلوب بفضل @ديفيد الجواب أعلاه.

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

ثم بناء المعاملات برمجيا.

"قيم" هو مجموعة يجب بناء قبل وأنها تمثل الصفوف تريد إدراج أو تحديث في الجدول.

"remoteid" هو معرف أنا استخدامها كمرجع ، أنا منذ المزامنة مع ملقم بعيد.

استخدام سكليتي قرطبة المساعد ، يرجى الرجوع إلى المسؤول الرابط

أعتقد أن هذا قد يكون ما كنت تبحث عن: على الصراع شرط.

إذا كنت تحديد الجدول الخاص بك مثل هذا:

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

الآن, إذا كنت لا تضاف مع معرف موجود بالفعل ، سكليتي التلقائى هل التحديث بدلا من إدراج.

Hth...

هذا الأسلوب يعدل عدد قليل من الطرق الأخرى من الإجابة عن هذا السؤال يتضمن استخدام CTE (المائدة المشتركة التعبير).وسوف أعرض الاستعلام ثم يفسر لماذا فعلت ما فعلت.

أود أن تغيير إسم الموظف 300 إلى ديفيس إذا كان هناك موظف 300.وإلا سوف إضافة موظف جديد.

اسم الجدول:الموظفين الأعمدة:معرف 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 إلى تقليل عدد مرات حدد البيان إلى أن تستخدم لتحديد القيم الافتراضية.لأن هذا هو CTE, نحن فقط حدد الأعمدة من الجدول إدراج بيان يستخدم هذا.

الآن يمكنك أن تقرر ما هي الافتراضات التي تريد استخدامها عن طريق استبدال بالقيم الخالية في تلتحم مع وظيفة ما القيم التي ينبغي أن يكون.

التالية أرسطو Pagaltzis و فكرة COALESCE من إريك ب الجواب, هنا هو upsert خيار التحديث فقط عدد قليل من الأعمدة أو إدراج الصف الكامل إذا كان غير موجود.

في هذه الحالة, تخيل أن العنوان يجب أن يكون محتوى محدث ، والحفاظ على غيرها من القيم القديمة عندما القائمة إدراج الموردة منها عندما لم يتم العثور على اسم:

ملاحظة 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"جي انا التحقيق...

يمكنك فعلا فعل هذا مباشرة و بسهولة في 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