É possível mysqldump um subconjunto de um banco de dados necessário para reproduzir uma consulta?

37

fundo

Gostaria de fornecer o subconjunto do meu banco de dados necessário para reproduzir uma selectconsulta. Meu objetivo é tornar reprodutível meu fluxo de trabalho computacional (como na pesquisa reproduzível ).

Questão

Existe uma maneira de incorporar essa instrução select em um script que despeja os dados consultados em um novo banco de dados, de modo que o banco de dados possa ser instalado em um novo servidor mysql e a instrução funcione com o novo banco de dados. O novo banco de dados não deve conter registros além daqueles que foram usados ​​na consulta.

Atualização: para esclarecimento, não estou interessado em um dump csv dos resultados da consulta. O que eu preciso fazer é despejar o subconjunto do banco de dados para que ele possa ser instalado em outra máquina e, em seguida, a própria consulta pode ser reproduzível (e modificável em relação ao mesmo conjunto de dados).

Exemplo

Por exemplo, minha análise pode consultar um subconjunto de dados que requer registros de várias tabelas (neste exemplo 3):

select table1.id, table1.level, table2.name, table2.level 
       from table1 join table2 on table1.id = table2.table1_id 
       join table3 on table3.id = table2.table3_id
       where table3.name in ('fee', 'fi', 'fo', 'fum'); 
David LeBauer
fonte
OK, então não há registros adicionais. Você deseja apenas as colunas especificadas pela consulta?
Richard
@ Richard Eu não tinha considerado isso - seria bom saber como fazer isso.
David LeBauer
3
Esta é uma pergunta muito única que tenho certeza de que alguns se perguntaram e precisavam ser respondidos. +1 por tornar público esse tipo de pergunta.
RolandoMySQLDBA
Leitores futuros: além da resposta aceita, consulte a resposta da randomx , que despeja especificamente os dados necessários à consulta.
ToolmakerSteve

Respostas:

52

O mysqldump possui a opção --where para executar uma cláusula WHERE para uma determinada tabela.

Embora não seja possível mysqldump uma consulta de junção, você pode exportar linhas específicas de cada tabela para que todas as linhas buscadas em cada tabela sejam envolvidas na junção posteriormente.

Para sua consulta, você precisaria mysqldump três vezes:

Primeiro, o mysqldump todas as linhas da table3 com o nome in ('fee', 'fi', 'fo', 'fum'):

mysqldump -u... -p... --where="name in ('fee','fi','fo','fum')" mydb table3 > table3.sql

Em seguida, o mysqldump todas as linhas da tabela2 que possuem valores correspondentes de table3_id do primeiro mysqldump:

mysqldump -u... -p... --lock-all-tables --where="table3_id in (select id from table3 where name in ('fee','fi','fo','fum'))" mydb table2 > table2.sql

Então, mysqldump todas as linhas da tabela1 que possuem valores correspondentes table1_id do segundo mysqldump:

mysqldump -u... -p... --lock-all-tables --where="id in (select table1_id from table2 where table3_id in (select id from table3 where name in ('fee','fi','fo','fum')))" mydb table1 > table1.sql

Nota: Como o segundo e o terceiro mysqldumps requerem o uso de mais de uma tabela, --lock-all-tables deve ser usado .

Crie seu novo banco de dados:

mysqladmin -u... -p... mysqladmin create newdb

Finalmente, carregue os três mysqldumps em outro banco de dados e tente a junção no novo banco de dados.

mysql -u... -p... -D newdb < table1.sql
mysql -u... -p... -D newdb < table2.sql
mysql -u... -p... -D newdb < table3.sql

No cliente mysql, execute sua consulta de junção

mysql> use newdb
mysql> select table1.id, table1.level, table2.name, table2.level 
       from table1 join table2 on table1.id = table2.table1_id 
       join table3 on table3.id = table2.table3_id
       where table3.name in ('fee', 'fi', 'fo', 'fum'); 

De uma chance !!!

AVISO: Se não estiver indexado corretamente, o segundo e o terceiro mysqldumps podem levar uma eternidade !!!

Apenas no caso, indexe as seguintes colunas:

ALTER TABLE table2 ADD INDEX (table1_id);
ALTER TABLE table2 ADD INDEX (table3_id);
ALTER TABLE table3 ADD INDEX (name,id);

Vou assumir que id é a chave primária da tabela3.

