MySQL Insertar en varias tablas? (¿Normalización de la base de datos?)

136

Intenté buscar una forma de insertinformación en varias tablas en la misma consulta, pero descubrí que es imposible. Entonces lo quiero insertsimplemente usando múltiples consultas, es decir;

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

Pero ¿cómo puedo dar el incremento automático idde la usersal "manual" useridpara la profilemesa?

Jay Wit
fuente
8
Desea aprender sobre transacciones.
vichle
3
posible duplicado de sql - insertar en varias tablas en una consulta
Expiación limitada

Respuestas:

241

No, no puede insertar en varias tablas en un comando MySQL. Sin embargo, puede usar transacciones.

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;

Eche un vistazo LAST_INSERT_ID()para reutilizar los valores de aumento automático.

Editar: usted dijo " Después de todo este tiempo tratando de resolverlo, todavía no funciona. ¿No puedo simplemente poner la ID recién generada en $ var y poner ese $ var en todos los comandos de MySQL? "

Permítanme explicarlo: hay 3 formas posibles aquí:

  1. En el código que ves arriba. Esto lo hace todo en MySQL, y el LAST_INSERT_ID() en la segunda instrucción será automáticamente el valor de la columna de autoincremento que se insertó en la primera instrucción.

    Desafortunadamente, cuando la segunda instrucción inserta filas en una tabla con una columna de incremento automático, LAST_INSERT_ID()se actualizará a la de la tabla 2 y no a la tabla 1. Si aún necesita la de la tabla 1 después, tendremos que almacenarla en una variable Esto nos lleva a las formas 2 y 3:

  2. Almacenará el LAST_INSERT_ID()en una variable MySQL:

    INSERT ...
    SELECT LAST_INSERT_ID() INTO @mysql_variable_here;
    INSERT INTO table2 (@mysql_variable_here, ...);
    INSERT INTO table3 (@mysql_variable_here, ...);
  3. Almacenará el LAST_INSERT_ID()en una variable php (o cualquier idioma que pueda conectarse a una base de datos, de su elección):

    • INSERT ...
    • Use su lenguaje para recuperar el LAST_INSERT_ID(), ya sea ejecutando esa declaración literal en MySQL, o usando, por ejemplo, php's mysql_insert_id()que lo hace por usted
    • INSERT [use your php variable here]

ADVERTENCIA

Cualquiera sea la forma de resolver esto que elija, debe decidir qué debe suceder si la ejecución se interrumpe entre consultas (por ejemplo, su servidor de base de datos se bloquea). Si puede vivir con "algunos han terminado, otros no", no siga leyendo.

Sin embargo, si decide que "todas las consultas finalizan o ninguna finaliza; no quiero filas en algunas tablas pero no hay filas coincidentes en otras, siempre quiero que las tablas de mi base de datos sean consistentes", debe ajustar todas las declaraciones en una transacción. Por eso usé el BEGINyCOMMIT aquí.

Comenta nuevamente si necesitas más información :)

Konerak
fuente
3
¿Y qué pasa si quiero insertar en más de 2 tablas y quiero que todas las demás tengan una identificación única y el ID de usuario? ¿Es eso posible?
Jay Wit
Puede colocar el last_insert_id de la tabla original en una variable MySQL y usar esa variable en todas sus otras tablas. La sugerencia de f00 para usar un Procedimiento almacenado tiene aún más sentido si vas a manipular muchas tablas de una vez.
Konerak
3
@ Jay Wit: Actualicé la respuesta. 'Way 3' explica que, de hecho, puede poner la ID en una variable y reutilizarla en todos los comandos de MySQL, pero debe leer sobre las transacciones si desea que su base de datos sea coherente en caso de bloqueo.
Konerak
3
Claro, son declaraciones MySQL aceptadas, al igual que SELECT, UPDATE, INSERT y DELETE. Primero haga un pequeño script, y si todo funciona bien, está listo para comenzar.
Konerak
2
@Konerak ¿algún consejo sobre cómo usar estas múltiples declaraciones SQL @mysql_variablesjunto con las declaraciones preparadas?
Fernando Silva
17

bastante simple si usa procedimientos almacenados:

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

guión completo:

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
fuente
2
Lo siento, pero ¿es PHP? Nunca he visto algo así. Estoy usando PHP y MySQL, ¿algún consejo?
Jay Wit
1
parece una larga serie de declaraciones SQL
Melbourne2991
1
@ JayWit: No, todas estas son declaraciones SQL. Cree los procedimientos, luego puede usarlos cuando lo desee. Ver dev.mysql.com/doc/refman/5.7/en/create-procedure.html
Pierre-Olivier Vares
7

¿Qué pasaría si desea crear muchos de esos registros (para registrar 10 usuarios, no solo uno)? Encuentro la siguiente solución (solo 5 consultas):

Paso I: crear una tabla temporal para almacenar nuevos datos.

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

Luego, llene esta tabla con valores.

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

Aquí, en lugar de $ALL_VALcolocar una lista de valores: ('test1', 'test1', 'bio1', 'home1'), ..., ('testn', 'testn', 'bion', 'homen')

Paso II: Enviar datos a la tabla 'usuario'.

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

