Kopieren von Daten von einer DB in eine andere DB

Themen rund um den praktischen Einsatz von Firebird. Fragen zu SQL, Performance, Datenbankstrukturen, etc.

Moderator: thorben.braun

Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

@Marin:

ja, die Source- und Ziel-DB liegen auf ja in dem Moment auf demselben Server-PC,
nur halt in unterschiedlichen Laufwerks-Pfaden.

Das findet nur auf dem Server-PC selbst statt und tangiert/braucht das Netzwerk
in keiner Weise.

Folglich kann auch von sehr stabilen DB-Verbindungen ausgegangen werden.

Viele Grüße
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

@Martin:

"Das heißt, du musst zwei Schritte berücksichtigen
1. Struktur in Ziel-DB anpassen
2. Neue und modifizierte Daten einspielen"

Nein, genau anders herum:

Alte, vorhandene, Daten in eine "modifizierte" Struktur der Ziel-DB einspielen.

Wobei die Modifizierungen von der "alten" gegenüber der "neuen" Struktur bekannt sind bzw. per Script automatisch ermittelt werden. Das Ermitteln, also Abgleichen zweier DB-Strukturen per Script kann ich inzwischen ziemlich perfekt.
bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Ich habe da ganz gute Erfahrungen mit DevExpress und deren XAF-Framework gemacht.
Dies entspricht i.W. einem Entity-Framework. Man definiert seine Anwendung, Tabellen und Zugriffe.
Die DB wird automatisch generiert und Änderungen am Modell werden beim nächsten Zugriff automatisch aktualisiert.
Ein Versionswechsel der DB war da dann kein Problem 8-).
vr2
Beiträge: 217
Registriert: Fr 13. Apr 2018, 00:13

Hallo Hamburgo,
Hamburgo hat geschrieben: Fr 8. Apr 2022, 11:49 @Volker:

Mir wäre sehr geholfen, wenn Du mir Muster-Codes für zwei Szenarien geben könntest.

1. Kopiere alle Daten-Sätze "aller" Spalten z.B. der Tabelle "Kunden" von Source-DB.

2. Kopiere aus allen Daten-Sätzen z.B. die Spalten Anrede, Vorname und Nachname aus Tabelle "Kunden"
von Source-DB in Tabelle "Alt_Kunden".

p.s.: Der Vollständigkeit halber die Info, ich nutze unter PHP die FireBird- und die PDO-Extension,
was aber, so glaube ich, hier keine Rolle spielt oder ?
Unten der Code der CrossDB-SP. Aufruf in der ZielDB, die SP zieht die Daten aus der entfernten QuellDB zu sich lokal, bspw so:

Code: Alles auswählen

select * from get_xdb_insertsql('quelldb', 'select * from kunden', 20, 'kunden')
Dabei ist quelldb ein Alias für die QuellDB, kann auch ein Connect-String sein,
dann kommt das Quellselect,
20 ist eine fürs Beispiel angenommene Zahl der Felder der Quelldaten, das steuert nur, ob in der SP das insert mit oder ohne Feldliste umgesetzt wird,
und schließlich der Name der Zieltabelle in der ZielDB (lokal). Die Zieltabelle muss existieren und eine passende Struktur haben.

Aufruf für den 2. Fall:

Code: Alles auswählen

select * from get_xdb_insertsql('quelldb', 'select anrede, vorname, nachname from kunden', 3, 'alt_kunden')
Die SP ist nur ein Codegenerator, sie erzeugt einen execute block, den Du dann ausführen kannst, und der transferiert die Daten.

Die SP:

Code: Alles auswählen

