MySQL Insérer dans plusieurs tables? (Normalisation de la base de données?)

136

J'ai essayé de rechercher un moyen d'accéder à des insertinformations dans plusieurs tables dans la même requête, mais j'ai découvert que c'était impossible? Donc, je veux inserten utilisant simplement plusieurs requêtes ie;

INSERT INTO users (username, password) VALUES('test', 'test')
INSERT INTO profiles (userid, bio, homepage) VALUES('[id of the user here?]','Hello world!', 'http://www.stackoverflow.com')

Mais comment puis-je donner l'incrémentation automatique iddu usersau "manuel" useridpour la profiletable?

Jay Wit
la source
8
Vous souhaitez en savoir plus sur les transactions.
vichle
3
duplication possible de sql - insérer dans plusieurs tables en une seule requête
Expiation limitée

Réponses:

241

Non, vous ne pouvez pas insérer dans plusieurs tables en une seule commande MySQL. Vous pouvez cependant utiliser des transactions.

BEGIN;
INSERT INTO users (username, password)
  VALUES('test', 'test');
INSERT INTO profiles (userid, bio, homepage) 
  VALUES(LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com');
COMMIT;

Jetez un œil à LAST_INSERT_ID()pour réutiliser les valeurs d'auto-incrémentation.

Edit: vous avez dit " Après tout ce temps à essayer de le comprendre, cela ne fonctionne toujours pas. Ne puis-je pas simplement mettre l'ID qui vient d'être généré dans un $ var et mettre ce $ var dans toutes les commandes MySQL? "

Laissez-moi élaborer: il y a 3 façons possibles ici:

  1. Dans le code que vous voyez ci-dessus. Cela fait tout dans MySQL, et LAST_INSERT_ID()dans la deuxième instruction sera automatiquement la valeur de la colonne d'auto-incrémentation qui a été insérée dans la première instruction.

    Malheureusement, lorsque la deuxième instruction elle-même insère des lignes dans une table avec une colonne auto-incrémentée, la LAST_INSERT_ID()sera mise à jour avec celle de la table 2, et non la table 1. Si vous avez toujours besoin de celle de la table 1 par la suite, nous devrons la stocker dans une variable. Cela nous amène aux voies 2 et 3:

  2. Stockera le LAST_INSERT_ID()dans une variable MySQL:

    INSERT ...
    SELECT LAST_INSERT_ID() INTO @mysql_variable_here;
    INSERT INTO table2 (@mysql_variable_here, ...);
    INSERT INTO table3 (@mysql_variable_here, ...);
  3. Stockera le LAST_INSERT_ID()dans une variable php (ou dans toute langue pouvant se connecter à une base de données, de votre choix):

    • INSERT ...
    • Utilisez votre langage pour récupérer le LAST_INSERT_ID(), soit en exécutant cette instruction littérale dans MySQL, soit en utilisant par exemple php mysql_insert_id()qui le fait pour vous
    • INSERT [use your php variable here]

AVERTISSEMENT

Quelle que soit la manière de résoudre ce problème que vous choisissez, vous devez décider de ce qui doit se passer si l'exécution est interrompue entre les requêtes (par exemple, votre serveur de base de données tombe en panne). Si vous pouvez vivre avec "certains ont fini, d'autres non", ne poursuivez pas votre lecture.

Si toutefois vous décidez "soit toutes les requêtes se terminent, soit aucune ne se termine - je ne veux pas de lignes dans certaines tables mais pas de lignes correspondantes dans d'autres, je veux toujours que mes tables de base de données soient cohérentes", vous devez envelopper toutes les instructions dans une transaction. C'est pourquoi j'ai utilisé le BEGINet COMMITici.

Commentez à nouveau si vous avez besoin de plus d'informations :)

Konerak
la source
3
Et si je veux insérer dans plus de 2 tables et que je veux que toutes les autres aient un identifiant unique et l'identifiant utilisateur? Est-ce possible?
Jay Wit
Vous pouvez mettre le last_insert_id de la table d'origine dans une variable MySQL et utiliser cette variable dans toutes vos autres tables. La suggestion de f00 pour l'utilisation d'une procédure stockée a encore plus de sens si vous allez manipuler beaucoup de table en une seule fois.
Konerak
3
@Jay Wit: J'ai mis à jour la réponse. 'Way 3' explique que vous pouvez en effet mettre l'ID dans une variable et le réutiliser dans toutes les commandes MySQL, mais vous devriez lire à propos des transactions si vous voulez que votre base de données soit cohérente en cas de crash.
Konerak
3
Bien sûr, ce sont des instructions MySQL acceptées, tout comme SELECT, UPDATE, INSERT et DELETE. Faites d'abord un petit testcript, et si tout fonctionne bien, vous êtes prêt à partir.
Konerak
2
@Konerak des conseils sur la façon d'utiliser ces multiples instructions SQL avec le @mysql_variablesen conjonction avec des instructions préparées?
Fernando Silva
17

assez simple si vous utilisez des procédures stockées:

call insert_user_and_profile('f00','http://www.f00.com');

script complet:

drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
username varchar(32) unique not null
)
engine=innodb;