Aquí, "IGNORE" puede usarse, si permite que algunos usuarios ya estén dentro. Opcionalmente, puede usar ACTUALIZACIÓN similar al paso III, antes de este paso, para encontrar a los usuarios que ya están dentro (y marcarlos en la tabla tmp). Aquí suponemos que ese nombre de usuario se declara comoPRIMARY en la tabla de usuarios.

Paso III: aplique la actualización para leer todas las identificaciones de los usuarios de la tabla tmp. ESTE ES UN PASO ESENCIAL.

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

Paso IV: Cree otra tabla, usando la identificación de lectura para los usuarios

INSERT INTO profiles (userid, bio, homepage) 
SELECT id, bio, homepage FROM tmp
Zasega Anonimno
fuente
1
son 6 consultas - no te olvides de
DEJAR
3

prueba esto

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

Referencias
PHP
MYSQL

diEcho
fuente
2
Esto probablemente resultaría en un comportamiento no deseado si el servidor falla después de la creación del usuario pero antes de la creación del perfil.
vichle
@Vichle entonces, ¿cuál es la mejor manera?
diEcho
2
@vichle agrega tu maldita transacción a este código y deja de decir tonterías
tu sentido común el
3
@Konerak Estoy ejecutando scripts PHP por más de 10 años. Ninguno de ellos tiene la costumbre de detenerse entre líneas. será mejor que pienses en mejorar la calidad de tu servidor para que no se bloquee TAN a menudo. Es web, amigo. No es la Reserva Federal. No hay nada malo con un registro de usuario roto de 100 000 000 exitosos.
Su sentido común
2
En este ejemplo, puede que tengas razón. Sin embargo, mi código siempre se usa para contabilidad: la idea de eliminar dinero en A y no agregarlo en B mientras debería, es intolerable. Además, incluso si es solo web, ¿una transacción no es tan difícil / costosa? En mi humilde opinión, hacen buenos hábitos.
Konerak
3

echar un vistazo a mysql_insert_id ()

aquí la documentación: http://in.php.net/manual/en/function.mysql-insert-id.php

Daniel Kutik
fuente
1
Esa función es el demonio de la coherencia de la base de datos.
vichle
de acuerdo - usar una transacción podría ser la mejor solución
Daniel Kutik
@vichle: ¿es así? No uso php con tanta frecuencia, pero pensé que era su atajo para llamar LAST_INSERT_ID()al programador.
Konerak
1
Todas las consultas son transacciones. El valor predeterminado en la mayoría de los lenguajes de programación es confirmar automáticamente las transacciones, ya que la mayoría de las transacciones son solo una consulta. La función mysql_insert_id () está pensada para cuando ha desactivado la confirmación automática, pero a menudo es utilizada en un contexto incorrecto por personas que no están familiarizadas con el concepto de transacción.
vichle
3
@dnl puedes usar esta función Y una transacción, de acuerdo. Este comentario de la transacción es irrelevante para la pregunta.
Su sentido común
1

Esta es la forma en que lo hice para un proyecto uni, funciona bien, probablemente no sea seguro

$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
fuente
-1

Solo un comentario sobre tu dicho

Hola, intenté buscar una forma de insertar información en varias tablas en la misma consulta

¿Come todos sus platos de almuerzo mezclados con bebidas en el mismo tazón?
Supongo que no.

Igual que aquí.
Hay cosas que hacemos por separado.
2 consultas de inserción son 2 consultas de inserción. Todo está bien. No tiene nada de malo. No es necesario mezclarlo en uno.
Lo mismo para seleccionar. La consulta debe ser sensata y hacer su trabajo. Esa es la única razón. Número de consultas no lo es.

En cuanto a las transacciones, puede usarlas, pero no es TAN importante para el sitio web promedio. Si sucedió una vez al año (si alguna vez) que se rompe el registro de un usuario, podrá solucionarlo, sin duda.
Hay cientos de miles de sitios que ejecutan MySQL sin controlador de soporte de transacciones. ¿Has oído hablar de desastres terribles que rompen estos sitios? Yo tampoco.

Y mysql_insert_id () tiene que ver con las transacciones. Puede incluir en la transacción todo bien. Es solo asuntos diferentes. Alguien planteó esta pregunta de la nada.

Su sentido común
fuente
¿Por qué querrías arriesgarte a tener que arreglar cosas así cuando es fácilmente evitable?
vichle
@vichle mis tablas son de tipo myisam. En aras de la búsqueda de texto completo, legado y hábito. Estoy arriesgando, sí. Qué peligro tan terrible (en teoría) me está esperando.
Su sentido común
66
¿Por qué estás siendo tan agresivo? Solo estoy afirmando que las transacciones son el camino a seguir aquí. Si lo desea, puede hacerlo a su manera, el hecho es que las transacciones se crearon para este tipo de situación.
vichle 3/03/11
@vichle, sí, fui bastante duro, me disculpo. Fue tuyo el That function is the devil of database consistency.que hizo eso, no las transacciones. No veo nada malo en las transacciones.
Su sentido común
Disculpa aceptada. Todo lo que quise decir es que la función a menudo es mal utilizada por personas que no son conscientes de la existencia de transacciones. Estas personas también están bastante representadas en la comunidad php.
vichle
-4

Para PDO puede hacer esto

$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
fuente
Eso es exactamente lo que estoy buscando.
Subroto Biswas