web-dev-qa-db-ja.com

PHP MySQL同じテーブル内の行をコピー...主キーと一意キーを使用して

私のテーブルには2つのキーがあります。1つは自動インクリメントID(PRIMARY)、もう1つはアイテムの名前(UNIQUE)です。

この同じテーブル内の行を複製することは可能ですか?私が試してみました:

INSERT INTO items
SELECT * FROM items WHERE id = '9198'

これにより、エラーDuplicate entry '9198' for key 'PRIMARY'が発生します

私も試しました:

INSERT INTO items
SELECT * FROM items WHERE id = '9198'
ON DUPLICATE KEY UPDATE id=id+1

エラーColumn 'id' in field list is ambiguous

また、項目名(UNIQUE)フィールドに関する限り、このフィールドも一意である必要があるため、項目名に(Copy)を追加する方法はありますか?

21
Norse

Id列を除くすべての列を明示的に選択します。

INSERT INTO items
(col1, col2, ..., coln)
SELECT col1, col2, ..., coln
FROM items
WHERE id = '9198'

あなたの次の質問はおそらくでしょう:

すべての列を明示的にリストせずにこれを行う方法はありますか?

回答:いいえ、そうは思いません。

45
Mark Byers

あなたが本当にマークの答えのようにすべてのテーブル列を一覧表示したくない場合は、これを試すことができます:

CREATE TEMPORARY TABLE temp_tbl SELECT * FROM items WHERE id = '9198';
SELECT @maxId := MAX(id) + 1 FROM items;
UPDATE temp_tbl SET id = @maxId;
INSERT INTO items SELECT * FROM temp_tbl;
DROP TABLE temp_tbl;

美しくも速くもない。しかし動作します。

24
Phius

優れたメンテナンスフリーのソリューションを提供してくれたhobaileyに感謝します。

MySQLi用に更新された、私が最終的に使用したコードは次のとおりです。

// Get the columns
$cols = array();
$result = $mysqli->query("SHOW COLUMNS FROM [TABLE]"); // Change table name

while ($r = $result->fetch_array(MYSQLI_ASSOC)) {
    if (!in_array($r["Field"], array("COLA", "COL4", "COL8"))) { // Edit array with any column names you want to exclude
        $cols[] = $r["Field"];
    }
}

// Build and do the insert
$result = $mysqli->query("SELECT * FROM [TABLE] WHERE [SELECTION CRITERIA];"); // Change table name and add selection criteria

while ($r = $result->fetch_array(MYSQLI_ASSOC)) {

    $insertSQL = "INSERT INTO [TABLE] (" . implode(", ",$cols) . ") VALUES ("; // Change table name
    $count = count($cols);

    foreach($cols as $counter=>$col) {
// This is where you can add any code to change the value of existing columns
        $insertSQL .= "'" . $mysqli->real_escape_string($r[$col]) . "'";
        if ($counter < ($count - 1)) {
            $insertSQL .= ", ";
        }
    } // END foreach

    $insertSQL .= ");";

    $mysqli->query($insertSQL);
    if ($mysqli->affected_rows < 1) {
// Add code if the insert fails
    } else {
// Add code if the insert is successful
    }

} // END while
4
Mark

または、すべての列を明示的に記述したくない場合(およびテーブルの作成や削除を開始したくない場合)、テーブルの列を取得してクエリを自動的に作成することができます。

//get the columns
$cols=array();
$result = mysql_query("SHOW COLUMNS FROM [table]"); 
 while ($r=mysql_fetch_assoc($result)) {
  if (!in_array($r["Field"],array("[unique key]"))) {//add other columns here to want to exclude from the insert
   $cols[]= $r["Field"];
  } //if
}//while

//build and do the insert       
$result = mysql_query("SELECT * FROM [table] WHERE [queries against want to duplicate]");
  while($r=mysql_fetch_array($result)) {
    $insertSQL = "INSERT INTO [table] (".implode(", ",$cols).") VALUES (";
    $count=count($cols);
    foreach($cols as $counter=>$col) {
      $insertSQL .= "'".$r[$col]."'";
  if ($counter<$count-1) {$insertSQL .= ", ";}//dont want a , on the last one
    }//foreach
  $insertSQL .= ")";

  mysql_query($insertSQL);//execute the query
  }//while

