Возможно ли mysqldump подмножество базы данных, необходимой для воспроизведения запроса?


37

Задний план

Я хотел бы предоставить подмножество моей базы данных, необходимое для воспроизведения selectзапроса. Моя цель - сделать мой вычислительный рабочий процесс воспроизводимым (как в воспроизводимых исследованиях ).

Вопрос

Есть ли способ, которым я могу включить этот оператор выбора в сценарий, который сбрасывает запрашиваемые данные в новую базу данных, так что база данных может быть установлена ​​на новом сервере MySQL, и оператор будет работать с новой базой данных. Новая база данных не должна содержать записей в дополнение к тем, которые были использованы в запросе.

Обновление: для пояснения, я не заинтересован в дампе CSV результатов запроса. Что мне нужно сделать, так это сбросить подмножество базы данных, чтобы его можно было установить на другом компьютере, а затем сам запрос можно было бы воспроизвести (и изменить по отношению к тому же набору данных).

пример

Например, мой анализ может запрашивать подмножество данных, для которого требуются записи из нескольких (в этом примере 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'); 

ОК, поэтому никаких дополнительных записей. Вы хотите только столбцы, указанные в запросе?
Ричард

@Richard Я не учел это - было бы неплохо знать, как это сделать.
Дэвид Лебауэр

3
Это очень уникальный вопрос, который, я уверен, некоторые задавались вопросом, и на него нужно было ответить. +1 за доведение этого типа вопроса до общественности.
RolandoMySQLDBA

Будущие читатели: в дополнение к принятому ответу см . Ответ randomx , в котором специально выводятся данные, необходимые для запроса.
ToolmakerSteve

Ответы:


52

mysqldump имеет опцию --where, чтобы выполнить предложение WHERE для данной таблицы.

Несмотря на то, что mysqldump невозможно выполнить запрос на соединение, вы можете экспортировать определенные строки из каждой таблицы, чтобы каждая строка, извлеченная из каждой таблицы, была позже включена в объединение.

Для вашего данного запроса вам нужно выполнить mysqldump три раза:

Во-первых, mysqldump все строки таблицы 3 с именем в ('fee', 'fi', 'fo', 'fum'):

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

Затем mysqldump все строки table2, которые имеют совпадающие значения table3_id из первого 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

Затем mysqldump все строки таблицы table1, которые имеют совпадающие значения table1_id из второго 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

Примечание: поскольку для второго и третьего mysqldumps требуется использование более одной таблицы, необходимо использовать --lock-all-tables .

Создайте свою новую базу данных:

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

Наконец, загрузите три mysqldumps в другую базу данных и попытайтесь соединиться там в новой базе данных.

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

В клиенте mysql запустите ваш запрос на присоединение

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

Попробуйте!

ВНИМАНИЕ: Если не проиндексированы правильно, второй и третий mysqldumps может занять вечно !!!

На всякий случай проиндексируйте следующие столбцы:

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

Я предполагаю, что id является первичным ключом таблицы 3.


1
спасибо за подробный пример! Я пропустил --whereпункт в документации; дам вам знать, как это работает после того, как я получу шанс попробовать.
Дэвид Лебауэр

1
+1 Мне нравится этот метод лучше, чем метод --tables. В общем, я бы в конечном итоге использовал --tables, но --where - очень хороший вариант.
Ричард

Когда вы запускаете mysqldump для одной таблицы, --lock-all-tables не используется. Поскольку в предложении where используются таблицы, отличные от сбрасываемых таблиц, вы должны указать mysqldump --lock-all-tables. Опция --lock-all-tables активна для выгрузки одной или нескольких баз данных, НЕ ДЛЯ ОДИНОЧНОЙ ТАБЛИЦЫ. Я пытался выполнить 2-й и 3-й mysqldumps, но он жаловался на это. Как только я вручную выполнил --lock-all-tables, ошибка исчезла, и mysqldump был успешным. Также, пожалуйста, обратите внимание, что первый mysqldump в моем ответе не имеет --lock-all-tables.
RolandoMySQLDBA

@Rolando спасибо за вашу помощь. Это сработало отлично
Дэвид Лебауэр

@Rolando извините, я не заметил, что вы ответили на мой комментарий / вопрос, прежде чем я удалил его. Я получаю ту же ошибку. Перечитав руководство, я вижу, что --lock-tables блокирует только удаляемые таблицы . Я был сбит с толку, потому что --lock-all-tables блокирует все таблицы во всех базах данных, что необязательно при использовании только одной базы данных.
Дэвид Лебауэр

7

Я решил бы использовать 'outfile' как часть вашего SELECT вместо mysqldump для решения этой проблемы. Вы можете создать любой оператор SELECT, который вам нужен, а затем добавить в конце «INTO OUTFILE» /path/to/outfile.csv '... »с соответствующей конфигурацией для вывода в стиле CSV. Тогда вы можете просто использовать что-то вроде синтаксиса « LOAD DATA INFILE ...», чтобы загрузить данные в новое местоположение схемы.

Например, используя ваш 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'
; 

Имейте в виду, что вам понадобится достаточно свободного места на целевом разделе диска.


Мне нравится это для dataload. Вам все еще нужно перенести схему в новую базу данных, но это легко сделать, используя некоторые другие приемы.
Ричард

Мне это тоже нравится, потому что некоторые люди могут не захотеть использовать базовые таблицы, просто объединенный результат как один импортированный CSV. +1 !!!
RolandoMySQLDBA

@randy Спасибо за ваш ответ, но я не думаю, что это решает мою проблему, потому что я не заинтересован в дампе результатов запроса csv. Что мне нужно сделать, так это сбросить подмножество базы данных, чтобы его можно было установить на другом компьютере, а затем сам запрос можно было бы воспроизвести (и изменить по отношению к тому же набору данных). Целью является вычислительный рабочий процесс, который поддерживает воспроизводимые исследования .
Дэвид Лебауэр

Для будущих читателей комментарий Дэвида: как упоминал Ричард, вам нужно отдельно экспортировать схему задействованных таблиц. Эти схемы могут быть легко загружены в новую базу данных. Затем, как сказал randomx, вы используете Load Data Infileдля загрузки этот .csv в эту новую базу данных. Теперь запрос может быть выполнен.
ToolmakerSteve

Я только что понял, что ограничением этого метода является то, что вывод запроса не в той же организации, что и исходные таблицы. Хотя мне все еще нравится этот подход, чтобы воссоздать исходную структуру таблицы: выполните отдельные запросы, по одному на таблицу, чтобы экспортировать данные, необходимые для этой таблицы.
ToolmakerSteve

6

Утилита mysqldump имеет опцию --tables, которая позволяет вам указать, какие таблицы выгружать. Позволяет указать список таблиц.

Я не знаю более простого (автоматизированного) способа.


спасибо за вашу помощь, но я хочу экспортировать только выбранные строки каждой таблицы, а не только необходимые таблицы. У меня мог бы быть сценарий, который следует за дампом delete from table1 where id not in (.....);, если это самый простой способ, поскольку сценарий можно автоматизировать, нет необходимости в том, чтобы существовал конкретный инструмент.
Дэвид Лебауэр

Вы заслуживаете +1, потому что --tables будет проще, а удаление ненужных данных будет просто дополнительной работой на новом сервере, особенно если размер используемых таблиц превышает 1 ГБ каждая. Большинство людей чувствовали бы больший уровень комфорта, делая это таким образом, потому что это просто имеет смысл с точки зрения шагов. Мой ответ просто требует немного планирования и немного больше риска.
RolandoMySQLDBA


2

Вы пробовали функцию цитаты в 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'); 

сохранить выше, как query.sql

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

1

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

В командной строке:

mysqldump mydb table4 |gzip > table4.sql.gz

На целевом сервере настройте ~ / .my.cnf

[client]
default-character-set=utf8

Импорт на целевой сервер

zcat table4.sql.gz | mysql

1

я написал небольшой скрипт для аналогичной проблемы, вот он: 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";
?>

т.е. у вас есть этот запрос :

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

Вы получили эту свалку :

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");
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.