0

我已经查看了SQL 主键约束,尽管记录不存在SO 上的所有问题中哪一个似乎最接近我的问题,但并不相同。

我毫不怀疑我可能在做一些愚蠢的事情,但这里是:

我正在尝试编写一个脚本(在 php 中),它将数据(没有结构,它假设结构已经完成)从任何给定的 PDO 数据库迁移到任何其他给定的 PDO 数据库 - 在我的情况下,我正在 sqlite3 上对其进行测试 - > mysql。

当我在我的测试数据库上运行脚本时,我得到“完整性约束违规:1062 Duplicate entry '1' for key 'PRIMARY'”我不太明白,因为表中没有数据(即使在脚本运行之前它也是删除语句)。

我假设它与主键是 auto_increment 的事实有关,但我尝试将下一个增量值设置为插入的任何值以外的值(认为我尝试将其设置为 80) - 没有区别。

我一直在寻找一种在交易期间禁用 auto_increment 的方法,但没有事先更改表,然后再将其更改回来,我想不出办法 - 更改整个表似乎是错误的,我真的不想涉及任何 DDL。

  1 <?php
  2
  3 $abspath = dirname(__FILE__)."/";
  4
  5 $source_dsn = 'sqlite:'.$abspath.'db.sqlitedb';
  6 $source_username = null;
  7 $source_password = null;
  8 $target_dsn = "mysql:dbname=name;host=127.0.0.1";
  9 $target_username = "name";
 10 $target_password = "pass";
 11
 12 $transfer_data = array();
 13 $table_data = array();
 14
 15 try {
 16
 17         // connect to source
 18         $source = new PDO($source_dsn, $source_username, $source_password);
 19         $source->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 20
 21         // connect to target
 22         $target = new PDO($target_dsn, $target_username, $target_password);
 23         $target->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 24
 25         //TODO Generalise this statement to all database types.
 26         $stmt = $source->prepare("SELECT * FROM sqlite_master WHERE type='table';");
 27         $stmt->execute();
 28
 29         // get all tables
 30         while($tablerow = $stmt->fetch(PDO::FETCH_ASSOC))
 31         {
 32                 // TODO Generalise these statements to all database types.
 33                 $transfer_data[$tablerow['tbl_name']] = array();
 34                 $table_data[$tablerow['tbl_name']] = array();
 35         }
 36         $stmt->closeCursor();
 37
 38         // for each table, load data
 39         foreach($transfer_data as $tablename => $void)
 40         {
 41                 $stmt = $source->prepare("SELECT * FROM $tablename;");
 42                 $stmt->execute();
 43                 // load data row at a time
 44                 while($datarow = $stmt->fetch(PDO::FETCH_ASSOC))
 45                 {
 46                         // store data for later
 47                         $transfer_data[$tablename][] = $datarow;
 48                         // if we haven't gained column data yet, do so now
 49                         if(!array_key_exists($tablename,$table_data))
 50                         {
 51                                 $t_data = array();
 52                                 foreach($datarow as $colname => $void)
 53                                 {
 54                                         $t_data[] = $colname;
 55                                 }
 56                                 $table_data[$tablename] = $t_data;
 57                         }
 58                 }
 59                 $stmt->closeCursor();
 60                 echo "Read $tablename\n";
 61         }
 62
 63         //start a transaction (if driver supports transactions / if not then this is noop)
 64         $target->beginTransaction();
 65         // for each table clear existing data and insert copied data
 66         foreach($table_data as $tablename => $columns)
 67         {
 68                 // not using an empty/truncate because mysql and possibly others autocommit
 69                 $stmt = $target->prepare("DELETE FROM $tablename;");
 70                 $stmt->execute();
 71                 $stmt->closeCursor();
 72
 73                 // prepare the insert statement - we don't know how many columns so is dynamic
 74                 $querystr = "INSERT INTO $tablename (".join(", ",$columns).") VALUES (";
 75                 foreach($columns as $k => $column)
 76                 {
 77                         $columns[$k] = ':'.$column;
 78                 }
 79                 // using named placeholders so order doesn't matter
 80                 $querystr = $querystr.join(", ",$columns).");";
 81                 $stmt = $target->prepare($querystr);
 82                 //echo "Using: $querystr\n";
 83                 $rowcount = 0;
 84                 // for each row of data, bind data and execute insert statement
 85                 foreach($transfer_data[$tablename] as $rowdata)
 86                 {
 87                         foreach($rowdata as $rowname => $rowvalue)
 88                         {
 89                                 $stmt->bindParam(':'.$rowname, $rowvalue);
 90                         }
 91                         $stmt->execute();
 92                         $stmt->closeCursor();
 93                         $rowcount++;
 94                 }
 95                 echo "Written $rowcount rows to $tablename\n";
 96         }
 97         $target->commit();
 98
 99 }