これはMySQLの非推奨コードを使用し、MySQLiである必要があることに注意してください。それも改善できると確信していますが、それは私が使用しているものであり、非常にうまく機能します。

4
hobailey

質問のタイトルには、PHPからこれを実行したいという記述があります。

同じ問題が発生し、テーブル構造を変更する(列の追加/削除)と、すべての列名を書き出すのは面倒で保守が困難です...一時テーブルを使用するソリューションは好きではありません。

PHP-うまく機能し、メンテナンスは不要です(免責事項:データベースアクセスにmeekrodbライブラリを使用します):から送信される2つのクエリでこの問題を解決することを選びました。

//get the data as an associative array
$row = DB::queryFirstRow("SELECT * FROM your_table WHERE id=%i",$id);
if ($row){
    unset($row["id"]); //unset the primary key
    DB::insert("your_table",$row);
    return DB::insertId();
} else {
    return false;
}

再挿入する前に、内部データに対してより多くの操作(他の列を無視するように設定解除、値の編集など)を実行することもできます。

2
Olivier

[〜#〜] php [〜#〜]の別の解決策は、特定の列なしで同じテーブル内の行をコピーするためのものです。主キー-"TEMPORARY TABLE"および "SHOW COLUMNS FROM ..."メソッドなし:

$stmt = $db->prepare("select * from table where id = :id;");
$stmt->bindValue(':id', $_GET['id'], PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
unset($row['id']);      //remove primary key

$columns = array_keys($row);
$query = "insert into table (`".implode('`, `', $columns)."`) select `".implode('`, `', $columns)."` from  data_ticket_serie where id = ".$_GET['id'].";";
// echo $query;
$stmt = $db->prepare($query);
$stmt->execute();

INSERTはSELECTステートメントであるため、値はステートメント内で直接ではありません-> "real_escape_string"などの問題はありません。

2
Oliver-xx

私は最近同様のことをしなければならなかったので、例が含まれている任意のサイズのテーブルの解決策を投稿すると思いました。それは、事実上あらゆるサイズのテーブルに調整できる構成配列を取ります。

$copy_table_row = array(
    'table'=>'purchase_orders',     //table name
    'primary'=>'purchaseOrderID',   //primary key (or whatever column you're lookin up with index)
    'index'=>4084,                  //primary key index number
    'fields' => array(
        'siteID',             //copy colunm
        ['supplierID'=>21],   //overwrite this column to arbirary value by wrapping it in an array
        'status',             //copy colunm
        ['notes'=>'copied'],  //changes to "copied"
        'dateCreated',        //copy colunm
        'approved',           //copy colunm
    ),
);
echo copy_table_row($copy_table_row);



function copy_table_row($cfg){
    $d=[];
    foreach($cfg['fields'] as $i => $f){
        if(is_array($f)){
            $d['insert'][$i] = "`".current(array_keys($f))."`";
            $d['select'][$i] = "'".current($f)."'";
        }else{
            $d['insert'][$i] = "`".$f."`";
            $d['select'][$i] = "`".$f."`";
        }
    }
    $sql = "INSERT INTO `".$cfg['table']."` (".implode(', ',$d['insert']).")
        SELECT ".implode(',',$d['select'])."
        FROM `".$cfg['table']."`
        WHERE `".$cfg['primary']."` = '".$cfg['index']."';";
    return $sql;
}

これは次のようなものを出力します:

INSERT INTO `purchase_orders` (`siteID`, `supplierID`, `status`, `notes`, `dateCreated`, `approved`)
SELECT `siteID`,'21',`status`,'copied',`dateCreated`,`approved`
FROM `purchase_orders`
WHERE `purchaseOrderID` = '4084';
1
Andy Gee

列数が多いテーブルでは、Phiusのアイデアに似た(はい、遅い)メソッドを使用します。
私は完全を期すためにここに置きました。

テーブル 'tbl'に次のように定義された 'id'があるとします。

id INT NOT NULL AUTO_INCREMENT PRIMARY KEY

次に、次の手順に従って行を複製/コピーできます。

  1. 一時テーブルを作成する

一時テーブルの作成tbl_tmp LIKE tbl;

  1. クローン/コピーするエントリを1つ以上挿入します

INSERT INTO tbl_tmp SELECT * FROM tbl WHERE ...;

  1. 'id'からAUTOINCREMENTタグを削除する

ALTER TABLE tbl_tmp MODIFY id INT;

  1. プライマリインデックスを削除する

ALTER TABLE tbl_tmp DROP PRIMARY KEY;

  1. 一意のインデックスを更新し、「id」を0に設定します(手順6を実行するには0が必要です)。

UPDATE tbl_tmp SET unique_value = ?, id = 0;

  1. 変更された行を「tbl」にコピーし、「id」を自動生成します。

INSERT INTO tbl SELECT * FROM tbl_tmp;に挿入します。

  1. クリーンアップ(または単にDB接続を閉じる)

DROP TABLE tbl_tmp;

他のテーブルの依存データも複製/コピーする必要がある場合は、各行に対して上記を実行します。手順6の後、最後に挿入されたキーを取得し、これを使用して、同じ手順で他のテーブル内の従属行を複製/コピーできます。

1
rockdaboot

PhpMyAdminを使用してクエリを作成することについて誰も言及しなかったことに驚いています。これにより、すべての列を追加するのが速くなり、次にwlfで前述したように、IDをnullまたはoに設定するだけです。

これは、これを行う最も簡単な方法です

INSERT INTO users SELECT 0,email,user FROM users WHERE id=10
1
The Amerloc

テーブルがuser(id,email,user)であり、WHERE句があるため、MAX(id)+1を使用できないとします。

INSERT INTO users SELECT 0,email,user FROM users WHERE id=10

ただし、INSERTを使用するときは常に列名を指定する必要があることに注意してください。

0
wlf

これは、任意のテーブルのレコードをコピーするための一般的な関数です。

/**
 * @param string $table         Name of table
 * @param array $primaryKey     Which record should be copied? array('nameOfColumnWithUniqueId' => "value")
 * @param array $excludeFields  Which columns should not be copied (e.q. Unique Cols)
 * @param string $database      Name of database
 * @return int                  ID of new inserted record
 */
function copyMysqlRow($table, $primaryKey, $excludeFields = array(), $database = "usr_web3_2")
{
    $field = key($primaryKey);
    $value = current($primaryKey);
    $sql = "
        SELECT
            *
        FROM
            $database.$table
        WHERE
          $field = '$value'
    ";

    $result = mysql_query($sql);
    $row = mysql_fetch_assoc($result);

    $cols = array();
    $values = array();
    foreach ($row AS $col=>$value) {
        if (!in_array($col, $excludeFields)) {
            $cols[] = "`" . $col . "`";
            $values[] = $value === null ? 'null' : "'" . $value . "'";
        }
    }

    $sql = sprintf(" INSERT INTO $database.$table (%s) VALUES  (%s) ", implode($cols, ','), implode($values, ','));

    mysql_query($sql);

    return mysql_insert_id();
}
0
MrCatGonzo

イベントテーブルの行をコピーしたかったのですが、マークのソリューションは非常に役に立ちました。少し短くしました。

public static function getColumnsOfTable($table,  $arr_exclude_cols=array()) {
    global $obj_db;

    $cols = array();
    $result = $obj_db->query("SHOW COLUMNS FROM `".$table."`");

    while ($r = $result->fetch_array(MYSQLI_ASSOC)) {
        if (!in_array($r["Field"], $arr_exclude_cols)) { 
            $cols[] = $r["Field"];
        }
    }

    return $cols;
}

そしてコピーのためのコード:

$cols = Utils::getColumnsOfTable('events', array('event_id'));

    $result1 = $obj_db->query('SELECT * FROM `events` WHERE `event_id` = '.$event_id);
    $arr_event = mysqli_fetch_array($result1, MYSQLI_NUM);
    unset($arr_event[0]);

    $insertSQL =  'INSERT INTO `events` (`' . implode('`, `',$cols) . '`) VALUES ("'. implode('","', $arr_event).'")'; 
0
Paul Wolbers