drop table if exists user_profile;
create table user_profile
(
profile_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
homepage varchar(255) not null,
key (user_id)
)
engine=innodb;

drop procedure if exists insert_user_and_profile;

delimiter #

create procedure insert_user_and_profile
(
in p_username varchar(32),
in p_homepage varchar(255)
)
begin
declare v_user_id int unsigned default 0;

insert into users (username) values (p_username);
set v_user_id = last_insert_id(); -- save the newly created user_id

insert into user_profile (user_id, homepage) values (v_user_id, p_homepage);

end#

delimiter ;

call insert_user_and_profile('f00','http://www.f00.com');

select * from users;
select * from user_profile;
Jon Black
la source
2
Désolé, mais est-ce PHP? Je n'ai jamais rien vu de tel. J'utilise PHP et MySQL, des conseils?
Jay Wit
1
ressemble à un long ensemble d'instructions sql
Melbourne2991
1
@JayWit: Non, ce sont toutes des instructions SQL. Créez les procédures, puis vous pouvez les utiliser à tout moment. Voir dev.mysql.com/doc/refman/5.7/en/create-procedure.html
Pierre-Olivier Vares
7

Que se passerait-il si vous souhaitiez créer de nombreux enregistrements de ce type (pour enregistrer 10 utilisateurs, pas un seul)? Je trouve la solution suivante (seulement 5 requêtes):

Étape I: Créez une table temporaire pour stocker de nouvelles données.

CREATE TEMPORARY TABLE tmp (id bigint(20) NOT NULL, ...)...;

Ensuite, remplissez ce tableau avec des valeurs.

INSERT INTO tmp (username, password, bio, homepage) VALUES $ALL_VAL

Ici, au lieu de $ALL_VALvous placez une liste de valeurs: ('test1', 'test1', 'bio1', 'home1'), ..., ('testn', 'testn', 'bion', 'homen')

Étape II: envoyer les données à la table «utilisateur».

INSERT IGNORE INTO users (username, password)
SELECT username, password FROM tmp;

Ici, "IGNORE" peut être utilisé, si vous autorisez déjà certains utilisateurs à être à l'intérieur. En option, vous pouvez utiliser UPDATE similaire à l'étape III, avant cette étape, pour trouver les utilisateurs déjà à l'intérieur (et les marquer dans la table tmp). Ici, nous supposons que le nom d'utilisateur est déclaré comme PRIMARYdans la table des utilisateurs.

Étape III: appliquez la mise à jour pour lire tous les identifiants d'utilisateurs des utilisateurs à la table tmp. CECI EST UNE ÉTAPE ESSENTIELLE.

UPDATE tmp JOIN users ON tmp.username=users.username SET tmp.id=users.id