CREATE PROCEDURE GET_XDB_INSERTSQL (
  SRCDBC VARCHAR(100),
  SRCSQL VARCHAR(10000),
  SRCFLDCOUNT INTEGER,
  DSTTBL VARCHAR(30)
)
RETURNS (
  "SQL" VARCHAR(20000)
)
AS
begin
  -- erzeugt crossdb-SQL für insert in die Zieltabelle und benutzt dazu das "insert" statement
  -- input Connect String zur Quell-DB, (SrcDBC), ein Quell-Select (SrcSQL),
  --       Anzahl seiner Spalten (SrcFldCount) und der Name der Zieltabelle (DstTbl)
  -- Die Feldliste der Zieltabelle werden ermittelt, das SrcSQL muss dieser Feldliste entsprechen
  -- Feldtypen der Zieltabelle werden ausgeliehen

  -- das SrcSQL muss bis zu SrcFldCount der Struktur der Zieltabelle entsprechen
  dsttbl = upper(:dsttbl);
  srcsql = replace(:srcsql, '''', '''''''''');
  -- SRCDBC auf DB-Alias reduzieren, damit user/pw der aktuellen connection benutzt werden können
  srcdbc = substring(:srcdbc from position(':', :srcdbc) + 1);  

  sql =
'execute block RETURNS (result integer, SQL varchar(30000))
as
declare declares varchar(10000);
declare params varchar(3000);
declare flds varchar(6000);
declare wildcards varchar(3000);
declare insertsql varchar(20000);
declare dstfldcount integer;
declare crlf varchar(2);
begin
  -- daten aus srcSQL nach dsttabname laden
  crlf = ascii_char(13) || ascii_char(10);

  -- anzahl der zieltabellenfelder ermitteln
  dstfldcount = (select count(rdb$field_position)
                 from rdb$relation_fields
                 where trim(rdb$relation_name) = ''' || :dsttbl || ''');

  -- struktur der zieltabelle ermitteln
  with
  a (fldname, fldpos) as (
    select trim(rdb$field_name) fldname, rdb$field_position
    from rdb$relation_fields
    where rdb$system_flag = 0 and trim(rdb$relation_name) = ''' || :dsttbl || '''
    order by 2
    rows ' || :srcfldcount || '),

  b (declares, flds, wildcards, params) as (
    select :crlf || list(''declare '' || fldname || '' TYPE OF COLUMN ' || :dsttbl || '.'' || fldname || '';'', :crlf) || :crlf,
           list(fldname), list(''?''), list('':'' || fldname)
    from a)

  select * from b
  into :declares, :flds, :wildcards, :params;

  -- kurzes statement ohne feldliste?
  if (:dstfldcount <= ' || :srcfldcount || ') then
    insertsql = ''''''insert into ' || :dsttbl || ' values ('' || :wildcards || '')'''''';
  else
    insertsql = ''''''insert into ' || :dsttbl || ' ('' || :flds || '')  values ('' || :wildcards || '')'''''';

  sql = ''execute block returns (result integer) as '' || :declares || ''begin '';
  sql = sql || :crlf || ''  result = 0;'';
  sql = sql || :crlf || ''  for execute statement ''''' || :srcsql || ''''' '';
  sql = sql || :crlf || ''  on external ''''' || :srcdbc || ''''' '';
  sql = sql || :crlf || ''  into '' || :params || '' '' || :crlf || ''  do '' || :crlf || ''  begin '';

  -- eintragen lokal
  sql = sql || :crlf || ''    execute statement ('' || :insertsql || '') ('' || :params || '');'';
  sql = sql || :crlf || ''    result = result + 1; '' || :crlf || ''  end '' || :crlf || ''  suspend; '' || :crlf || ''end;'';

  in autonomous transaction
  do
    execute statement sql
    -- anzahl der inserts zurückgeben
    into :result;
  suspend;
end';
  suspend;
end
;
Grüße, Volker
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Volker,

ich bin schwer beeindruckt was Du mir da alles reingestellt hast !!!

Das ist ja Wahnsinn. Herzlichen Dank für Deine Mühe und den Aufwand.

Hätte ich geahnt, wieviel Zeugs das ist, dann hätte ich mich kaum getraut
danach zu fragen.

Auf den ersten Blick verstehe ich zwar nur "Bahnhof", gehe aber davon aus,
nach ein paar Stunden Hirn-Jogging und üben wird schon Licht ins Dunkle
kommen.

Danke und viele Grüße
Hamburgo
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Volker,

nachdem ich nun endlich alle Probleme in Sachen CharSet's aus dem
Weg geräumt habe, geht es nun an den 2. Versuch Deine SP zum Laufen
zu bringen.

Der 1. Versuch war eben genau an diesen falschen CharSet's in meinen DB's gescheitert,
was auch gut so war, da erst dieser die Schwere des Problems mir deutlich gemacht hat.

Ich hatte es mir anders erhoft, aber nicht wirklich erwartet, dass dieser gleich klappt.

Leider fliegt dieser der mir wenig sagenden Meldung um die Ohren:
SQLSTATE[HY000]: General error: -804 Dynamic SQL Error SQL error code = -804 Incorrect values within SQLDA structure
Folgendes kann ich an Info's liefern:

1. Dieser Teil zeigt, dass Deine SP in der DB vorhanden ist:

Code: Alles auswählen


$Select = SELECT * FROM RDB$PROCEDURES WHERE RDB$PROCEDURE_NAME='GET_XDB_INSERTSQL';

Connect-String: >//localhost/E:/DataBase/Labor_Update.FDB, SYSDBA, HomePassword<

$Record:

Array
(
    [RDB$PROCEDURE_NAME] => GET_XDB_INSERTSQL                                                                            
    [RDB$PROCEDURE_ID] => 1
    [RDB$PROCEDURE_INPUTS] => 4
    [RDB$PROCEDURE_OUTPUTS] => 1
    [RDB$DESCRIPTION] => 
    [RDB$PROCEDURE_SOURCE] => begin
  -- erzeugt crossdb-SQL für insert in die Zieltabelle und benutzt dazu das "insert" statement
  -- input Connect String zur Quell-DB, (SrcDBC), ein Quell-Select (SrcSQL),
  --       Anzahl seiner Spalten (SrcFldCount) und der Name der Zieltabelle (DstTbl)
  -- Die Feldliste der Zieltabelle werden ermittelt, das SrcSQL muss dieser Feldliste entsprechen
  -- Feldtypen der Zieltabelle werden ausgeliehen

  -- das SrcSQL muss bis zu SrcFldCount der Struktur der Zieltabelle entsprechen
  dsttbl = upper(:dsttbl);
  srcsql = replace(:srcsql, '''', '''''''''');
  -- SRCDBC auf DB-Alias reduzieren, damit user/pw der aktuellen connection benutzt werden können
  srcdbc = substring(:srcdbc from position(':', :srcdbc) + 1);  

  sql =
'execute block RETURNS (result integer, SQL varchar(30000))
as
declare declares varchar(10000);
declare params varchar(3000);
declare flds varchar(6000);
declare wildcards varchar(3000);
declare insertsql varchar(20000);
declare dstfldcount integer;
declare crlf varchar(2);
begin
  -- daten aus srcSQL nach dsttabname laden
  crlf = ascii_char(13) || ascii_char(10);

  -- anzahl der zieltabellenfelder ermitteln
  dstfldcount = (select count(rdb$field_position)
                 from rdb$relation_fields
                 where trim(rdb$relation_name) = ''' || :dsttbl || ''');

  -- struktur der zieltabelle ermitteln
  with
  a (fldname, fldpos) as (
    select trim(rdb$field_name) fldname, rdb$field_position
    from rdb$relation_fields
    where rdb$system_flag = 0 and trim(rdb$relation_name) = ''' || :dsttbl || '''
    order by 2
    rows ' || :srcfldcount || '),

  b (declares, flds, wildcards, params) as (
    select :crlf || list(''declare '' || fldname || '' TYPE OF COLUMN ' || :dsttbl || '.'' || fldname || '';'', :crlf) || :crlf,
           list(fldname), list(''?''), list('':'' || fldname)
    from a)

  select * from b
  into :declares, :flds, :wildcards, :params;

  -- kurzes statement ohne feldliste?
  if (:dstfldcount <= ' || :srcfldcount || ') then
    insertsql = ''''''insert into ' || :dsttbl || ' values ('' || :wildcards || '')'''''';
  else
    insertsql = ''''''insert into ' || :dsttbl || ' ('' || :flds || '')  values ('' || :wildcards || '')'''''';

  sql = ''execute block returns (result integer) as '' || :declares || ''begin '';
  sql = sql || :crlf || ''  result = 0;'';
  sql = sql || :crlf || ''  for execute statement ''''' || :srcsql || ''''' '';
  sql = sql || :crlf || ''  on external ''''' || :srcdbc || ''''' '';
  sql = sql || :crlf || ''  into '' || :params || '' '' || :crlf || ''  do '' || :crlf || ''  begin '';

  -- eintragen lokal
  sql = sql || :crlf || ''    execute statement ('' || :insertsql || '') ('' || :params || '');'';
  sql = sql || :crlf || ''    result = result + 1; '' || :crlf || ''  end '' || :crlf || ''  suspend; '' || :crlf || ''end;'';

  in autonomous transaction
  do
    execute statement sql
    -- anzahl der inserts zurückgeben
    into :result;
  suspend;
end';
  suspend;
end
;

    [RDB$PROCEDURE_BLR] => &5d&5'&5&5 N&5 N-›g))ºREPLACE)''''')()#"ºPOSITION:)ÿÿÿ)'''''''''''''''''';execute block RETURNS (result integer, SQL varchar(30000))
as
declare declares varchar(10000);
declare params varchar(3000);
declare flds varchar(6000);
declare wildcards varchar(3000);
declare insertsql varchar(20000);
declare dstfldcount integer;
declare crlf varchar(2);
begin
  -- daten aus srcSQL nach dsttabname laden
  crlf = ascii_char(13) || ascii_char(10);

  -- anzahl der zieltabellenfelder ermitteln
  dstfldcount = (select count(rdb$field_position)
                 from rdb$relation_fields
                 where trim(rdb$relation_name) = ')ì');

  -- struktur der zieltabelle ermitteln
  with
  a (fldname, fldpos) as (
    select trim(rdb$field_name) fldname, rdb$field_position
    from rdb$relation_fields
    where rdb$system_flag = 0 and trim(rdb$relation_name) = ')'
    order by 2
    rows )w),

  b (declares, flds, wildcards, params) as (
    select :crlf || list('declare ' || fldname || ' TYPE OF COLUMN )ó.' || fldname || ';', :crlf) || :crlf,
           list(fldname), list('?'), list(':' || fldname)
    from a)

  select * from b
  into :declares, :flds, :wildcards, :params;

  -- kurzes statement ohne feldliste?
  if (:dstfldcount <= )') then
    insertsql = '''insert into )K values (' || :wildcards || ')''';
  else
    insertsql = '''insert into )æ (' || :flds || ')  values (' || :wildcards || ')''';

  sql = 'execute block returns (result integer) as ' || :declares || 'begin ';
  sql = sql || :crlf || '  result = 0;';
  sql = sql || :crlf || '  for execute statement '')0'' ';
  sql = sql || :crlf || '  on external '')à'' ';
  sql = sql || :crlf || '  into ' || :params || ' ' || :crlf || '  do ' || :crlf || '  begin ';

  -- eintragen lokal
  sql = sql || :crlf || '    execute statement (' || :insertsql || ') (' || :params || ');';
  sql = sql || :crlf || '    result = result + 1; ' || :crlf || '  end ' || :crlf || '  suspend; ' || :crlf || 'end;';

  in autonomous transaction
  do
    execute statement sql
    -- anzahl der inserts zurückgeben
    into :result;
  suspend;
end)ÿ›ÿÿÿÿ)ÿÿL
    [RDB$SECURITY_CLASS] => SQL$9                                                                                        
    [RDB$OWNER_NAME] => SYSDBA                                                                                       
    [RDB$RUNTIME] => 
    [RDB$SYSTEM_FLAG] => 0
    [RDB$PROCEDURE_TYPE] => 1
    [RDB$VALID_BLR] => 1
    [RDB$DEBUG_INFO] => SRCDBCSRCSQLSRCFLDCOUNTDSTTBLSQLSQL?AOwT´TQ	ÿ
)

GET_XDB_INSERTSQL-Check_Procedur: $numRowS = 1

2. Die Tabelle, die ich für diesem Versuch genommen habe, hat in Quell- und Ziel-DB folgende Struktur:

Code: Alles auswählen

CREATE TABLE SYSTEM (
  RID_SYSTEM     RID NOT NULL,
  SYSTEM_NR      STXT,
  LIZENZ_NR      STXT,
  AKTIV          SI DEFAULT 0,
  DATEN          TS DEFAULT '11.11.1111 00:00',
  DATENBANK      TS DEFAULT '11.11.1111 00:00',
  EDITDATE       TS DEFAULT '11.11.1111 00:00',
  INDATE         TS DEFAULT 'NOW',
  STATUS         SI DEFAULT 0,
  SCRIPT         MTXT,
  SCRIPT_USER_NR STXT,
  SCRIPT_USER    STXT,
  BATCH_SCRIPT   MTXT,
  BATCH_DATE     TS DEFAULT '11.11.1111 00:00',
  ANDROID        TS DEFAULT '11.11.1111 00:00',
  DISTANCEMATRIX BI DEFAULT 0,
  GEOCODING      BI DEFAULT 0
);
3. Dieser Teil zeigt, mit welcher Systax ich die SP angesprochen habe und die Meldung:

Code: Alles auswählen

$SQL: >select * from get_xdb_insertsql('//localhost/E:/DataBase/Labor.FDB, SYSDBA, HomePassword', 'select * from system', 17, 'system')<

Connect-String: >//localhost/E:/DataBase/Labor_Update.FDB, SYSDBA, HomePassword<

$Message: >SQLSTATE[HY000]: General error: -804 Dynamic SQL Error SQL error code = -804 Incorrect values within SQLDA structure < 
Habe ich evtl. irgendwelche Hochkommas oder Vergleichbares vergessen ?

Danke und viele Grüße
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Volker,

heute Morgen habe ich Deine von mir gestern eingetragene SP gelöscht und nochmals
Dein Original von oben sorgfälltig eingestellt, um sicher zu stellen, dass mir gestern kein Copy&Paste-Fehler unterlaufen ist.

Leider hat das nichts gebracht.
vr2
Beiträge: 217
Registriert: Fr 13. Apr 2018, 00:13

Hallo Hamburgo,

Missverständnis, der Connectstring ist nicht richtig, es muss ohne username/password sein. Connectstring im Sinne von Firebird, nicht aus Anwendungssicht wie etwa php, wo eine ganze Funktion mit Connectstring und Logindaten kommagetrennt aufgerufen wird.

Code: Alles auswählen

select * from get_xdb_insertsql('//localhost/E:/DataBase/Labor.FDB', 'select * from system', 17, 'system')
Allerdings geht die SP von DB-Aliasen aus und nicht von kompletten Pfadangaben zu einer DB. Das hab ich vergessen zu erwähnen, sorry. Wenn Du Dir in databases.conf (oder aliases.conf, falls FB25) einen Alias labor so definierst:

Code: Alles auswählen

labor = E:\DataBase\Labor.FDB
wird der Aufruf zu

Code: Alles auswählen

select * from get_xdb_insertsql('//localhost/labor', 'select * from system', 17, 'system')
localhost kannst Du weglassen, denn Du bist ja bereits auf dem Zielserver, connectest dort nur aus der ZielDB zur DB labor, und der Server lauscht auf dem Standardport, dh

Code: Alles auswählen

select * from get_xdb_insertsql('labor', 'select * from system', 17, 'system')
reicht, wenn Du den DB-Alias definiert hast.

Zur der Sache, dass die SP in der DB vorhanden ist: Verwendest Du kein DB-Admintool wie Flamerobin o.ä.? Denn da sieht man gleich in der Liste der Prozeduren, welche da sind. Du musst nicht die Systemtabellen dafür abfragen.

Grüße, Volker
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Volker,

vielen Dank für die aufklärende Antwort.

Ob ich das hinbekomme, wie von Dir beschrieben, weiss ich noch nicht,
weil ich mich noch nie mit DB-Aliasen beschäftigt habe.

Das ist Neu-Land für mich. Ich werde Dir berichten. :)

Danke und viele Grüße
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Volker,

zu Deinem letzten Absatz:

Doch, ich verwende u.a. IB-Expert. Damit habe ich auch Deine SP in die DB gebracht
und konnte das dort natürlich auch sofort sehen, dass die SP drin ist.

Die Abfrage in meinem Script auf die System-Tabelle habe ich nur deshalb eingebaut,
um über diese Kontroll-Abfrage sicherzustellen, dass ich nicht versehentlich die falsche
DB anspreche oder sonstige Syntax-Fehler begehe.

Da wusste ich ja noch nichts von dem Erfordernis eines DB-Aliases für Deine SP.

Jetzt erklärt sich natürlich das Scheitern meines Versuches. Vorher war ich echt
der Verzweiflung nahe.
Antworten