Получать все вставленные идентификаторы при вставке нескольких строк с использованием одного запроса

Я уже посмотрел на другие ответы, и я все еще чувствую, что мой вопрос уместен и заслуживает отдельной записи.

У меня есть таблица с именем settings (которая хранит пользовательские настройки), и мне нужно вставить несколько настроек для каждого пользователя. Первоначально я выполнил отдельный оператор insert для каждого параметра, но, почувствовав, что это не очень хороший способ сделать это, я подумал о вставке нескольких строк одним и тем же вложением. Моя единственная проблема заключается в том, что мне нужны идентификаторы auto_incremented для каждой из вновь вставленных строк.

Я прочитал ответы, которые говорят, что это невозможно / масштабируемо и т. Д., Но я чувствую, что я ударил по решению. Мне нужна обратная связь, верен ли мой путь или нет, и, следовательно, этот вопрос.

То, что я сделал, просто. После ввода нескольких строк я вызываю last_insert_id (), чтобы получить идентификатор первой строки одновременно вставленных строк. У меня уже есть счетчик числа вставленных строк, поэтому я просто создаю новый массив и заполняю его идентификаторами, начинающимися с last_insert_id () и заканчивая last_insert_id () + n-1 (где n – количество вставленных строк).

Я считаю, что это будет работать по следующим причинам:

1.) Документация MYSQL гласит, что last_insert_id () зависит от соединения, и если другой клиент / соединение вставляет новые записи, то это не повлияет на last_insert_id () другого клиента.

2.) Я чувствую, что, когда вставка выполняется с помощью одного оператора SQL, вся вставка должна рассматриваться как одна транзакция. Если это так, то должны применяться правила ACID, а значения auto_incremented должны быть последовательными. Я не уверен в этом.

Это мои причины, по которым я чувствую, что логика должна работать. Итак, мой вопрос заключается в том, будет ли приведенная выше логика работать для ВСЕХ условий? Могу ли я полагаться на это, чтобы работать правильно во всех ситуациях? Я знаю, что это работает для меня в настоящее время.

Если вам нравится играть – тогда сделайте это 🙂

Чтобы быть на 99% уверенным, вам придется заблокировать стол для записи. Вы не уверены, что (в будущем) две транзакции не смогут переплетаться.

Чтобы быть на 100% уверенным, что вы читаете эти значения. (Или проанализировать исходный MySQL). Лучшим решением было бы добавить дату в настройки редактирования таблицы и прочитать последнюю. Если вы не хотите изменять структуру, вы можете использовать триггеры http://dev.mysql.com/doc/refman/5.0/en/create-trigger.html .

Хорошее решение будет обновлять все ваши настройки или только пары: key – setting name

На это поведение нельзя полагаться; помимо очевидных проблем с блокировкой, допустим, вы хотите установить главную репликацию master <->; внезапно, приращение id на 2 каждый раз.

Кроме того, вместо того, чтобы писать несколько операторов вставки, может быть полезно использовать подготовленные заявления:

$db = new PDO(...); $db->beginTransaction(); $stmt = $db->prepare('INSERT INTO `mytable` (a, b) VALUES (?, ?)'); foreach ($entries as $entry) { $stmt->execute(array($entry['a'], $entry['b'])); $id = $db->lastInsertId(); } $db->commit(); 

Я согласен с @anigel. Вы не можете быть уверены, что некоторые транзакции не будут запутаны. Вы можете разделить вставки на отдельные запросы, вызвать last_insert_id () для каждого отдельного запроса и заполнить массив результатами.

Конечно, это может увеличить время обработки, но, по крайней мере, вы можете быть уверены в предотвращении конфликтов. Плюс, так как его таблица настроек, маловероятно, что вам придется запускать тонну транзакций на запрос клиента

Как насчет:

 $id = array(); $sql = "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; $sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; $sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; $sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; $sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; $sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; if ($db=new mysqli('host', 'user', 'pass', 'dbase')) { $db->multi_query($sql); if ( isset($db->insert_id) ) $id[] = $db->insert_id; while ($db->more_results()) { if ($db->next_result()) $id[] = $db->insert_id; else trigger_error($db->error); } $db->close(); } else trigger_error($db->error); if (count($id) == 0) trigger_error('Uh oh! No inserts succeeded!'); else print_r($id); 

Возможно, это вернет ваши вставки, как вы этого хотите. Я не тестировал его, но, возможно, вы могли бы адаптировать его для своей цели, и кто знает, что он действительно может работать.