100 catch (PDOException $e)
101 {
102         echo 'PDO Error: '.get_class($e).' - '.$e->getMessage()."\n";
103         echo 'Query String was: '.$querystr."\nData:\n";
104         var_export($transfer_data[$tablename]);
105         if($target->inTransaction()){
106                 $target->rollBack();
107         }
108 }
109

现在我的目标数据库中有一个表:

+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| channel_id        | int(11)          | NO   | PRI | NULL    | auto_increment |
| channel_parent_id | int(10) unsigned | YES  |     | NULL    |                |
| server_id         | int(10) unsigned | NO   | MUL | NULL    |                |
+-------------------+------------------+------+-----+---------+----------------+

输出是:

PDO Error: PDOException - SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'PRIMARY'
Query String was: INSERT INTO channels (channel_id, channel_parent_id, server_id) VALUES (:channel_id, :channel_parent_id, :server_id);
Data:
array (
  0 =>
  array (
    'channel_id' => '1',
    'channel_parent_id' => '0',
    'server_id' => '1',
  ),
  1 =>
  array (
    'channel_id' => '24',
    'channel_parent_id' => '0',
    'server_id' => '1',
  ),
  2 =>
  array (
    'channel_id' => '34',
    'channel_parent_id' => '0',
    'server_id' => '1',
  ),
4

3 回答 3

2

很高兴你解决了这个问题。但是,这是为了解决对您不起作用的原因。bindParam()这不是一个错误,它是按照设计的方式工作的。

根据文档

将 PHP 变量绑定到用于准备语句的 SQL 语句中的相应命名或问号占位符。与 PDOStatement::bindValue() 不同,该变量被绑定为引用,并且只会在调用 PDOStatement::execute() 时进行评估

(强调我的)

考虑到上述情况,这:

 87                         foreach($rowdata as $rowname => $rowvalue)
 88                         {
 89                                 $stmt->bindParam(':'.$rowname, $rowvalue);
 90                         }

...将$rowvalue 通过引用绑定每个参数,在查询执行时,将始终是1(的最后一个元素$rowdata

使其工作的方法bindParam()如下:

 87                         foreach($rowdata as $rowname => $rowvalue)
 88                         {
 89                                 $stmt->bindParam(':'.$rowname, $rowdata[$rowname]);
 90                         }

...或者,也许,甚至:

 87                         foreach($rowdata as $rowname => &$rowvalue)
 88                         {
 89                                 $stmt->bindParam(':'.$rowname, $rowvalue);
 90                         }

...这样每个参数都会引用相应的数组元素

如上所述,另一种选择是按值而不是按引用bindValue()绑定参数。这意味着参数将在调用时进行评估,而不是在实际需要时进行评估(即查询执行):bindValue()

 87                         foreach($rowdata as $rowname => $rowvalue)
 88                         {
 89                                 $stmt->bindValue(':'.$rowname, $rowvalue);
 90                         }

当然,另一种选择是execute()输入一组参数,这可以execute()解决绑定部分(因此是我个人最喜欢的!)

于 2013-11-08T19:14:33.553 回答
0

在尝试将数据插入循环时打印数据。如果您在一个事务中,并尝试在同一事务中插入 2 条具有相同主键的记录,您将在第二条记录中收到错误,并且如果您回滚,表将保持为空。

在 MySQL 中不需要禁用 auto_increment。插入上的显式 pk 值应该只为下一个非显式插入提前 id 计数器。

于 2013-11-08T13:37:38.390 回答
0

如果您尝试手动插入的值与自动增量打开的数字相同,我很确定为 auto_increment 字段设置插入值将失败。插入时可以忽略 auto_increment 字段,但获取 last_insert_id()

这是 auto_increment 是有原因的;-)。让 MySQL 设置它。

于 2015-12-15T21:30:34.723 回答