2013-06-04 3 views
9

Angenommen, ich besitze Tabelle A und Tabelle B. Tabelle B verweist auf Tabelle A. Ich möchte eine Reihe von Zeilen in Tabelle A und Tabelle B tiefkopieren. Ich möchte, dass alle neuen Tabelle B-Zeilen auf die neuen Zeilen von Tabelle A verweisen.Wie kann ich eine Datenmenge tief kopieren und FK-Referenzen so ändern, dass sie auf alle Kopien zeigen?

Beachten Sie, dass ich die Zeilen nicht in andere Tabellen kopiere. Die Zeilen in Tabelle A werden in Tabelle A kopiert, und die Zeilen in Tabelle B werden in Tabelle B kopiert.

Wie kann ich sicherstellen, dass die Fremdschlüsselreferenzen als Teil der Kopie neu ausgerichtet werden?

Um zu klären, ich versuche, eine generische Möglichkeit zu finden, dies zu tun. Das Beispiel, das ich gebe, beinhaltet zwei Tabellen, aber in der Praxis kann das Abhängigkeitsdiagramm viel komplizierter sein. Selbst eine generische Art, SQL dynamisch zu generieren, um die Arbeit zu erledigen, wäre in Ordnung.

UPDATE:

Die Leute fragen, warum dies notwendig ist, also werde ich einige Hintergrundinformationen geben. Es kann viel zu viel sein, aber hier geht:

Ich arbeite mit einer alten Desktop-Anwendung, die auf ein Client-Server-Modell verschoben wurde. Aber die Anwendung verwendet immer noch ein rudimentäres internes Binärdateiformat zum Speichern von Daten für ihre Tabellen. Eine Datendatei ist nur eine Kopfzeile, gefolgt von einer Reihe von Zeilen, von denen jede nur die binären serialisierten Feldwerte ist, deren Reihenfolge durch eine Schemadatei bestimmt wird. Das einzig Gute daran ist, dass es sehr schnell ist. Es ist schrecklich in jeder anderen Hinsicht. Ich verschiebe die Anwendung zu SQL Server und versuche, die Leistung nicht zu sehr zu verschlechtern.

Dies ist eine Art von Scheduling-Anwendung; Die Daten sind für niemanden kritisch, und es ist kein Audit-Tracking usw. erforderlich. Es ist keine supermassive Menge an Daten, und wir müssen nicht unbedingt sehr alte Daten herumtragen, wenn die Datenbank zu groß wird.

Eine Funktion, die sie gewohnt sind, ist die Fähigkeit, komplette Zeitpläne zu duplizieren, um "Was-wäre-wenn" -Szenarien zu erstellen, mit denen sie sich beschäftigen können. Jeder Benutzer kann das so oft machen, wie er möchte, so oft er möchte. In der alten Datenbank werden die Datendateien für jeden Zeitplan in einem eigenen Datenordner gespeichert, der anhand seines Namens identifiziert wird. Das Kopieren eines Zeitplans war also so einfach wie das Kopieren des Datenordners und das Umbenennen.

I muss in der Lage, die gleiche Sache mit SQL Server effektiv zu tun oder die Migration wird nicht funktionieren. Vielleicht denkst du, dass ich nur die Daten kopieren kann, die tatsächlich geändert werden, um Redundanz zu vermeiden; aber das klingt ehrlich zu kompliziert, um machbar zu sein.

Um einen weiteren Schraubenschlüssel in den Mix zu werfen, kann es eine Hierarchie von Zeitplandatenordnern geben. Ein Datenordner kann also einen Datenordner enthalten, der einen Datenordner enthalten kann. Und das Kopieren kann auf jeder Ebene erfolgen.

In SQL Server implementiere ich eine geschachtelte Mengenhierarchie, um dies nachzuahmen. Ich habe eine DATA_SET-Tabelle wie folgt:

CREATE TABLE dbo.DATA_SET 
(
    DATA_SET_ID UNIQUEIDENTIFIER PRIMARY KEY, 
    NAME NVARCHAR(128) NOT NULL, 
    LFT INT NOT NULL, 
    RGT INT NOT NULL 
) 

Also, es gibt eine Baumstruktur von Datensätzen. Jeder Datensatz stellt einen Zeitplan dar und kann untergeordnete Datensätze enthalten. Jede Zeile in jeder Tabelle hat eine DATA_SET_ID FK-Referenz, die angibt, zu welchem ​​Datensatz sie gehört. Wenn ich einen Datensatz kopiere, kopiere ich alle Zeilen in der Tabelle für diesen Datensatz und jeden zweiten Datensatz in die gleiche Tabelle, aber referenziere neue Datensätze.

So, hier ist ein einfaches konkretes Beispiel:

CREATE TABLE FOO 
(
    FOO_ID BIGINT PRIMARY KEY, 
    DATA_SET_ID BIGINT FOREIGN KEY REFERENCES DATA_SET(DATA_SET_ID) NOT NULL 
) 