RolandoMySQLDBA
fonte
1
obrigado pelo exemplo detalhado! Eu perdi a --wherecláusula na documentação; informará como isso funciona depois que eu tiver a chance de experimentar.
David LeBauer
1
+1 Gosto disso melhor do que o método --tables para esse problema. Em geral, eu acabaria usando --tables, mas o --where é uma opção muito boa.
Richard
Quando você mysqldump uma única tabela, --lock-all-tables não é usada. Como a cláusula where envolveu tabelas diferentes da que está sendo despejada, você deve informar ao mysqldump --lock-all-tables. A opção --lock-all-tables está ativa para despejar um ou mais bancos de dados, NÃO PARA UMA ÚNICA TABELA. Eu tentei executar o 2º e o 3º mysqldumps, mas ele reclamou disso. Depois de emitir manualmente --lock-all-tables, o erro desapareceu e o mysqldump foi bem-sucedido. Além disso, observe que o primeiro mysqldump na minha resposta não possui --lock-all-tables.
RolandoMySQLDBA 02/09
@Rolando obrigado pela sua ajuda. Isso funcionou perfeitamente
David LeBauer
@Rolando desculpe, eu não percebi que você tinha respondido meu comentário / pergunta antes de eu excluí-lo. Eu estava recebendo o mesmo erro. Depois de reler o manual, vejo --lock-tables apenas bloqueia as tabelas que estão sendo despejadas. Fiquei confuso porque --lock-all-tables bloqueia todas as tabelas em todos os bancos de dados, o que não é necessário ao usar apenas um único banco de dados.
David LeBauer 02/09/11
7

Eu consideraria usar um 'outfile' como parte do seu SELECT em vez do mysqldump para resolver esse problema. Você pode produzir qualquer instrução SELECT desejada e anexar "INTO OUTFILE '/path/to/outfile.csv' ..." no final com a configuração apropriada para a saída no estilo CSV. Em seguida, você pode simplesmente usar algo como a sintaxe ' LOAD DATA INFILE ...' para carregar os dados em seu novo local do esquema.

Por exemplo, usando seu SQL:

select table1.id, table1.level, table2.name, table2.level 
       from table1 join table2 on table1.id = table2.table1_id 
       join table3 on table3.id = table2.table3_id
       where table3.name in ('fee', 'fi', 'fo', 'fum')
INTO OUTFILE '/tmp/fee-fi-fo-fum.csv'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
; 

Lembre-se de que você precisará de espaço de armazenamento disponível suficiente na partição do disco de destino.

randomx
fonte
Eu gosto disso para o dataload. Você ainda precisará transferir o esquema para o novo banco de dados, mas isso é facilmente alcançado usando outros truques.
Richard
Também gosto disso porque algumas pessoas podem não querer as tabelas base, apenas o resultado associado como um único CSV importado. +1 !!!
RolandoMySQLDBA
@ Randy Obrigado por sua resposta, mas não acho que isso resolva meu problema, porque não estou interessado em um dump csv dos resultados da consulta. O que eu preciso fazer é despejar o subconjunto do banco de dados para que ele possa ser instalado em outra máquina e, em seguida, a própria consulta pode ser reproduzível (e modificável em relação ao mesmo conjunto de dados). O objetivo é um fluxo de trabalho computacional que ofereça suporte a pesquisas reproduzíveis .
David LeBauer
Para futuros leitores, comente o comentário de David: como Richard mencionou, é necessário exportar separadamente o esquema das tabelas envolvidas. Esses esquemas podem ser facilmente carregados em um novo banco de dados. Então, como randomx disse, você usa Load Data Infilepara carregar esse .csv nesse novo banco de dados. Agora, a consulta pode ser executada.
ToolmakerSteve
Acabei de perceber que a limitação dessa técnica é que a saída da consulta não está na mesma organização que as tabelas originais. Embora eu ainda goste dessa abordagem, para recriar a estrutura da tabela original: Execute consultas separadas, uma por tabela, para exportar os dados necessários para essa tabela.
ToolmakerSteve
6

O utilitário mysqldump possui uma opção --tables que permite especificar quais tabelas despejar. Permite especificar a lista de tabelas.

Não conheço nenhuma maneira mais fácil (automatizada).

