Ist es möglich, eine Teilmenge einer Datenbank, die zum Reproduzieren einer Abfrage erforderlich ist, mit mysqldump zu sichern?


37

Hintergrund

Ich mag die Teilmenge bereitzustellen meine Datenbank , die eine reproduzieren erforderliche selectAbfrage. Mein Ziel ist es, meinen Computerworkflow reproduzierbar zu machen (wie in der reproduzierbaren Forschung ).

Frage

Gibt es eine Möglichkeit, diese select-Anweisung in ein Skript zu integrieren, das die abgefragten Daten in eine neue Datenbank speichert, sodass die Datenbank auf einem neuen MySQL-Server installiert werden kann und die Anweisung mit der neuen Datenbank funktioniert. Die neue Datenbank sollte keine Datensätze zusätzlich zu denen enthalten, die in der Abfrage verwendet wurden.

Update: Zur Verdeutlichung bin ich nicht an einem CSV-Dump der Abfrageergebnisse interessiert. Ich muss in der Lage sein, die Datenbank-Teilmenge zu sichern, damit sie auf einem anderen Computer installiert werden kann, und dann kann die Abfrage selbst reproduzierbar sein (und in Bezug auf dieselbe Datenmenge geändert werden).

Beispiel

Beispielsweise kann meine Analyse eine Teilmenge von Daten abfragen, für die Datensätze aus mehreren Tabellen (in diesem Beispiel 3) erforderlich sind:

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

OK, also keine zusätzlichen Aufzeichnungen. Möchten Sie nur die von der Abfrage angegebenen Spalten?
Richard

@Richard Das habe ich nicht bedacht - es wäre schön zu wissen, wie das geht.
David LeBauer

3
Dies ist eine sehr einzigartige Frage, die sich sicher einige gewundert haben und beantwortet werden mussten. +1 für die Veröffentlichung dieser Art von Frage.
RolandoMySQLDBA

Zukünftige Leser: Zusätzlich zur akzeptierten Antwort wird auf die Antwort von randomx verwiesen , die speziell die von der Abfrage benötigten Daten ausgibt .
ToolmakerSteve

Antworten:


51

mysqldump hat die Option --where , um eine WHERE-Klausel für eine bestimmte Tabelle auszuführen.

Obwohl es nicht möglich ist, eine Join-Abfrage zu mysqldumpen, können Sie bestimmte Zeilen aus jeder Tabelle exportieren, sodass jede aus jeder Tabelle abgerufene Zeile später in den Join einbezogen wird.

Für Ihre gegebene Abfrage müssten Sie dreimal mysqldump ausführen:

Zuerst mysqldump alle table3-Zeilen mit Namen in ('fee', 'fi', 'fo', 'fum'):

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

Als nächstes mysqldump alle table2-Zeilen, die übereinstimmende table3_id-Werte aus dem ersten mysqldump haben:

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

Dann mysqldump alle table1-Zeilen, die übereinstimmende table1_id-Werte aus dem zweiten mysqldump haben:

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

Hinweis: Da für den zweiten und dritten mysqldump mehr als eine Tabelle erforderlich ist, muss --lock-all-tables verwendet werden .

Erstellen Sie Ihre neue Datenbank:

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

Laden Sie zum Schluss die drei mysqldumps in eine andere Datenbank und versuchen Sie dort den Join in der neuen Datenbank.

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

Führen Sie im MySQL-Client Ihre Join-Abfrage aus

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

Versuche es !!!

WARNUNG: Wenn nicht korrekt indiziert, kann der zweite und dritte mysqldump ewig dauern !!!

Indizieren Sie für alle Fälle die folgenden Spalten:

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

Ich gehe davon aus, dass id der Primärschlüssel von table3 ist.


1
danke für das ausführliche beispiel! Ich habe die --whereKlausel in der Dokumentation verpasst . Lassen Sie Sie wissen, wie das funktioniert, nachdem ich die Gelegenheit habe, es auszuprobieren.
David LeBauer

1
+1 Das gefällt mir besser als die --tables-Methode für dieses Problem. Im Allgemeinen würde ich --tables verwenden, aber --where ist eine sehr gute Option.
Richard

Wenn Sie eine einzelne Tabelle mysqldumpen, wird --lock-all-tables nicht verwendet. Da es sich bei der where-Klausel um andere als die zu sichernden Tabellen handelt, müssen Sie mysqldump --lock-all-tables mitteilen. Die Option --lock-all-tables ist aktiv, um eine oder mehrere Datenbanken zu sichern, NICHT FÜR EINE EINZELNE TABELLE. Ich habe versucht, den zweiten und dritten mysqldump durchzuführen, aber es hat sich darüber beschwert. Nachdem ich manuell --lock-all-tables ausgegeben hatte, verschwand der Fehler und der mysqldump war erfolgreich. Beachten Sie auch, dass der erste mysqldump in meiner Antwort keine --lock-all-tables enthält.
RolandoMySQLDBA

@ Rolando danke für deine Hilfe. Das hat perfekt funktioniert
David LeBauer