CREATE TABLE BAR 
(
    BAR_ID BIGINT PRIMARY KEY, 
    DATA_SET_ID BIGINT FOREIGN KEY REFERENCES DATA_SET(DATA_SET_ID) NOT NULL, 
    FOO_ID UNIQUEIDENTIFIER PRIMARY KEY 
) 

INSERT INTO FOO 
SELECT 1, 1 UNION ALL 
SELECT 2, 1 UNION ALL 
SELECT 3, 1 UNION ALL 

INSERT INTO BAR 
SELECT 1, 1, 1 
SELECT 2, 1, 2 
SELECT 3, 1, 3 

So lassen Sie uns sagen, dass ich Daten 1 in einen neuen Datensatz von ID gesetzt kopieren 2. Nachdem ich kopieren, werden die Tabellen wie folgt aussehen:

FOO 
FOO_ID, DATA_SET_ID 
1 1 
2 1 
3 1 
4 2 
5 2 
6 2 

BAR 
BAR_ID, DATA_SET_ID, FOO_ID 
1 1 1 
2 1 2 
3 1 3 
4 2 4 
5 2 5 
6 2 6 

Wie Sie sehen können, verweisen die neuen BAR-Zeilen auf die neuen FOO-Zeilen. Es ist nicht die Umverdrahtung der DATA_SET_IDs, nach denen ich frage. Ich frage nach der Neuverschlüsselung der Fremdschlüssel im Allgemeinen.

Also, das war sicher zu viel Information, aber da gehts los.

Ich bin sicher, es gibt viele Bedenken hinsichtlich der Leistung mit der Idee der Massenkopie der Daten so. Die Tische werden nicht riesig sein. Ich erwarte in keiner Tabelle mehr als 1000 Datensätze, und die meisten Tabellen werden viel kleiner sein. Alte Datensätze können sofort und ohne Auswirkungen gelöscht werden.

Danke, Tedderz

+0

Können Sie ein Beispiel für die Daten und was Sie erwarten? –

+0

Sie haben also 2 Tabellen A & B, die sich gegenseitig referenzieren, und Sie möchten eine Teilmenge davon in die Tabellen C & D kopieren (die referentielle Integrität bleibt dabei erhalten). Warum ändern sich die Fremdschlüssel überhaupt? Ich denke, es geht dir gut, es sei denn, es gibt eine Eigenschaft von Schlüsseln, die mir nicht bekannt ist. – PowerUser

+0

Ihr Entwurf ist vermutlich nicht normalisiert? Wenn ich es richtig verstehe, möchte ich einige Zeilen extrahieren und sie unverändert mit neuen Primärschlüsseln in Tabelle A zurückschreiben. Dann extrahiere die Zeilen aus tableB, die auf die alten von tableA extrahierten Zeilen verweisen, und schreibe sie zurück in tableB, die auf die neuen Zeilen in tableA verweisen (Ich bin mir bewusst, dass ich nur wiederhole, was du gesagt hast, aber ich möchte bestätigen, dass ich es verstehe). Können Sie vielleicht Ihre Daten beschreiben, weil ich mir keinen Anwendungsfall vorstellen kann, der mit einem normalisierten Design nicht besser bedient werden könnte? –

Antwort

1

Hier ist ein Beispiel mit drei Tabellen, die wahrscheinlich bekommen Sie begonnen haben.

DB-Schema

CREATE TABLE users 
    (user_id int auto_increment PRIMARY KEY, 
    user_name varchar(32)); 
CREATE TABLE agenda 
    (agenda_id int auto_increment PRIMARY KEY, 
    `user_id` int, `agenda_name` varchar(7)); 
CREATE TABLE events 
    (event_id int auto_increment PRIMARY KEY, 
    `agenda_id` int, 
    `event_name` varchar(8)); 

Ein SP einen Benutzer mit seiner Agenda und Ereignisse zu klonen zeichnet

DELIMITER $$ 
CREATE PROCEDURE clone_user(IN uid INT) 
BEGIN 
    DECLARE last_user_id INT DEFAULT 0; 

    INSERT INTO users (user_name) 
    SELECT user_name 
     FROM users 
    WHERE user_id = uid; 

    SET last_user_id = LAST_INSERT_ID(); 

    INSERT INTO agenda (user_id, agenda_name) 
    SELECT last_user_id, agenda_name 
     FROM agenda 
    WHERE user_id = uid; 

    INSERT INTO events (agenda_id, event_name) 
    SELECT a3.agenda_id_new, e.event_name 
     FROM events e JOIN 
    (SELECT a1.agenda_id agenda_id_old, 
      a2.agenda_id agenda_id_new 
     FROM 
    (SELECT agenda_id, @n := @n + 1 n 
     FROM agenda, (SELECT @n := 0) n 
     WHERE user_id = uid 
     ORDER BY agenda_id) a1 JOIN 
    (SELECT agenda_id, @m := @m + 1 m 
     FROM agenda, (SELECT @m := 0) m 
     WHERE user_id = last_user_id 
     ORDER BY agenda_id) a2 ON a1.n = a2.m) a3 
     ON e.agenda_id = a3.agenda_id_old; 
