使用单个查询插入多行时,获取所有插入的ID

时间:2013-05-14 10:38:04

标签: php mysql database auto-increment

我已经查看了其他答案,我仍然认为我的问题是相关的,值得一个单独的条目。

我有一个名为settings的表(存储用户设置),我必须为每个用户插入多个设置。最初,我为每个设置执行了一个单独的insert语句,但是觉得这不是一个特别好的方法,我想通过相同的insert语句插入多行。我唯一的问题是我想要每个新插入行的auto_incremented ID。

我已经读过这样的答案,说这不可能/可扩展等,但我觉得我已经找到了解决方案。我想要反馈我的方式是否正确,因此这个问题。

我所做的很简单。插入多行后,我调用last_insert_id()来获取同时插入的行的第一行的ID。我已经计算了插入的行数,所以我只需要创建一个新数组并使用从last_insert_id()开始并以last_insert_id()+ n-1结尾的ID填充它(其中n是插入的行数)。

我觉得这会有效,原因如下:

1。)MYSQL文档声明last_insert_id()依赖于连接,如果另一个客户端/连接插入新记录,那么这不会影响其他客户端的last_insert_id()。

2.)我觉得由于插入是由单个SQL语句完成的,因此应将整个插入视为单个事务。如果这是真的,那么应该应用ACID规则并且auto_incremented值应该是顺序的。我不确定这个。

这些是我认为逻辑应该起作用的原因。所以我的问题是,上述逻辑是否适用于所有条件?我可以依靠它在所有情况下都能正常工作吗?我知道它目前对我有用。

7 个答案:

答案 0 :(得分:1)

如果你喜欢赌博 - 那么就这样做:)

要99%确定你必须锁定表格才能写作。您不确定(将来)两个交易将无法交织。

要100%确定您已阅读这些值。 (或分析源MySQL) 最好的解决方案是将日期添加到tablei编辑设置并阅读最新的。如果您不想更改结构,可以使用触发器http://dev.mysql.com/doc/refman/5.0/en/create-trigger.html

好的解决方案是刷新所有设置或仅修改对:键 - 设置名称

答案 1 :(得分:1)

不应依赖此行为;除了明显的锁定问题之外,假设您要设置主< - >主复制;突然间,id每次增加2。

除此之外,不是实际编写多个insert语句,而是使用预准备语句可能是值得的:

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

答案 2 :(得分:0)

我同意@anigel。你不能确定某些交易不会混淆。您可以将插入拆分为单独的查询,为每个单独的查询调用last_insert_id()并使用结果填充数组。

当然,这可能会增加处理时间,但至少可以确保避免冲突。另外,由于它是一个设置表,因此您不太可能每个客户端请求运行大量事务

答案 3 :(得分:0)

怎么样:

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

也许这会像你想要的那样返回你的插入id。我还没有对它进行测试,但也许你可以根据自己的目的调整它,谁知道它可能真的有效。

答案 4 :(得分:0)

我已经这样做是我的应用程序。我在循环中插入记录,当插入每个记录时,我将通过调用last_insert_id()将自动增量id存储在数组中。所以我可以在任何需要的地方使用插入id的数组。

答案 5 :(得分:0)

我做了一点虽然没有发现数据最终有用,所以停止了。

我跟踪表中每个条目的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';

这是一种很好的做法,因为您可以跟踪哪个用户修改了记录,而且不依赖于锁定或机会。

至于您自己的解决方案,它将起作用并保存查询。我会担心它如何响应繁忙的桌子上准备好的语句,并且可能使用的方法应该考虑这一点。我使用的方法不受这些问题的影响。

答案 6 :(得分:0)

有些事情一直困扰着我这个问题...为什么你需要每一行的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是对特定用户的引用(usersid)。

我还假设您有一个名为$ settings的关联数组,您试图将其放入数据库中。

$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.
}

现在当你需要用户的设置时,你可以用一个JOIN做一个SELECT来把所有的设置和用户信息放在一起,如果我把这一点搞砸了就原谅我,因为我到目前为止还不是mysql(i)专家,所以我不确定你是否需要做任何特别的事情,因为设置表中有多个条目和users表中的一个条目,但我相信如果我搞砸了,有人可以直接设置我们:

$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语句中使用语法时犯了一些错误,或者只是使用了多余的克劳斯(如) 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')";
}

我要注意的最后一件事是:除非您特别需要为用户设置相同设置的多个条目,否则您可以在INSERT查询之前执行UPDATE,如果生成$ mysqli-> insert_id,则只执行INSERT == 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)                         |
+--------------------------------------------------+

如果您将默认值time设置为CURRENT_TIMESTAMP或只是将日期('Ymd G:i:s')插入该字段,那么您可以通过在新设置时插入日志来跟踪设置更改已创建或更改。

如果UPDATE没有改变任何内容,要执行INSERTS,您可以使用两个单独的语句。也可以使用“INSERT VALUES()ON DUPLICATE KEY UPDATE ...”来执行此操作,但显然不建议在具有多个UNIQUE字段的表上使用此方法。使用后一种方法意味着单个查询,但您必须组合usersetting 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,但最终结果是相同的。仅当UPDATE没有更改表中的任何内容时才会调用INSERT语句(这表示该设置和该用户没有记录)。

我很抱歉这是一个非常冗长的回复,它偏离了你原来的问题,但我希望它与你的真正目的/目标相关。我期待您的回复以及有关如何从真正的SQL大师改进我的SQL语句的评论。