MySQL Inserir em várias tabelas? (Normalização de banco de dados?)

136

Tentei pesquisar uma maneira de insertobter informações em várias tabelas na mesma consulta, mas descobri que é impossível? Então, eu quero insertisso simplesmente usando várias consultas, ou seja;

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

Mas como posso dar o incremento automático iddo userspara o "manual" useridda profiletabela?

Jay Wit
fonte
8
Você quer aprender sobre transações.
vichle
3
possível duplicata de sql - inserção em várias tabelas em uma consulta
Expiação Limitada

Respostas:

241

Não, você não pode inserir em várias tabelas em um comando MySQL. No entanto, você pode usar transações.

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;

Dê uma olhada LAST_INSERT_ID()para reutilizar valores de incremento automático.

Edit: você disse " Depois de todo esse tempo tentando descobrir, ele ainda não funciona. Não posso simplesmente colocar o ID apenas gerado em um $ var e colocar esse $ var em todos os comandos do MySQL? "

Deixe-me elaborar: existem três maneiras possíveis aqui:

  1. No código que você vê acima. Isso faz tudo no MySQL, e a LAST_INSERT_ID()segunda instrução será automaticamente o valor da coluna de incremento automático que foi inserida na primeira instrução.

    Infelizmente, quando a segunda instrução inserir linhas em uma tabela com uma coluna de incremento automático, LAST_INSERT_ID()ela será atualizada para a tabela 2, e não para a tabela 1. Se você ainda precisar da tabela 1 posteriormente, teremos que armazená-la em uma variável Isso nos leva às maneiras 2 e 3:

  2. Estocará a LAST_INSERT_ID()variável MySQL:

    INSERT ...
    SELECT LAST_INSERT_ID() INTO @mysql_variable_here;
    INSERT INTO table2 (@mysql_variable_here, ...);
    INSERT INTO table3 (@mysql_variable_here, ...);
  3. Vai estocar o LAST_INSERT_ID()em uma variável php (ou qualquer linguagem que pode se conectar a um banco de dados, de sua escolha):

    • INSERT ...
    • Use sua linguagem para recuperar LAST_INSERT_ID(), executando a instrução literal no MySQL ou usando, por exemplo, php's mysql_insert_id()que fazem isso por você
    • INSERT [use your php variable here]

AVISO

Qualquer que seja a maneira de resolver isso, você deve decidir o que deve acontecer caso a execução seja interrompida entre as consultas (por exemplo, seu servidor de banco de dados trava). Se você pode viver com "alguns terminaram, outros não", não continue lendo.

Se, no entanto, você decidir "todas as consultas terminam ou nenhuma termina - eu não quero linhas em algumas tabelas, mas nenhuma linha correspondente em outras, sempre quero que minhas tabelas de banco de dados sejam consistentes", você precisa agrupar todas as instruções em uma transação. Por isso usei o BEGINe COMMITaqui.

Comente novamente se precisar de mais informações :)

Konerak
fonte
3
E se eu quiser inserir em mais de 2 tabelas e desejar que todas as outras tenham um ID exclusivo e o ID do usuário? Isso é possível?
Jay Wit
Você pode colocar o last_insert_id da tabela original em uma variável do MySQL e usar essa variável em todas as suas outras tabelas. A sugestão do f00 de usar um Procedimento armazenado faz ainda mais sentido se você for manipular muitas tabelas de uma vez.
precisa saber é o seguinte
3
@ Jay Wit: Eu atualizei a resposta. 'Way 3' explica que você realmente pode colocar o ID em uma variável e reutilizá-lo em todos os comandos do MySQL, mas você deve ler sobre transações se quiser que seu banco de dados seja consistente em caso de falha.
precisa saber é o seguinte
3
Claro, são aceitas instruções MySQL, assim como SELECT, UPDATE, INSERT e DELETE. Faça primeiro um pequeno teste e, se tudo funcionar bem, você estará pronto.
precisa saber é o seguinte
2
@Konerak alguma dica sobre como usar essas múltiplas instruções SQL com as instruções @mysql_variablesem conjunto com as preparadas?
Fernando Silva
17

bastante simples se você usar procedimentos armazenados:

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

script 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
fonte
2
Desculpe, mas isso é PHP? Eu nunca vi nada assim. Estou usando PHP e MySQL, alguma dica?
Jay Wit #
1
parece um longo conjunto de instruções sql
Melbourne2991
1
@ JayWit: Não, estas são todas as instruções SQL. Crie os procedimentos e poderá usar quando quiser. Veja dev.mysql.com/doc/refman/5.7/en/create-procedure.html
Pierre-Olivier Vares
7

O que aconteceria se você quisesse criar muitos desses registros (para registrar 10 usuários, não apenas um)? Eu encontro a seguinte solução (apenas 5 consultas):

Etapa I: Crie uma tabela temporária para armazenar novos dados.

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

Em seguida, preencha esta tabela com valores.

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

Aqui, em vez de $ALL_VALcolocar uma lista de valores: ('test1', 'test1', 'bio1', 'home1'), ..., ('testn', 'testn', 'bion', 'homen')

Etapa II: envie dados para a tabela 'usuário'.

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

