Як виконати UPSERT, щоб я міг використовувати як нові, так і старі значення в частині оновлення


78

Дурний, але простий приклад: Припустимо, у мене є таблиця "Позиція", де я зберігаю підсумки отриманих елементів.

Item_Name              Items_In_Stock

Назва елемента тут є первинним ключем. Як мені досягти наступного, коли я коли-небудь отримую предмет А у кількості X.

Якщо товар не існує, я вставляю новий відновлений для елемента A і встановлюю на складі значення X, а якщо існує запис, де на складі є Y, то нова вартість на складі є (X + Y)

INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = 27 + (SELECT items_in_stock where item_name = 'A' )

Моя проблема полягає в тому, що в моїй фактичній таблиці є кілька стовпців. Чи хороша ідея писати декілька операторів вибору в частині оновлення?

Звичайно, я можу це зробити в коді, але чи є кращий спосіб?


Чому б ви використовували підзапит у своєму прикладі? Ви могли просто використати ON DUPLICATE KEY UPDATE new_items_count = new_items_count + 27. Побачивши, що ви не розміщували інші стовпці, важко відповісти на ваше запитання, оскільки очікувана поведінка невідома. Що ви робите з іншими колонками? Оновлення деяких номерів чи?
Michael JV

Чудово. Це відповідає на питання. Я не знав, що можу використовувати ім'я стовпця замість підзапиту. Якщо ви опублікуєте це як відповідь, я прийму це як відповідь.
WPFAbsoluteNewBie

Існує рубінова бібліотека, яка визначає функцію MySQL: github.com/seamusabshere/upsert
Seamus Abshere

що new_items_countтут? це ще одна колонка на вашому столі?
питання

Відповіді:


153

Як згадувалося в моєму коментарі, вам не потрібно робити підвибір, щоб посилатися на рядок, який викликає спрацьовування ON DUPLICATE KEY. Отже, у вашому прикладі ви можете використовувати наступне:

INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = `new_items_count` + 27

Пам’ятайте, що більшість речей насправді прості, якщо ви ловите себе надмірно ускладнюючи щось, що повинно бути простим, тоді ви, швидше за все, робите це неправильно :)


Ви рятуєте день. Попрацюйте також із PDO
Ruwantha

Приємна чиста та стисла відповідь
MikeT

2
Примітка: Схоже, речення ON DUPLICATE KEY UPDATE збільшить значення автоматичного збільшення, якщо поле PK є автоматичним збільшенням, незалежно від того, який ефект (INSERT або UPDATE) був запущений (при використанні механізму InnoDB). Дивіться: Довідковий посібник MySQL
Алехандро

Щоб запобігти дублюванню коду, вам слід скористатисяnew_items_count = new_items_count + VALUES(items_in_stock)
Paul Spiegel

1
Чи можна це зробити за допомогою REPLACEзапиту? Так все набагато простіше
Шанкар

13

Ви можете отримати ідею з цього прикладу:

Припустимо, ви хочете додати дані про користувачів за 7 днів

Він повинен мати унікальне значення для ідентифікатора користувача та дня

UNIQUE KEY `seven_day` (`userid`,`day`)

Ось таблиця

CREATE TABLE `table_name` (
  `userid` char(4) NOT NULL,
  `day` char(3) NOT NULL,
  `open` char(5) NOT NULL,
  `close` char(5) NOT NULL,
  UNIQUE KEY `seven_day` (`userid`,`day`)
);

І ваш запит буде

INSERT INTO table_name (userid,day,open,close) 
    VALUES ('val1', 'val2','val3','val4') 
        ON DUPLICATE KEY UPDATE open='val3', close='val4';

Приклад:

<?php
//If your data is
$data= array(
        'sat'=>array("userid"=>"1001", "open"=>"01.01", "close"=>"11.01"),
        'sun'=>array("userid"=>"1001", "open"=>"02.01", "close"=>"22.01"),
        'sat'=>array("userid"=>"1001", "open"=>"03.01", "close"=>"33.01"),
        'mon'=>array("userid"=>"1002", "open"=>"08.01", "close"=>"08.01"),
        'mon'=>array("userid"=>"1002", "open"=>"07.01", "close"=>"07.01")
    );


