web-dev-qa-db-fra.com

PHP MySQL Copier une ligne dans la même table ... avec une clé primaire et unique

Ma table a deux clés, l’une est un identifiant auto-incrémenté (PRIMARY), l’autre est le nom de l’article (UNIQUE).

Est-il possible de dupliquer une ligne dans cette même table? J'ai essayé:

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

Cela donne l'erreur Duplicate entry '9198' for key 'PRIMARY'

J'ai aussi essayé:

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

Ce qui donne l'erreur Column 'id' in field list is ambiguous 

Et en ce qui concerne le champ nom d’élément (UNIQUE), existe-t-il un moyen d’ajouter (Copy) au nom de l’élément, puisque ce champ doit également être unique?

17
Norse

Sélectionnez toutes les colonnes explicitement, à l'exception de la colonne id:

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

Votre prochaine question sera probablement:

Y a-t-il un moyen de faire cela sans lister explicitement toutes les colonnes?

Réponse: non, je ne pense pas.

38
Mark Byers

Si vous vraiment ne voulez pas lister toutes les colonnes du tableau comme dans la réponse de Mark, essayez ceci:

CREATE TEMPORARY TABLE temp_tbl SELECT * FROM items WHERE id = '9198';
UPDATE temp_tbl SET id = id + 1;
INSERT INTO items SELECT * FROM temp_tbl;
DROP TABLE temp_tbl;

Pas beau, pas rapide. Mais ça marche.

22
Phius

Merci à Hobailey pour sa solution sans entretien.

Voici le code que j'ai finalement utilisé, qui est mis à jour pour 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
3
Mark

Si vous ne voulez pas écrire explicitement toutes les colonnes (et ne voulez pas commencer à créer/supprimer des tables), vous pouvez simplement obtenir les colonnes de la table et construire la requête automatiquement:

//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

Notez que cela utilise le code déprécié de MySQL et que cela devrait être MySQLi. Je suis sûr que cela pourrait également être amélioré, mais c'est ce que j'utilise et cela fonctionne très bien.

3
hobailey

Le titre de la question indique que vous souhaitez le faire à partir de PHP.

J'ai rencontré le même problème et écrire tous les noms de colonnes est fastidieux et difficile à maintenir si vous modifiez la structure de votre table (ajout/suppression de colonnes) ... et je n'aime pas les solutions qui utilisent des tables temporaires. 

J'ai choisi de résoudre ce problème avec deux requêtes envoyées par PHP - fonctionne très bien et ne nécessite aucune maintenance (avertissement: j'utilise la bibliothèque meekrodb pour accéder à une base de données) :

//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;
}

Vous pouvez même effectuer davantage de manipulations sur les données internes (désélectionner d'autres colonnes à ignorer, modifier des valeurs, etc.) avant de les réinsérer. 

2
Olivier

Une autre solution dansPHPpour copier une ligne de la même table sans colonne spécifique/p. Ex. clé primaire - et sans les méthodes "TABLE TEMPORAIRE" et "AFFICHER LES COLONNES DE ...":

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

L'INSERT est une instruction SELECT, les valeurs ne sont donc pas directes dans l'instruction -> aucun problème avec "real_escape_string" ou quelque chose comme ça.

2
Oliver-xx

Pour les tables à plusieurs colonnes, j'utilise une méthode (oui lente) similaire à celle de Phius idea .
Je le mets ici juste pour être complet.

Supposons que la table 'tbl' a un 'id' défini comme

id INT NON NULL AUTO_INCREMENT PRIMARY KEY

Ensuite, vous pouvez cloner/copier une ligne en procédant comme suit:

  1. créer une table tmp

CREATE TEMPORARY TABLE tbl_tmp LIKE tbl;

  1. Insérer une ou plusieurs entrées que vous souhaitez cloner/copier

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

  1. supprimer la balise AUTOINCREMENT de 'id'

ALTER TABLE tbl_tmp MODIFY id INT;

  1. déposer l'index primaire

ALTER TABLE tbl_tmp DROP PRIMARY KEY;

  1. mettez à jour vos index uniques et définissez "id" sur 0 (0 nécessaire pour que l'étape 6 fonctionne)

UPDATE tbl_tmp SET valeur_unique = ?, id = 0;

  1. copiez vos lignes modifiées dans 'tbl' avec 'id' généré automatiquement.

INSERT INTO tbl SELECT * FROM tbl_tmp;

  1. nettoyage (ou fermez simplement la connexion à la base de données)

DROP TABLE tbl_tmp;

Si vous avez également besoin de cloner/copier des données dépendantes dans d'autres tables, procédez comme suit Pour chaque ligne. Après l'étape 6, vous pouvez obtenir la dernière clé insérée et l'utiliser pour Cloner/copier les lignes dépendantes dans d'autres tables en suivant la même procédure.

1
rockdaboot

Je suis surpris que quiconque n'ait pas mentionné l'utilisation de phpMyAdmin pour créer la requête. Parce que cela rendrait rapide l'ajout de toutes les colonnes et ensuite vous définissez l'identifiant sur null ou o comme mentionné ci-dessus par wlf 

C'est de loin le moyen le plus simple de le faire 

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

Supposons que la table est user(id,email,user) et que vous avez une clause WHERE, vous ne pouvez pas utiliser MAX(id)+1:

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

Notez cependant que vous devez toujours spécifier les noms de colonne lorsque vous utilisez INSERT.

0
wlf

Ceci est une fonction générale pour copier un enregistrement de n'importe quelle table:

/**
 * @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

Je voulais copier une ligne dans ma table d'événements et j'ai trouvé la solution de Mark très utile. Je l'ai fait un peu plus court.

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

et le code pour copier:

$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