Я уже делаю это мое приложение. Я вставляю записи в цикл, и когда каждая запись вставлена, я буду хранить идентификатор Auto increment в массиве по вызову last_insert_id (). Поэтому я могу использовать массив вставленных id, где мне когда-либо понадобится.

Я сделал это немного, но не нашел полезные данные в конце, поэтому остановился.

Я отслеживаю datetime и user_who_altered каждой записи в таблице, поэтому получение списка становится простым.

Однако важно установить переменную со временем, а не полагаться на NOW ():

  INSERT INTO table (username,privelege,whenadded,whoadded) VALUES ('1','2','$thetime','$theinserter'),('1','5','$thetime','$theinserter'),etc...; 

будет вставлять несколько строк красиво.

для извлечения массива, который вы ищете:

 SELECT idrow FROM table WHERE username='1' AND whenadded='$thetime' AND whoadded='$theinserter'; 

Это хорошая практика, поскольку вы можете отслеживать, какой пользователь изменил запись, и это не зависит от блокировок или шансов.

Что касается вашего собственного решения, он будет работать и сохраняет запрос. Я буду беспокоиться о том, как он может реагировать на подготовленные заявления на занятой таблице, хотя, возможно, и используемый метод должен учитывать это. Метод, который я использую, невосприимчив к таким проблемам.

Что-то меня беспокоило об этом вопросе … зачем вам нужно вставлять insert_id из каждой строки? Мне кажется, что если вы вставляете в таблицу настроек, чтобы скорректировать настройки для пользователя, было бы лучше добавить какой-то пользовательский ключ в таблицу. Я, вероятно, просто не вижу всей картины, но это идея, которую я получаю:

 +---------------------------------------+ | `settings` | +------+--------+-----------+-----------+ | id | user | setting | value | +------+--------+-----------+-----------+ | `id` INT(11) PRIMARY AUTO_INCREMENT | +---------------------------------------+ | `user` INT(11) | +---------------------------------------+ | `setting` VARCHAR(15) | +---------------------------------------+ | `value` VARCHAR(25) | +---------------------------------------+ +---------------------------------------+ | `users` | +------+--------+--------+--------------+ | id | name | email | etc | +------+--------+--------+--------------+ | `id` INT(11) PRIMARY AUTO_INCREMENT | +---------------------------------------+ | `name` VARCHAR(32) | +---------------------------------------+ | `email` VARCHAR(64) | +---------------------------------------+ | `etc` VARCHAR(64) | +---------------------------------------+ 

Мое основное мышление здесь заключается в том, что то, что вы делаете с insert_id (s), тогда является тем, как пытаться создать ссылку между пользователями и настройками, чтобы вы знали, кто задал то, что вы дадите, и верните их настройки позже.

Если я полностью потерял смысл, пожалуйста, верните меня по теме, и я попытаюсь придумать другое решение, но если я удалюсь где угодно, где бы вы ни пытались пойти:

Если вы действительно пытаетесь сопоставить две таблицы с помощью insert_id (s), тогда не будет похоже на код ниже (если у вас есть структура таблицы, аналогичная приведенной выше):

Я предполагаю, что $ user является ссылкой на конкретного пользователя ( users . id ).

Я также предполагаю, что у вас есть ассоциативный массив с параметрами $, которые вы пытаетесь поместить в базу данных.

 $mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Could not connect.'); if ($mysqli) { foreach ($settings AS $setting_name=>$setting_value) { // I'll do individual queries here so error checking between is easy, but you could join them into a single query string and use a $mysqli->multi_query(); // I'll give two examples of how to make the multi_query string below. $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'; } $mysqli->close() // Since we know in this scope that $mysqli was created we need to close it when we are finished with it. } 

Теперь, когда вам понадобятся настройки пользователя, вы можете сделать SELECT с помощью JOIN, чтобы поместить все настройки и информацию о пользователе вместе и простить меня, если я вставлю этот бит, потому что я, безусловно, не эксперт mysql (i), поэтому я ' m не уверен, что вам нужно будет сделать что-нибудь особенное: в таблице настроек есть несколько записей и одна запись в таблице пользователей, но я уверен, что кто-то может установить нас прямо, если я это испортил:

 $mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Unable to connect.'); if ($mysqli) { $sql = "SELECT `users`.`name`, `users`.`email`, `users`.`id`, `users`.`etc`, `settings`.`setting`, `settings`.`value` FROM `settings` JOIN (`users`) ON (`users`.`id`=`settings`.`user`) WHERE `settings`.`user`=$user GROUP BY `settings`.`user` ORDER BY `settings`.`user` ASC"; if ($result=$mysqli->query($sql)) { if ($result->num_rows == 0) echo "Uh oh! $user has no settings or doesn't exist!"; else { // Not sure if my sql would get multiple results or if it would get it all in one row like I want so I'll assume multiple which will work either way. while ($row=$result->fetch_array()) print_r($row); // Just print the array of settings to see that it worked. } $result->free(); // We are done with our results so we release it back into the wild. } else trigger_error('[MYSQLI]: '.$mysqli->error . '['.$sql.']'); $mysqli->close(); // Our if ($mysqli) tells us that in this scope $mysqli exists, so we need to close it since we are finished. } 