//If you query this in a loop
//$conn = mysql_connect("localhost","root","");
//mysql_select_db("test", $conn);

foreach($data as $day=>$info) {
    $sql = "INSERT INTO table_name (userid,day,open,close) 
                VALUES ('$info[userid]', '$day','$info[open]','$info[close]') 
            ON DUPLICATE KEY UPDATE open='$info[open]', close='$info[close]'";
    mysql_query($sql);
}
?>

Ваші дані будуть у таблиці:

+--------+-----+-------+-------+
| userid | day | open  | close |
+--------+-----+-------+-------+
| 1001   | sat | 03.01 | 33.01 |
| 1001   | sun | 02.01 | 22.01 |
| 1002   | mon | 07.01 | 07.01 |
+--------+-----+-------+-------+

6
Ніколи не об'єднуйте дані безпосередньо в запит. Використовуйте підготовлені / параметризовані запити з PDO або подібними. У випадку з цим прикладом було б доречним хоча б уникнути даних.
Бред

Зверніть увагу, що цей код використовує mysql_*функції, які були видалені в PHP 7; розглядаючи можливість заміни, див. цю цікаву загадку щодо PDO проти MySQLi.
i336_

11

Незважаючи на те, що відповідь Майкла правильна, вам потрібно знати трохи більше, щоб виконати програмне забезпечення:

Спочатку створіть свою таблицю та вкажіть, у яких стовпцях ви хочете отримати унікальний індекс:

CREATE TABLE IF NOT EXISTS Cell (
  cellId BIGINT UNSIGNED,
  attributeId BIGINT UNSIGNED,
  entityRowId BIGINT UNSIGNED,
  value DECIMAL(25,5),
  UNIQUE KEY `id_ce` (`cellId`,`entityRowId`)
)

Потім вставте в нього деякі значення:

INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );

Спробуйте зробити те ж саме ще раз, і ви отримаєте помилку повторюваного ключа, оскільки cellIdі entityRowIdє однаковими:

INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );

Дубльований запис "1-199" для ключа "id_ce"

Ось чому ми використовуємо команду upsert:

INSERT INTO Cell ( cellId, attributeId, entityRowId, value)
VALUES( 1, 6, 199, 300.0 )
ON DUPLICATE KEY UPDATE `value` = `value` + VALUES(`value`)

Ця команда приймає значення, 1.0яке вже є, як значення, і робить a value = value + 300.0.

Тож навіть після виконання наведеної команди в таблиці буде лише один рядок, і значення буде 301.0.


Чи не повинно кінцеве значення бути, 301.0а ні 302.0?
Алехандро

2
Можливо, це не прийнята відповідь, але додаткові подробиці для мене затягнулися і дали їй позитивний голос.
HPWD

1

Це синтаксис для упсерта

INSERT INTO `{TABLE}` (`{PKCOLUMN}`, `{COLUMN}`) VALUES (:value)
ON DUPLICATE KEY UPDATE `{COLUMN}` = :value_dup';

1

Якщо у вас є значення для ПК колонці, або унікального індексу на колонці , яка задовольняє єдиність, Ви можете використовувати INSERT IGNORE, INSERT INTO ... ON DUPLICATEабоREPLACE

Приклад з INSERT IGNORE

INSERT IGNORE INTO Table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (....);

Приклад з INSERT INTO .. ON DUPLICATE KEY UPDATE

SET @id = 1,
    @serverId = 123545,
    @channelId = 512580,
    @channelRole = 'john';
INSERT INTO Table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (@id, @serverId, @channelId, @channelRole)
ON DUPLICATE KEY UPDATE
    serverId = @serverId,
    channelId = @channelId,
    channelRole = @channelRole;

Приклад з Replace

REPLACE INTO table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (...);

0

Приклад для верху

INSERT INTO table1 (col1, col2, col3)
VALUES ($1, $2, $3)
ON CONFLICT (col1)
DO
UPDATE
SET col2 = $2, col3 = $3
WHERE col1 = $1
RETURNING col1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.