2013-05-14 57 views
11

我已经查看了其他答案,但仍然觉得我的问题是相关的并且值得单独输入。使用单个查询插入多行时获取所有插入的ID

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

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

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

我觉得这会由于以下原因有:

1),其LAST_INSERT_ID MYSQL文档状态()是连接依赖,如果其他客户端/连接插入新记录,则不会影响其他客户端的LAST_INSERT_ID()。

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

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

+0

我不确定关于第2点。如果是我,我会看看创建一个临时表,将每个last_insert_id添加到它,然后返回。或者更好的是,用另一种方式选择刚才发生的事情(用户并说一个datelastchanged时间戳) – 2013-05-14 10:45:50

+1

简单的答案no。它不健壮,尽管任何单独的last_insert_id()都可能依赖于连接,但它并不提供*保证*,所有插入都将位于并发块中,而不会与来自另一个并发进程的插入交织。在编程方面,依靠强大的工程设计比认为应该是好的感觉要好得多。 – Anigel 2013-05-14 10:50:46

+0

我同意上述问题。但是,可能会在要插入的表上添加一个额外的字段,并将其放入该唯一标识符中。然后,您可以读取该表以查找具有该唯一标识符的所有记录,以获取插入的id的列表。我仍然会担心哪个id引用的是你插入的数据集是未定义的。 – Kickstart 2013-05-14 12:51:49

回答

1

如果你喜欢赌博 - 然后做到这一点:)

为99%肯定你将不得不锁定表进行写入。您不确定(未来)两笔交易将无法交织。

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

良好的解决方案将刷新所有的设置或只对:键 - 设置名称

0

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

当然,这可能会增加处理时间,但至少可以确保避免冲突。此外,因为它是一个设置表,它极不可能的,你必须每运行客户端请求一吨的交易

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

这或许会像你想回到您的插入IDS 。我没有对它进行测试,但也许你可以根据你的目的进行调整,谁知道它可能会起作用。

+0

最后插入的id是基于会话的变量,因此下一个会覆盖前一个。 – 2013-06-07 03:43:31

+0

@Jack是否意味着在每个查询运行之后,在while语句循环之前,所有之前的insert_id都已经是历史记录,并且没有与结果一起维护? – txpeaceofficer09 2013-06-07 04:01:36

+0

是的,这是正确的:) – 2013-06-07 04:03:05

1

不应该依赖此行为;除了明显的锁定问题之外,假设您要设置主设备< - >主复制;突然,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(); 
+0

Dous这会导致实际数据库访问?没有事务执行插入,性能会变差吗? – Sangoku 2016-05-25 06:56:58

+1

@使用交易时,不应将三国绩效作为首要考虑因素;数据一致性是。 – 2016-05-29 21:22:35

+1

在一个案例中,我正在研究它。如果somone想知道表现。它快了4倍。 :) – Sangoku 2016-05-30 06:58:27

0

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

+0

这个回答有什么问题? – 2013-06-07 03:44:16

0

我这样做了一下,虽然没有找到有用的数据,所以停下来。

我跟踪每个条目的datetime和user_who_altered到表中,因此检索列表变得很简单。

但是设置随时间改变,而不是依靠现在这是很重要的():

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

这是很好的做法,你可以跟踪哪些用户修改过的记录,它不依赖于锁或机会。

至于你自己的解决方案,它将工作并保存查询。我担心它会如何响应忙碌的桌子上的预备陈述,也许所用的方法应该考虑这一点。我使用的方法是免疫这些问题的。

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关联这两个表,那么就不会像下面的代码更有意义(假设你有一个类似于上面的表结构):

我会假设$ 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. 
} 

现在,当你需要的用户的设置,你可以做一个加入一个SELECT把所有的设置和用户信息一起,原谅我,如果我虱子这一点,因为我远远不是一个MySQL(我)专家,所以我不确定你是否需要做任何特别的事情,因为设置表中有多个条目,用户表中有单个条目,但是我确定有人可以让我们两个都挺直,如果我搞砸:

$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(我)的专家和有可能的方式,以简化的东西,我可能犯了一些错误与信达x在我的JOIN语句上,或者只是使用多余的claus(es),比如GROUP BY。我从settings开始选择了一个拉,并且加入了users,因为我不确定是否将设置连接到用户将产生包含用户的单个结果以及来自匹配我们WHERE子句的设置的所有可能值。我只有加入AB,每个都有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,并且只在执行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)       | 
+--------------------------------------------------+ 

如果您的time默认的CURRENT_TIMESTAMP或刚插入日期(“YMD G:我:S” )添加到该字段中,然后您可以通过在创建或更改新设置时插入日志来跟踪对设置的更改。

要做INSERTS如果UPDATE没有改变任何东西,你可以使用两个单独的语句。也可以通过“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,但最终的结果是一样的。 INSERT语句只有在UPDATE没有改变表中的任何内容时才被调用(这将表明该设置和该用户没有记录)。

我很抱歉,这是一个非常冗长的答复,它偏离了你的原始问题,但我希望它是相对于你的真正目的/目标。我期待着您的回应,以及关于如何从真正的SQL高手改进我的SQL语句的意见。