Aqui, "IGNORE" pode ser usado, se você permitir que alguns usuários já estejam dentro. Opcionalmente, você pode usar UPDATE semelhante à etapa III, antes desta etapa, para descobrir quem os usuários já estão dentro (e marcá-los na tabela tmp). Aqui supomos que esse nome de usuário é declarado como PRIMARYna tabela de usuários.

Etapa III: aplique a atualização para ler todos os IDs de usuários da tabela tmp. ESTE É UM PASSO ESSENCIAL.

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

Etapa IV: criar outra tabela, usando o ID de leitura para usuários

INSERT INTO profiles (userid, bio, homepage) 
SELECT id, bio, homepage FROM tmp
Zasega Anonimno
fonte
1
é de 6 consultas - não se esqueça de queda temporária
Zigulik
3

tente isso

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

Referências
PHP
MYSQL

diEcho
fonte
2
Isso provavelmente resultaria em algum comportamento indesejado se o servidor travasse após a criação do usuário, mas antes da criação do perfil.
vichle
@ Vichle então qual é a melhor maneira?
diEcho 3/03/11
2
@vichle adicionar sua transação danado para este código e parar esse absurdo já
Seu senso comum
3
@Konerak Estou executando scripts PHP há mais de 10 anos. Nenhum deles tem o hábito de parar nas entrelinhas. é melhor pensar em melhorar a qualidade do servidor para não travar com tanta frequência. É web, cara. não é o Federal Reserve. Nada de errado com um registro de usuário quebrado de 100 000 000 de sucesso.
Seu senso comum
2
Neste exemplo, você pode estar certo. Meu código é sempre usado para contabilidade - o pensamento de remover dinheiro em A e não adicioná-lo em B enquanto deveria, é intolerável. Além disso, mesmo que seja apenas web, uma transação não é tão difícil / cara? IMHO, eles fazem bons hábitos.
precisa saber é o seguinte
3

veja mysql_insert_id ()

aqui a documentação: http://in.php.net/manual/en/function.mysql-insert-id.php

Daniel Kutik
fonte
1
Essa função é o diabo da consistência do banco de dados.
vichle
concordou - usando uma transação pode ser a melhor solução
Daniel Kutik
@ Vichle: é isso mesmo? Não uso php com tanta frequência, mas achei que era o atalho deles para chamar LAST_INSERT_ID()o programador?
precisa saber é o seguinte
1
Todas as consultas são transações. O padrão na maioria das linguagens de programação é confirmar transações automaticamente, pois a maioria das transações é apenas uma consulta. A função mysql_insert_id () destina-se a quando você desativa o commit automático, mas é frequentemente usada no contexto errado por pessoas não familiarizadas com o conceito de transação.
vichle
3
@ DNL você pode usar esta função e uma transação tudo bem. essa observação da transação é irrelevante para a questão.
Seu senso comum
1

Esta é a maneira que eu fiz isso para um projeto uni, funciona bem, provavelmente não é 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
fonte
-1

Apenas um comentário sobre o seu ditado

Olá, tentei pesquisar uma maneira de inserir informações em várias tabelas na mesma consulta

Você come todos os seus pratos de almoço misturados com bebidas na mesma tigela?
Eu suponho - não.

O mesmo aqui.
Há coisas que fazemos separadamente.
2 consultas de inserção são 2 consultas de inserção. Está tudo bem. Nada de errado com isso. Não há necessidade de amassá-lo em um.
Mesmo para selecionar. A consulta deve ser sensata e fazer seu trabalho. Essa é a única razão. O número de consultas não é.

Quanto às transações - você pode usá-las, mas isso não é grande coisa para o site médio. Se acontecer uma vez por ano (se houver) que um registro de usuário está sendo quebrado, você poderá corrigir, sem dúvida.
existem centenas de milhares de sites executando o mysql sem nenhum driver de suporte a transações. Você já ouviu falar de desastres terríveis desmembrando esses sites? Nem eu.

E mysql_insert_id () tem a ver com transações. você pode incluir a transação corretamente. são apenas assuntos diferentes. Alguém levantou essa questão do nada.

Seu senso comum
fonte
Por que você gostaria de arriscar ter que consertar coisas assim quando é facilmente evitável?
vichle
@vichle minhas tabelas são do tipo myisam. Por uma questão de pesquisa de texto completo, legado e hábito. Estou arriscando, sim. Que terrível (em teoria) perigo está me esperando.
Seu senso comum
6
Por que você está sendo tão agressivo? Estou apenas afirmando que as transações são o caminho a seguir aqui. Você faz do seu jeito, se desejar, o fato ainda é que as transações foram criadas para esse tipo de situação.
vichle
@ Vichle sim, eu era bastante duro, peço desculpas. Foi seu That function is the devil of database consistency.que fez isso, não transações. Não vejo nada de ruim nas transações.
Seu senso comum
Desculpas aceitas. Tudo o que eu quis dizer foi que a função geralmente é mal utilizada por pessoas que não estão cientes da existência de transações. Essas pessoas também são super-representadas na comunidade php.
vichle
-4

Para DOP Você pode fazer isso

$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
fonte
É exatamente isso que estou procurando.
Subroto Biswas