Richard
fonte
obrigado por sua ajuda, mas só quero exportar as linhas selecionadas de cada tabela, não apenas as tabelas necessárias. Eu poderia ter um script que segue o despejo com delete from table1 where id not in (.....);, se essa for a maneira mais fácil, desde que o script possa ser automatizado, não é necessário que a ferramenta específica exista.
David LeBauer
Você merece um +1 porque --tables seria mais simples e soltar os dados desnecessários seria apenas mais trabalho no novo servidor, especialmente se as tabelas envolvidas tiverem mais de 1 GB cada. A maioria das pessoas sentiria um nível maior de conforto ao fazê-lo dessa maneira, porque apenas faz sentido em termos das etapas. Minha resposta exige apenas um pouco de planejamento e um pouco mais de risco.
RolandoMySQLDBA
3

O que foi útil para mim foi algo como:

mysqldump -u db_user -p db_name table_name --no_create_info \
--lock-all-tables --where 'id in (SELECT tn.id FROM table_name AS tn \
JOIN related_table AS rt ON tn.related_table_id = rt.id \
WHERE rt.some_field = 1)' > data.sql

De http://krosinski.blogspot.com/2012/12/using-table-join-with-mysqldump.html

Ryan
fonte
2

Você já tentou a função quote no mysql?

SELECT CONCAT("insert into table4(id,level,name,levelt2) VALUES(",   quote(table1.id),   ",",    quote(table1.level),   ",",    quote(table2.name),   ",",    quote(table2.level),    ");") as q
       from table1 join table2 on table1.id = table2.table1_id 
       join table3 on table3.id = table2.table3_id
       where table3.name in ('fee', 'fi', 'fo', 'fum'); 

salve o acima, como query.sql

cat query.sql|mysql --skip-column-names --raw > table4.sql
velcrow
fonte
1

No MySQL:

SHOW CREATE TABLE table1; -- use these two create statements
SHOW CREATE TABLE table2; -- to design table4's create statement
CREATE TABLE table4( .... );
INSERT INTO table4(id,level,name,levelt2)
SELECT table1.id, table1.level, table2.name, table2.level 
   from table1 join table2 on table1.id = table2.table1_id 
   join table3 on table3.id = table2.table3_id
   where table3.name in ('fee', 'fi', 'fo', 'fum'); 

Na linha de comando:

mysqldump mydb table4 |gzip > table4.sql.gz

No servidor de destino, configure ~ / .my.cnf

[client]
default-character-set=utf8

Importar no servidor de destino

zcat table4.sql.gz | mysql
velcrow
fonte
1

eu escrevi um pequeno script para um problema semelhante, aqui está: https://github.com/digitalist/mysql_slice

include ('queryDumper.php');


$exampleQuery="select * from information_schema.columns c1 
left join information_schema.columns c2 on 1=1 limit 1";

//define credentials
$exampleMysqli = new mysqli($host, $user, $password, $database);
$exampleResult=$exampleMysqli->query($exampleQuery);

//if  mysqlnd (native driver installed), otherwise use wrapper
$exampleData=fetchAll($exampleResult);
$exampleMeta=$exampleResult->fetch_fields();

/*
 * field content removal options
 * column name => function name in queryDumper.php, namespace QueryDumperHelpers
 * 
 * */

$forbiddenFields=array(
'password'=>'replacePassword', //change password -> md5("password")
'login'=>'replaceLogin', //change login vasya@mail.ru -> vasya@example.com
'comment'=>'sanitizeComment' //lorem ipsum or 
);


//get tables dump
$dump=(\queryDumper\dump($exampleData, $exampleMeta, $forbiddenFields));



$dropDatabase=true; //default false
$dropTable=true; //default false

$dbAndTablesCreationDump=\QueryDumperDatabaseAndTables\dump($exampleMysqli,$exampleMeta, $dropDatabase, $dropTable);

$databases=$dbAndTablesCreationDump['databases'];
$tables=$dbAndTablesCreationDump['tables'];
$eol=";\n\n";
echo implode($eol, $databases)."\n";
echo implode($eol, $tables).";\n";
echo "\n";

//consider using array_unique($dump) before imploding
echo implode("\n\n", $dump);
echo "\n";
?>

ou seja, você tem esta consulta :

SELECT * FROM employees.employees e1 
LEFT JOIN employees.employees e2 ON 1=1 
LIMIT 1; 

você recebeu esse despejo :

DROP DATABASE `employees`;

CREATE DATABASE `employees`;
CREATE TABLE `employees` ( /* creation code */ ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT IGNORE INTO `employees`.`employees` VALUES ("10001","1953-09-02","Georgi","Facello","M","1986-06-26");

INSERT IGNORE INTO `employees`.`employees` VALUES ("10001","1953-09-02","Georgi","Facello","M","1986-06-26");
digitalista
fonte