Babylonisches Sprachgewirr in Datenbank
Hallo zusammen,
mal wieder mein Moppedforum.... (zu meiner Verteidigung: Das ist nicht auf meinem Mist gewachsen)
Die jetzige Datenbank beinhaltet einen Mischmasch aus UTF-8, UTF8-general_ci, swedish_latin_1, latin1_german2_ci, latin1_german1_ci und noch diversen anderen Sprachkodierungen.
Per Hand kann das kein Mensch ersetzen, das muss automatisiert stattfinden. Suchen und ersetzen über die gesamte DB geht leider nicht . Es werden die ersten Korrekturen dann teilweise wieder von der zweiten Korrektur verschlimmbessert. War eben über 5h mit einem Programmierer dran. Aber er hat es jetzt aufgegeben.
Im Endeffekt müsste jeder einzelne Eintrag in der DB automatisiert exportiert, analysiert, korrekt umgewandelt und wieder importiert werden.
Nach meinem Verständnis: Export Datensatz, check auf umwandelbarkeit, die Variante bei der keine Fehlermeldung kommt dann zu UTF-8 umwandeln und den nächsten Datensatz vornehmen.
Sollten einige wenige Beiträge dadurch verstümmelt werden ist das kein Problem, darauf könnte man verzichten.
Es geht um ca. 130MB Datenbank.
Haben wir hier jemanden im Forum, der sowas programmieren kann ? Oder gibt es dafür eine fertige Lösung ?
Grüße, Henere
PS: Soweit haben wir das gemacht, aber das haut alles nicht hin:
Ich versteh davon nur Bahnhof Westerland, bitte umsteigen.
mal wieder mein Moppedforum.... (zu meiner Verteidigung: Das ist nicht auf meinem Mist gewachsen)
Die jetzige Datenbank beinhaltet einen Mischmasch aus UTF-8, UTF8-general_ci, swedish_latin_1, latin1_german2_ci, latin1_german1_ci und noch diversen anderen Sprachkodierungen.
Per Hand kann das kein Mensch ersetzen, das muss automatisiert stattfinden. Suchen und ersetzen über die gesamte DB geht leider nicht . Es werden die ersten Korrekturen dann teilweise wieder von der zweiten Korrektur verschlimmbessert. War eben über 5h mit einem Programmierer dran. Aber er hat es jetzt aufgegeben.
Im Endeffekt müsste jeder einzelne Eintrag in der DB automatisiert exportiert, analysiert, korrekt umgewandelt und wieder importiert werden.
Nach meinem Verständnis: Export Datensatz, check auf umwandelbarkeit, die Variante bei der keine Fehlermeldung kommt dann zu UTF-8 umwandeln und den nächsten Datensatz vornehmen.
Sollten einige wenige Beiträge dadurch verstümmelt werden ist das kein Problem, darauf könnte man verzichten.
Es geht um ca. 130MB Datenbank.
Haben wir hier jemanden im Forum, der sowas programmieren kann ? Oder gibt es dafür eine fertige Lösung ?
Grüße, Henere
PS: Soweit haben wir das gemacht, aber das haut alles nicht hin:
root@www:/install# more fix_umlauts.pl
#!/usr/bin/perl
binmode STDOUT, ":encoding(utf8)";
while( <> ){
s/\x{83}\x{C2}//g;
print;
next;
s/\x{C3}\x{83}\x{C2}\x{9C}/\x{C3}\x{9C}/g;
s/\x{C3}\x{83}\x{C2}\x{BC}/\x{C3}\x{BC}/g;
s/\x{C3}\x{83}\x{C2}\x{83}\x{C3}\x{82}\x{C2}\x{BC}/\x{C3}\x{BC}/g; # 83c3 83c2 82c3 bcc2
s/\x{C3}\x{83}\x{C2}\x{96}/\x{C3}\x{96}/g;
s/\x{C3}\x{83}\x{C2}\x{B6}/\x{C3}\x{B6}/g;
s/\x{C3}\x{83}\x{C2}\x{83}\x{C3}\x{82}\x{C2}\x{B6}/\x{C3}\x{B6}/g; # 83c3 83c2 82c3 b6c2
s/\x{C3}\x{83}\x{C2}\x{84}/\x{C3}\x{84}/g;
s/\x{C3}\x{83}\x{C2}\x{A4}/\x{C3}\x{A4}/g;
s/\x{C3}\x{83}\x{C2}\x{83}\x{C3}\x{82}\x{C2}\x{A4}/\x{C3}\x{A4}/g; # 83c3 83c2 82c3 a4c2
s/\x{C3}\x{83}\x{C2}\x{9F}/ß/g;
s/\x{C3}\x{83}\x{C2}\x{A0}/á/g;
# 82c3 a0c2 # das koennte ein non-breaking space sein
s/\x{C3}\x{82}\x{C2}\x{A0}/ /g;
s/\x{C3}\x{82}\x{C2}\x{B0}/°/g;
s/\x{C3}\x{82}\x{C2}\x{B4}/´/g; # 82c3 b4c2
s/\x{C3}\x{82}\x{C2}\x{BA}/º/g; # 82c3 bac2
s/\x{C3}\x{82}\x{C2}\x{AB}/«/g; # 82c3 abc2
s/\x{C3}\x{82}\x{C2}\x{BB}/»/g; # 82c3 bbc2
s/\x{C3}\x{82}\x{C2}\x{A7}/§/g; # 82c3 a7c2
s/\x{C3}\x{82}\x{C2}\x{A4}/¤/g; # 82c3 a4c2
s/\x{C3}\x{83}\x{C2}\x{A9}/é/g; # 83c3 a9c2
s/\x{C3}\x{83}\x{C2}\x{BA}/ê/g; # 83c3 bac2
s/\x{C3}\x{A2}\x{C2}\x{82}\x{C2}\x{AC}/€/g; # a2c3 82c2 acc2
s/\x{C3}\x{83}\x{C2}\x{89}/É/g; # 83c3 83c2 82c3 89c2
s/\x{C3}\x{82}\x{C2}\x{BF}/¿/g; # 82c3 bfc2
s/\x{C3}\x{83}\x{C2}\x{A8}/¨/g; # 82c3 a8c2
s/\x{C3}\x{83}\x{C2}\x{81}/Ä/g; # 82c3 a8c2
s/\x{C3}\x{82}\x{C2}\x{B3}/³/g;
print;
# chomp;
# s/[ÜüÖöÄäßáÉéê§°º~«»¨³óa-zA-Z0-9 \t\.:,;\/\r\+"\*\@!\?_\`\'´\(\)\[\]\{\}<>\\|#%^§€¤&¿=-]*{{comment_single_line_double_slash:0}}
# die "error: \"$_\"\n" if( $_ ne "" );
}
Ich versteh davon nur Bahnhof Westerland, bitte umsteigen.
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 299734
Url: https://administrator.de/forum/babylonisches-sprachgewirr-in-datenbank-299734.html
Ausgedruckt am: 20.04.2025 um 17:04 Uhr
14 Kommentare
Neuester Kommentar
Das "next" in Zeile 10 verhindert, daß die folgenden Zeilen ausgeführt werden. Ist das so gewollt?
Zielführender als Zeichen(ketten) zu ersetzen ist hier die Verwendung des Moduls Encode:
So wie dein gepostetes Skript aussieht, wendest du das auf einen SQL Dump an. Das würde ich nur machen, wenn ich sicher wäre, daß die Datenbank ausschließlich Text enthält.
Ansonsten würde ich die Tabellen und Felder identifizieren, welche babylonischen Salat enthalten, und ausschließlich diese umkodieren, z.B. so:
(update: Encode::Guess eingebaut, weil es wohl viele Sprachkodierungen gibt)
Code untested. Für Fragen zu perl empfiehlt es sich auf http://perlmonks.org zu posten (geht auch anonym), da bekommt man sehr schnell wirklich kompetente Hilfe.
HTH,
0--gg-
Zielführender als Zeichen(ketten) zu ersetzen ist hier die Verwendung des Moduls Encode:
#!/usr/bin/perl
use Encode qw(from_to);
while(<>){
from_to($_, 'latin1', 'utf8'); # umwandlung von IS08856 nach UTF-8
from_to($_, 'utf8', 'latin1'); # oder andersrum
print;
}
So wie dein gepostetes Skript aussieht, wendest du das auf einen SQL Dump an. Das würde ich nur machen, wenn ich sicher wäre, daß die Datenbank ausschließlich Text enthält.
Ansonsten würde ich die Tabellen und Felder identifizieren, welche babylonischen Salat enthalten, und ausschließlich diese umkodieren, z.B. so:
(update: Encode::Guess eingebaut, weil es wohl viele Sprachkodierungen gibt)
#!/usr/bin/perl
use strict; use warnings; use diagnostics;
use DBI;
use Encode qw(from_to encode);
use Encode::Guess; # (update)
# hash keyed on table names, holding primary key and array of fields as a sub-hash
my %tables = (
table1 => {
key => "Id",
fields => [ qw(feld1 feld4 feld0815) ],
},
table2 => {
key => "primaryKey",
fields => [ qw(text description) ],
},
);
my @encodings = qw( iso8859-1 iso8859-15 cp775 iso-8859-10); # possible encodings, see Encode::Supported
my $database = "mopedDB";
my $user = "dbuser";
my $pass = "geheim";
my $dbh = DBI->connect("DBI:mysql:database=$database", $user, $pass, {RaiseError => 1} );
for my $table(keys %tables) {
my @fields = @{$tables{$table}->{fields}};
my $key = $tables{$table}->{key};
my $read_sth = $dbh->prepare('select $key, '. join(',', @fields) . " from $table");
my $write_sth = $dbh->prepare("update $table set " . join(',', map { "$_ = ?"}@fields). "where $key = ?");
$read_sth->execute;
while(my $r = $read_sth->fetchrow_arrayref) {
my @strings = @{{$r}{@fields}};
# from_to($_,'latin1','utf8') for @strings; # or vice versa # update - auskommentiert
# update
for(@strings) {
my $enc = guess_encoding($_, @encodings);
ref($enc) or die "Can't guess: '$_': $enc"; # trap error this way
my $utf8 = $enc->decode($_);
# or
# $utf8 = decode($enc->name, $_);
# or even
# from_to( $_, $enc->name, 'utf8');
print "$_ => $utf8\n"; # verbose
$_ = $utf8;
}
# end update
$write_sth->execute (@strings, $key);
}
}
Code untested. Für Fragen zu perl empfiehlt es sich auf http://perlmonks.org zu posten (geht auch anonym), da bekommt man sehr schnell wirklich kompetente Hilfe.
HTH,
0--gg-
Geht doch nicht darum, wer an was schuld ist sondern wie man das in Ordnung bringt. Oder?
Mein Vorschlag: Kopie der DB machen und das Skript auf die Kopie anwenden.
Vorher müssen die Mock-Up-Variablen %tables, $database, $user, $pass mit realen Werten befüllt werden.
Wenn das dann klappt, Fisch geputzt. Wenn nicht, kann ich vllt. weiterhelfen.
0--gg-
update: und @encodings, die Variable fehlte auch im geposteten Skript. "man Encode::Supported" für Info was es so alles gibt.
Mein Vorschlag: Kopie der DB machen und das Skript auf die Kopie anwenden.
Vorher müssen die Mock-Up-Variablen %tables, $database, $user, $pass mit realen Werten befüllt werden.
Wenn das dann klappt, Fisch geputzt. Wenn nicht, kann ich vllt. weiterhelfen.
0--gg-
update: und @encodings, die Variable fehlte auch im geposteten Skript. "man Encode::Supported" für Info was es so alles gibt.
Das folgende Skript ermittelt die Tabellen der Datenbank und rekodiert alle Felder vom typ "text" oder "varchar.*".
Trage die verwendeten Encodings in das Array @encodings in Zeile 12 ein.
0--gg-
Trage die verwendeten Encodings in das Array @encodings in Zeile 12 ein.
#!/usr/bin/perl
use strict; use warnings; use diagnostics;
use DBI;
use Encode qw(from_to encode);
use Encode::Guess; # (update)
my $database = "mopedDB";
my $user = "dbuser";
my $pass = "geheim";
# possible encodings, see "man Encode::Supported"
my @encodings = qw( iso8859-1 iso8859-15 cp775 iso-8859-10 utf-8-strict utf8);
my $dbh = DBI->connect("DBI:mysql:database=$database", $user, $pass, {RaiseError => 1} );
my (%tables,@tables);
my $sth = $dbh->prepare("show tables");
$sth->execute;
while(my($table) = $sth->fetchrow_array) {
push @tables, $table;
}
for my $table ( @tables ) {
$sth = $dbh->prepare("describe $table");
$sth->execute;
my $fields = [ ]; # empty array reference
my @primary = ();
while ( my $hr = $sth->fetchrow_hashref ) {
my ($col, $type) = @$hr{qw(Field Type)};
push @$fields, $col if $type eq 'text' or $type =~ /varchar/;
push @primary, $col if $hr->{Key} eq 'PRI';
}
if ( 1 == @primary) {
if (@$fields) {
$tables{$table} = { key => $primary, fields => $fields };
}
} else {
warn "no primary key found for table '$table'\n" if 0 == @primary;
warn "more than one primary key found for table '$table': (@primary)\n"
if @primary > 0;
}
}
for my $table(keys %tables) {
my @fields = @{$tables{$table}->{fields}};
my $key = $tables{$table}->{key};
my $read_sth = $dbh->prepare("select $key, ". join(',', @fields) . " from $table");
my $write_sql = "update $table set " . join(',', map { "$_ = ?"}@fields). " where $key = ?";
print "------------------\n$write_sql\n------------------\n";
my $write_sth = $dbh->prepare($write_sql);
$read_sth->execute;
while(my $r = $read_sth->fetchrow_hashref) {
my @strings = @$r{@fields};
my $id = $r->{$key};
for(@strings) {
next unless length $_;
my $enc;
for my $c (@encodings) {
$enc = guess_encoding($_, $c);
last if ref $enc;
}
ref($enc) or die "Can't guess: '$_': $enc"; # trap error this way
my $utf8 = $enc->decode($_);
from_to( $_, $enc->name, 'utf8') if $enc->name ne 'utf8';
}
$write_sth->execute (@strings, $id) or die "execute failed\n";
}
$write_sth->finish;
}
0--gg-
Man kann zwar alle angeben, dann wird aber evtl die Reihenfolge aus Gründen der Performance wichtig.
Obwohl... ist ja ein One-Shot.
Es wird jedes Encoding einzeln probiert (man kann zwar auf mehrere prüfen, aber guess_encoding() liefert nen Fehler zurück bzw. einen String und keine Referenz, wenn der zu prüfende String auf mehrere Encodings passt) und sobald ein gültiges Encoding erkannt wurde wird das verwendet und die anderen nicht mehr geprũft (das "last" in Zeile 59).
In der Datenbank werden wahrscheinlich Varianten von ISO8859 vorkommen, also ISO8859-1 bis ISO8859-16, denn ich glaube kaum dass jemand vietnamesisch kyrillisch oder kanji geschrieben hat.
Die Encodings stehen in der Handbuchseite für Encoding::Supported.
TL;DR:
wenn Du alle Encodings inkludieren willst, verwende
0--gg-
Obwohl... ist ja ein One-Shot.
Es wird jedes Encoding einzeln probiert (man kann zwar auf mehrere prüfen, aber guess_encoding() liefert nen Fehler zurück bzw. einen String und keine Referenz, wenn der zu prüfende String auf mehrere Encodings passt) und sobald ein gültiges Encoding erkannt wurde wird das verwendet und die anderen nicht mehr geprũft (das "last" in Zeile 59).
In der Datenbank werden wahrscheinlich Varianten von ISO8859 vorkommen, also ISO8859-1 bis ISO8859-16, denn ich glaube kaum dass jemand vietnamesisch kyrillisch oder kanji geschrieben hat.
Die Encodings stehen in der Handbuchseite für Encoding::Supported.
TL;DR:
wenn Du alle Encodings inkludieren willst, verwende
my @encodings = Encode->encodings(":all"); # currently 124 encodings
0--gg-
Der Output ist sinnlos, da war nur noch bissel debugging statement an :-P
- Zumindest hast Du Encodings eingegeben, die keine sind, sondern Kollationen - also Sortierreihenfolgen, die für die Sortierung einer ORDER BY Clause der Datenbank relevant sind; latin1_german1_ci ist z.B. kein Encoding, swedish_ci auch nicht. Die Bezeichnungen sind für phpMyAdmin interessant. Das sollte aber nix machen, weil die relevanten Encodings weiter vorne im Array stehen.
Wenn das Datum von PNs beim Update eines Textfeldes aktualisiert werden, gibt es wahrscheinlich einen Trigger in der DB, denn das Skript faßt Datumsfelder nicht an.
Der Screenshot sieht nach Datenbankinhalt in UTF-8 aus, das als latin1 dargestellt wird.
Das 'ß' ist in UTF-8 2 Bytes: 0xc39f, dargestellt wird es hier als "chr(0xc3) . chr(0x9f)" .
Oder es ist doppelt kodiert, dann müsste das 'Ã' als 2 Bytes in der Datenbank stehen - 0xc383 - und das, was 'ß' sein soll, ist dann 4 Bytes lang: 0xc383 0xc29f.
Man muss also mal so ein Feld auslesen und d byteweise entpacken (z.b. mit od).
Ist die Datenbank überhaupt in UTF-8?
0--gg-
Hab ich da jetzt was falsch gemacht mit der Eingabe der Encodings ?
- Zumindest hast Du Encodings eingegeben, die keine sind, sondern Kollationen - also Sortierreihenfolgen, die für die Sortierung einer ORDER BY Clause der Datenbank relevant sind; latin1_german1_ci ist z.B. kein Encoding, swedish_ci auch nicht. Die Bezeichnungen sind für phpMyAdmin interessant. Das sollte aber nix machen, weil die relevanten Encodings weiter vorne im Array stehen.
Wenn das Datum von PNs beim Update eines Textfeldes aktualisiert werden, gibt es wahrscheinlich einen Trigger in der DB, denn das Skript faßt Datumsfelder nicht an.
Der Screenshot sieht nach Datenbankinhalt in UTF-8 aus, das als latin1 dargestellt wird.
Das 'ß' ist in UTF-8 2 Bytes: 0xc39f, dargestellt wird es hier als "chr(0xc3) . chr(0x9f)" .
Oder es ist doppelt kodiert, dann müsste das 'Ã' als 2 Bytes in der Datenbank stehen - 0xc383 - und das, was 'ß' sein soll, ist dann 4 Bytes lang: 0xc383 0xc29f.
Man muss also mal so ein Feld auslesen und d byteweise entpacken (z.b. mit od).
Ist die Datenbank überhaupt in UTF-8?
0--gg-