END$$ 
DELIMITER ; 

einem Benutzer

CALL clone_user(3); 

Hier ist SQLFiddle Demo zu klonen.

0

Kürzlich musste ich ein ähnliches Problem lösen; das heißt, ich musste eine Reihe von Zeilen in einer Tabelle (Tabelle A) sowie alle Zeilen in verknüpften Tabellen kopieren, deren Fremdschlüssel auf den Primärschlüssel von Tabelle A zeigen. Ich habe Postgres verwendet, so dass die genauen Abfragen abweichen können, aber der Gesamtansatz ist der gleiche. Der größte Vorteil dieses Ansatzes besteht darin, dass es rekursiv

TLDR unendlich tief zu gehen, verwendet werden können: Der Ansatz sieht wie folgt aus

1) find all the related table/columns of Table A 
2) copy the necessary data into temporary tables 
3) create a trigger and function to propagate primary key column 
    updates to related foreign keys columns in the temporary tables 
4) update the primary key column in the temporary tables to the next 
    value in the auto increment sequence 
5) Re-insert the data back into the source tables, and drop the 
    temporary tables/triggers/function 

1) Der erste Schritt ist es, das Informationsschema abfragen zu finden alle Tabellen und Spalten, die Tabelle A. In Postgres verweisen könnte dies wie folgt aussehen:

SELECT tc.table_name, kcu.column_name 
FROM information_schema.table_constraints tc 
JOIN information_schema.key_column_usage kcu 
ON tc.constraint_name = kcu.constraint_name 
JOIN information_schema.constraint_column_usage ccu 
ON ccu.constraint_name = tc.constraint_name 
WHERE constraint_type = 'FOREIGN KEY' 
AND ccu.table_name='<Table A>' 
AND ccu.column_name='<Primary Key>' 

2) Als nächstes werden wir die Daten aus Tabelle A und alle anderen Tabellen kopieren müssen die Tabelle verweisen A - sagen wir, es gibt eine namens Tabelle B. Um diesen Prozess zu starten, erstellen wir eine temporäre Tabelle für jede dieser Tabellen und wir werden sie mit den Daten füllen, die wir kopieren müssen. Dies könnte wie folgt aussehen:

CREATE TEMP TABLE temp_table_a AS (
    SELECT * FROM <Table A> WHERE ... 
) 

CREATE TEMP TABLE temp_table_b AS (
    SELECT * FROM <Table B> WHERE <Foreign Key> IN (
     SELECT <Primary Key> FROM temp_table_a 
    ) 
) 

3) Wir können nun eine Funktion definieren, die Primärschlüsselspalte Updates, um damit verbundene Fremdschlüsselspalten kaskadiert werden, und Trigger, die immer dann, wenn die Primärschlüsselspalte Änderungen ausgeführt werden.Zum Beispiel:

CREATE OR REPLACE FUNCTION cascade_temp_table_a_pk() 
RETURNS trigger AS 
$$ 
BEGIN 
    UPDATE <Temp Table B> SET <Foreign Key> = NEW.<Primary Key> 
    WHERE <Foreign Key> = OLD.<Primary Key>; 

    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 

CREATE TRIGGER trigger_temp_table_a 
AFTER UPDATE 
ON <Temp Table A> 
FOR EACH ROW 
WHEN (OLD.<Primary Key> != NEW.<Primary Key>) 
EXECUTE PROCEDURE cascade_temp_table_a_pk(); 

4) Jetzt aktualisieren wir nur die Primärschlüsselspalte in auf den nächsten Wert der Sequenz der Quelltabelle(). Dadurch wird der Trigger aktiviert und die Aktualisierungen werden in die Spalten des Fremdschlüssels in kaskadiert. In Postgres können Sie folgendes tun:

UPDATE <Temp Table A> 
SET <Primary Key> = nextval(pg_get_serial_sequence('<Table A>', '<Primary Key>')) 

5) Legen Sie die Daten wieder aus den temporären Tabellen zurück in die Quellentabellen. Und dann die temporären Tabellen, Trigger und Funktionen löschen.

INSERT INTO <Table A> (SELECT * FROM <Temp Table A>) 
INSERT INTO <Table B> (SELECT * FROM <Temp Table B>) 
DROP TRIGGER trigger_temp_table_a 
DROP cascade_temp_table_a_pk() 

Es ist möglich, diese allgemeine Ausrichtung zu nehmen und es in ein Skript dreht die rekursiv aufgerufen, um werden kann, um unendlich tief zu gehen. Ich habe genau das mit Python gemacht (unsere Anwendung verwendete django, so dass ich das django ORM verwenden konnte, um dies einfacher zu machen)

Verwandte Themen