Étape IV: créer une autre table en utilisant l'ID de lecture pour les utilisateurs

INSERT INTO profiles (userid, bio, homepage) 
SELECT id, bio, homepage FROM tmp
Zasega Anonimno
la source
1
c'est 6 requêtes - n'oubliez pas de DROP
TEMPORARY
3

essaye ça

$sql= " INSERT INTO users (username, password) VALUES('test', 'test') ";
mysql_query($sql);
$user_id= mysql_insert_id();
if(!empty($user_id) {

$sql=INSERT INTO profiles (userid, bio, homepage) VALUES($user_id,'Hello world!', 'http://www.stackoverflow.com');
/* or 
 $sql=INSERT INTO profiles (userid, bio, homepage) VALUES(LAST_INSERT_ID(),'Hello   world!', 'http://www.stackoverflow.com'); */
 mysql_query($sql);
};

Références
PHP
MYSQL

diEcho
la source
2
Cela entraînerait très probablement un comportement indésirable si le serveur se bloque après la création de l'utilisateur mais avant la création du profil.
vichle
@Vichle alors quel est le meilleur moyen ??
diEcho
2
@vichle ajoutez votre sacrée transaction à ce code et arrêtez déjà ce non-sens
Votre bon sens
3
@Konerak J'utilise des scripts PHP depuis plus de 10 ans. Aucun d'eux n'a l'habitude de s'arrêter entre les lignes. vous feriez mieux de penser à améliorer la qualité de votre serveur pour qu'il ne plante pas SI souvent. C'est du web, mec. ce n'est pas la Réserve fédérale. Rien de mal avec un enregistrement d'utilisateur cassé de 100 000 000 d'entre eux réussis.
Votre bon sens
2
Dans cet exemple, vous avez peut-être raison. Mon code est toujours utilisé pour la comptabilité - l'idée de retirer de l'argent dans A et de ne pas l'ajouter dans B alors qu'il le devrait, est intolérable. De plus, même si ce n'est que du Web, une transaction n'est pas si difficile / coûteuse? IMHO, ils prennent de bonnes habitudes.
Konerak
3

jetez un œil à mysql_insert_id ()

ici la documentation: http://in.php.net/manual/en/function.mysql-insert-id.php

Daniel Kutik
la source
1
Cette fonction est le diable de la cohérence des bases de données.
vichle
d'accord - l'utilisation d'une transaction pourrait être la meilleure solution
Daniel Kutik
@vichle: est-ce vrai? Je n'utilise pas php si souvent, mais j'ai pensé que c'était leur raccourci pour appeler LAST_INSERT_ID()le programmeur?
Konerak
1
Toutes les requêtes sont des transactions. La valeur par défaut dans la plupart des langages de programmation est de valider automatiquement les transactions, car la plupart des transactions ne sont qu'une seule requête. La fonction mysql_insert_id () est destinée lorsque vous avez désactivé la validation automatique, mais est très souvent utilisée dans le mauvais contexte par des personnes qui ne connaissent pas le concept de transaction.
vichle
3
@dnl vous pouvez utiliser cette fonction ET une transaction très bien. cette remarque de transaction est sans rapport avec la question.
Votre bon sens
1

C'est la façon dont je l'ai fait pour un projet uni, fonctionne bien, probablement pas en sécurité tho

$dbhost = 'localhost';
$dbuser = 'root';
$dbpass = '';
$conn = mysql_connect($dbhost, $dbuser, $dbpass);

$title =    $_POST['title'];            
$name =     $_POST['name'];         
$surname =  $_POST['surname'];                  
$email =    $_POST['email'];            
$pass =     $_POST['password'];     
$cpass =    $_POST['cpassword'];        

$check = 1;

if (){
}
else{
    $check = 1;
}   
if ($check == 1){

require_once('website_data_collecting/db.php');

$sel_user = "SELECT * FROM users WHERE user_email='$email'";
$run_user = mysqli_query($con, $sel_user);
$check_user = mysqli_num_rows($run_user);

if ($check_user > 0){
    echo    '<div style="margin: 0 0 10px 20px;">Email already exists!</br>
             <a href="recover.php">Recover Password</a></div>';
}
else{
    $users_tb = "INSERT INTO users ". 
           "(user_name, user_email, user_password) ". 
        "VALUES('$name','$email','$pass')";

    $users_info_tb = "INSERT INTO users_info".
           "(user_title, user_surname)".
        "VALUES('$title', '$surname')";

    mysql_select_db('dropbox');
    $run_users_tb = mysql_query( $users_tb, $conn );
    $run_users_info_tb = mysql_query( $users_info_tb, $conn );

    if(!$run_users_tb || !$run_users_info_tb){
        die('Could not enter data: ' . mysql_error());
    }
    else{
        echo "Entered data successfully\n";
    }

    mysql_close($conn);
}

}

SebastianZdroana
la source
-1

Juste une remarque sur ton dicton

Salut, j'ai essayé de rechercher un moyen d'insérer des informations dans plusieurs tables dans la même requête

Mangez-vous tous vos plats du midi mélangés à des boissons dans le même bol?
Je suppose - non.

Pareil ici.
Il y a des choses que nous faisons séparément.
2 requêtes d'insertion sont 2 requêtes d'insertion. C'est bon. Rien de mal à cela. Pas besoin de l'écraser en un.
Idem pour select. La requête doit être sensée et faire son travail. Voilà les seules raisons. Le nombre de requêtes ne l'est pas.

En ce qui concerne les transactions - vous pouvez les utiliser, mais ce n'est pas si grave pour le site Web moyen. S'il arrivait une fois par an (le cas échéant) qu'un enregistrement d'utilisateur soit interrompu, vous pourrez le réparer, sans aucun doute.
il existe des centaines de milliers de sites exécutant mysql sans pilote de prise en charge des transactions. Avez-vous entendu parler de terribles catastrophes qui ont brisé ces sites? Moi non plus.

Et mysql_insert_id () n'a rien à voir avec les transactions. vous pouvez inclure dans la transaction tout droit. c'est juste des questions différentes. Quelqu'un a soulevé cette question de nulle part.

Votre bon sens
la source
Pourquoi voudriez-vous risquer d'avoir à réparer des choses comme ça alors que c'est facilement évitable?
vichle
@vichle mes tables sont de type myisam. Par souci de recherche plein texte, d'héritage et d'habitude. Je risque, ouais. Quel terrible péril (en théorie) m'attend.
Votre bon sens
6
Pourquoi êtes-vous si agressif? Je dis simplement que les transactions sont la voie à suivre ici. Vous le faites à votre façon si vous le souhaitez, le fait est toujours que des transactions ont été créées pour ce genre de situation.
vichle
@vichle ouais, j'étais assez dur, je m'excuse. C'est la vôtre That function is the devil of database consistency.qui a fait cela, pas les transactions. Je ne vois rien de mal dans les transactions.
Votre bon sens
Excuses acceptées. Tout ce que je voulais dire, c'est que la fonction est souvent mal utilisée par des personnes qui ne sont pas au courant de l'existence de transactions. Ces personnes sont également assez surreprésentées dans la communauté php.
vichle
-4

Pour l'AOP, vous pouvez le faire

$stmt1 = "INSERT INTO users (username, password) VALUES('test', 'test')"; 
$stmt2 = "INSERT INTO profiles (userid, bio, homepage) VALUES('LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com')";

$sth1 = $dbh->prepare($stmt1);
$sth2 = $dbh->prepare($stmt2);

BEGIN;
$sth1->execute (array ('test','test'));
$sth2->execute (array ('Hello world!','http://www.stackoverflow.com'));
COMMIT;
ocnet
la source
C'est exactement ce que je recherche.
Subroto Biswas le