@Rolando sorry, ich habe nicht bemerkt, dass du meinen Kommentar / meine Frage beantwortet hast, bevor ich sie gelöscht habe. Ich bekam den gleichen Fehler. Nach dem erneuten Lesen des Handbuchs sehe ich, dass --lock-tables nur Tabellen sperrt , die gesichert werden . Ich war verwirrt, weil --lock-all-tables alle Tabellen in allen Datenbanken sperrt, was nicht erforderlich ist, wenn nur eine einzige Datenbank verwendet wird.
David LeBauer

7

Ich würde in Betracht ziehen , ein 'outfile' als Teil Ihres SELECT anstelle von mysqldump zu verwenden, um dieses Problem zu lösen. Sie können eine beliebige SELECT-Anweisung erstellen und dann am Ende "INTO OUTFILE '/path/to/outfile.csv' ..." mit der entsprechenden Konfiguration für die CSV-Ausgabe anhängen. Dann können Sie einfach die Syntax ' LOAD DATA INFILE ...' verwenden, um die Daten in Ihren neuen Schema-Speicherort zu laden.

Zum Beispiel mit Ihrem 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'
; 

Denken Sie daran, dass Sie auf der Zielfestplattenpartition genügend verfügbaren Speicherplatz benötigen.


Ich mag das für den Dataload. Sie müssen das Schema noch in die neue Datenbank übertragen, aber dies lässt sich mit einigen anderen Tricks problemlos erreichen.
Richard

Ich mag das auch, weil manche Leute die Basistabellen nicht wollen, nur das verbundene Ergebnis als einzelne CSV importiert. +1 !!!
RolandoMySQLDBA

@randy Vielen Dank für Ihre Antwort, aber ich glaube nicht, dass dies mein Problem löst, da ich nicht an einem CSV-Dump mit Abfrageergebnissen interessiert bin. Ich muss in der Lage sein, die Datenbank-Teilmenge zu sichern, damit sie auf einem anderen Computer installiert werden kann, und dann kann die Abfrage selbst reproduzierbar sein (und in Bezug auf dieselbe Datenmenge geändert werden). Ziel ist ein rechnerischer Workflow, der reproduzierbare Recherchen unterstützt .
David LeBauer

Für zukünftige Leser zu Davids Kommentar: Wie Richard bereits erwähnte, müssen Sie das Schema der betroffenen Tabellen separat exportieren . Diese Schemata können einfach in eine neue Datenbank geladen werden. Dann laden Sie, wie Randomx sagte, Load Data Infilediese .csv- Datei in diese neue Datenbank. Jetzt kann die Abfrage ausgeführt werden.
ToolmakerSteve

Ich habe gerade festgestellt, dass die Einschränkung dieser Technik darin besteht, dass sich die Abfrageausgabe nicht in derselben Organisation wie die Originaltabellen befindet. Während mir dieser Ansatz immer noch gefällt, wird die ursprüngliche Tabellenstruktur neu erstellt: Führen Sie separate Abfragen aus, eine pro Tabelle, um die für diese Tabelle erforderlichen Daten zu exportieren.
ToolmakerSteve

6

Das mysqldump-Dienstprogramm verfügt über die Option --tables , mit der Sie angeben können, welche Tabellen gesichert werden sollen . Hier können Sie die Liste der Tabellen angeben.

Ich kenne keinen einfacheren (automatisierten) Weg.


Vielen Dank für Ihre Hilfe, aber ich möchte nur die ausgewählten Zeilen jeder Tabelle exportieren, nicht nur die erforderlichen Tabellen. Ich könnte einen Skript, das den Dump mit folgt delete from table1 where id not in (.....);, wenn dies der einfachste Weg ist, solange das Skript werden kann automatisiert, es ist nicht notwendig , dass das spezifische Werkzeug vorhanden ist .
David LeBauer

Sie verdienen eine +1, weil --tables einfacher wäre und das Löschen der nicht benötigten Daten auf dem neuen Server mehr Arbeit bedeuten würde, insbesondere wenn die beteiligten Tabellen jeweils mehr als 1 GB groß sind. Die meisten Menschen würden sich auf diese Weise wohler fühlen, weil es nur in Bezug auf die Schritte Sinn macht. Meine Antwort erfordert nur ein wenig Planung und ein wenig mehr Risiko.
RolandoMySQLDBA


2

Haben Sie die Anführungszeichenfunktion in MySQL ausprobiert ?

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

Speichern Sie das obige als query.sql

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

1

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

In der Befehlszeile:

mysqldump mydb table4 |gzip > table4.sql.gz

Richten Sie auf Ihrem Zielserver ~ / .my.cnf ein

[client]
default-character-set=utf8

Auf Zielserver importieren

zcat table4.sql.gz | mysql

1

Ich habe ein kleines Skript für ein ähnliches Problem geschrieben, hier ist es: 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";
?>

dh Sie haben diese Abfrage :

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

Du hast diesen Dump :

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");
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.