Как я уже говорил ранее, я далеко не эксперт mysql (i), и, вероятно, есть способы упорядочить вещи, и я, возможно, допустил некоторые ошибки с синтаксисом в своем заявлении JOIN или просто использовал лишние claus (ы), такие как GROUP BY , Я выбрал SELECT из settings и присоединил users к нему, потому что я не был уверен, что присоединение настроек к пользователям приведет к одному результату, содержащему пользователей и все возможные значения, из параметров, которые соответствуют нашему предложению WHERE. Я только когда-либо присоединился к A с B где в каждом из них было 1 результат, но я уверен, что JOIN находится в правильной общей области.

В качестве альтернативы нескольким запросам я сказал, что приведу примеры построения одной строки запроса для multi_query, так что:

 $arr = array(); foreach($settings AS $setting_name=>$setting_value) { $arr[] = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; } $sql = join('; ', $arr); 

или

 foreach($settings AS $setting_name=>$setting_value) { $sql = ( isset($sql) ) ? $sql.'; '."INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')" : "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; } 

Последнее, что я хочу отметить: если вам специально не нужны несколько записей одного и того же параметра для пользователя, тогда вы можете сделать UPDATE перед запросом INSERT и только сделать INSERT, если получится $ mysqli-> insert_id == 0. Если вы пытаетесь сохранить историю изменений настроек для пользователей, и поэтому вам нужно несколько записей, я бы предложил создать отдельную таблицу с журналом, содержащим структуру таблицы, такую ​​как:

 +--------------------------------------------------+ | `logs` | +------+--------+--------+-----------+-------------+ | id | time | user | setting | new_value | +------+--------+--------+-----------+-------------+ | `id` INT(11) PRIMARY AUTO_INCREMENT | +--------------------------------------------------+ | `time` TIMESTAMP | +--------------------------------------------------+ | `user` INT(11) | +--------------------------------------------------+ | `setting` INT(11) | +--------------------------------------------------+ | `new_value` VARCHAR(25) | +--------------------------------------------------+ 

Если вы установите значение по умолчанию CURRENT_TIMESTAMP или просто вставьте дату («Ymd G: i: s») в это поле, вы можете отслеживать изменения настроек, вставляя их в журнал при создании или изменении новых параметров.

Чтобы делать ВСТАВКИ, если ОБНОВЛЕНИЕ ничего не изменило, вы могли бы использовать два отдельных оператора. Это также можно сделать с помощью «INSERT VALUES () ON DUPLICATE KEY UPDATE …», но этот метод, по-видимому, не рекомендуется использовать для таблиц с несколькими полями UNIQUE. Использовать последний метод будет означать один запрос, но вам нужно будет сделать комбинацию user и setting UNIQUE (или, возможно, DISTINCT?). Если вы setting UNIQUE, вы могли бы иметь только одну setting = 'foo' в таблице, если я не ошибаюсь. Чтобы сделать это с двумя утверждениями, вы сделали бы что-то вроде:

 $sql = "UPDATE `settings` SET `value`='bar' WHERE `user`=$user AND `setting`='foo' LIMIT 1"; $mysqli->query() or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'); if ( $mysqli->affected_rows == 0 ) { $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, 'foo', 'bar')"; $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'); } 

В приведенном выше коде вы можете заменить $ mysqli-> insert_id на $ mysqli-> affected_rows, если хотите, но конечный результат тот же. Оператор INSERT вызывается только в том случае, если UPDATE ничего не меняет в таблице (что указывает на отсутствие записей для этого параметра и этого пользователя).

Я прошу прощения, что это очень длинный ответ, и он отклонился от вашего первоначального вопроса, но я надеюсь, что он согласен с вашей истинной целью / целью. Я с нетерпением жду вашего ответа и комментариев о том, как улучшить мои SQL-запросы от истинных мастеров SQL.