Konvertierung von ISO-8859-1 nach UTF-8

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

Hallo zusammen,

Ich habe meine ISO-8859-1 DB in eine UTF-8 DB gepumpt und das ist auch
klaglos gelaufen.

Die Tabellen-Schemen sind zumindest nominell identisch.

Nun prüfe ich ob in der UTF-8 DB die Daten identisch aus der ISO-8859-1 DB
übernommen wurden und das schaut auch ganz gut aus, bis auf folgende
Ausnahme (bis jetzt).

Ich habe in der Tabelle Aktionen ein Feld Beginn mit der Domain SC5:

Code: Alles auswählen

 CREATE DOMAIN SC5 AS CHAR(5);
Echo-Vergleichs-Ergebnis (links ISO / rechts UTF-8):
1. $Source[BEGINN]: >19:00< | . $Current[BEGINN]: >19:00 <

1. BEGINN (Char-20) Alt: (5) >19:00< | Neu: (20) >19:00###############<

Die Rauten ("#") setze ich zur Verdeutlichung mit einem str_replace(" ", "#", $Content).

Und wie man sieht, liest er aus der UTF-8 DB den selben Wert, jedoch am Ende mit
15 Blank's garniert.

Was ist da beim Pumpen falsch gelaufen ?
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Nichts. Das Problem ist CHAR statt VARCHAR.
UTF8 wird grundsätzlich mit 4-facher Anzahl Bytes erstellt, da UTF-8 ein variabler Zeichensatz von 1-4 Bytes ist.
Durch CHAR(5) werden 5 * 4 also 20 Bytes erstellt.
Du kannst also obwohl als char(5) definiert bist zu 20 Zeichen im Bereich bis x'7F' speichern. Erst Umlaute und Sonderzeichen belegen 2 Bytes sowie chinesische u.a. Zeichen 3-4 Bytes.
Durch fixe Länge wird eben der Rest mit Blanks (Leerzeichen, Space) belegt.
Allerdings kosten die keinen Platz, da das wegkomprimiert wird und Space am Ende bei DB-Vergleichen keine Rolle spielen. In deinen Programmvariablen kann sich das leider fatal auswirken, da hier die Space am Ende bei Vergleichen mitzählen.
Daher ist es immer besser varchar statt char zu verwenden und beim Speichern in der DB Trim() zu verwenden.

Allerdings ist dies eine Eigenheit der Firebird dass die Anzahl Zeichen (statt Bytes) nicht geprüft und verwendet wird, sondern das maximal mögliche.

Ich hatte da mal den Fall in Oracle da muss beim NCHAR die Anzahl Bytes und nicht Zeichen angegeben werden. Somit war ein Feld "Lager nchar(2)" auch nur 2 Bytes groß. Da ließ sich dann der Wert "Ü1" leider nicht unterbringen, da dieser 3-Bytes benötigte.

Bei anderen DB's wird nchar/nvarchar i.d.r. als UTF16 (2/4-Byte-Code) gespeichert. Da ist jedes Feld N*2-Bytes groß, da in den seltensten Fällen Zeichen oberhalb x'7FFF' verwendet werden.
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Guten Morgen bfuerchau,

danke für die sehr nachvollziehbar Erläuterung. Habe ich verstanden.

Was ich allerdings nicht so recht nachvollziehen kann ist, dass die
FireBird-Leute es nicht auf die Kette bekommen, dieses meiner
Meinung nach recht triviale Problem, korrekt zu handeln.

Wo ist denn das Problem bei CHAR definierten Feldern nur soviele
Zeichen, wie die Definierung vorgiebt, unabhängig davon wieviele
reale Bytes man für die reale Speicherung technisch braucht ?

Wenn das evtl. zuviel Erwartungshaltung gegenüber den FB-Leuten
sein sollte, dann wäre es doch konsequenter den Typ CHAR abzu-
schaffen, statt ungewollte Blanks auszuliefern, welche die Logik
von Programmen torpediert.

In meinem Beipiel scheitert künftig jede simple IF-Abfrage auf
19:00 und das im Jahr 2022. Das ist echt ärgerlich.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Hallo zusammen,

tatsächlich kommt es darauf an, wie du die Abfragen durchführst. Datenbankseitig solltest du "=" zum Vergleich verwenden. Hierbei werden nachführende Leerzeichen nicht berücksichtigt. Anders ist das mit "LIKE". Hier müsste ein Vergleich z.B. mit TRIM() erfolgen.

Code: Alles auswählen

CREATE TABLE t1 ( c1 CHAR(5) );
COMMIT;

INSERT INTO t1 VALUES ('abcd');

-- Gibt eine Zeile aus: --
SELECT * FROM t1 WHERE c1 = 'abcd';

-- Gibt keine Zeile aus: --
SELECT * FROM t1 WHERE c1 LIKE 'abcd';

-- Gibt eine Zeile aus: --
SELECT * FROM t1 WHERE TRIM(c1) LIKE 'abcd';
Das Verhalten hat auch weniger mit den FB-Leuten zu tun als vielmehr mit dem SQL-Standard. Dieser ist hier leider nicht klar definiert. Die Entwickler haben sich an die grundsätzliche Aussage des Standards gehalten und auch so implementiert. Jede DB macht das allerdings anders.

Das Problem wurde bereits diskutiert: https://github.com/FirebirdSQL/firebird/issues/4602

Gruß
Martin
Martin Köditz
it & synergy GmbH
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Martin,

nach meiner Einschätzung sind doch die entstehenden Probleme auf der Ebene von SQL
für einen Entwickler, der seinen Fokus im Coden einer Applikation hat, von sekundärer
Natur.

Das DB-Spezies das evtl. entspannter betrachten und auf ihrer Ebene das auch zu lösen
wissen, glaube ich gern.

Bei mir entstehen die Probleme in PHP und bei anderen wohl auch in Delphi, C++ usw.

Natürlich kann man auch dort als Krücke trimmen, was aber den Code nur unnötig
aufbläht und langsamer macht, auch wenn es wohl nur Nano-Sekunden sind.

Spezifikationen hin oder her, für mich macht es keinen Sinn, dass sich eine DB nicht
bei allen Character-Sets identisch verhält.

Und selbst wenn es sinnvolle Gründe gäbe, glaube ich kaum, dass diese für den Otto-
Normal-DB-User von Bedeutung wären, sondern nur für eine Minderheit.

Und wenn die FB-Leute partout davon nicht lassen wollen, dann sollte das meines Erachtens
konfigurierbar sein. Dann wäre allen gedient, den Exoten und den Normalos.

Dazu kommt, dass das Trimmen den Nachteil mit sich bringt, dass gewollte bzw.
sachlich erforderliche Blanks am Anfang bzw. Ende der Daten keine Rolle mehr spielen und
da fallen mir ein ganzer Batzen Argumente ein, weshalb das doof ist, z.B. beim
Verarbeiten von mehrzeiligen Verwendungszwecken in DTAUS- bzw. SEPA-Daten im
Zahlungsverkehr. Da wird mit festen Satzlängen operiert und zwar zwingend, auch nach
Spezifikation.

Nur eine, halt meine Meinung.

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

Hallo Martin,

ein gutes Beispiel ist die von Dir ver-linkte Diskussion.

Da sitzen nur reine DB-Spezies in der Runde. Sie diskutieren das Thema
ausschliesslich auf der technischen Ebene.

Da fällt nicht ein Argument was diese technische Entscheidung für Konsequenzen
auf Daten aus dem wirklichen Leben hat und für die Programmierer, die diese
Daten verarbeiten sollen.

In der ganzen Diskussion wird nicht einmal Bezug auf das Verhalten genommen,
worin der "nachvollziebare" Sinn liegen kann, dass eine Abfrage:

Code: Alles auswählen

if (BEGINN = "19:00")  {
    do something.
}
bei einem ISO-88901-Feld funktioniert und bei einem UTF8-Feld halt nicht,
weil bei UTF8 der Wert "19:00################" ausgeliefert wird !

Es ist auch kein einziges Argument dabei, dass es wertvoll ist das CHAR 5 in
einem ISO-Feld IMMER 5 Zeichen stehen und in einem UTF8 irgendetwas
zwischen 5 - 20 Zeichen, je nachdem welche gespeichert wurden.

Im Deutschen gleicht das einer Lotterie und genau da liegt wohl das Problem,
dass solche Diskussionen primär von Leuten mit anglo-amerikanischen
Migrations-Hintergrund geführt werden.

Ich kann da, wie gesagt, keinen Sinn erkennen.

Gruss
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Die Entwickler beziehen sicher auf den SQL-Standard. Dabei ist es zunächst einmal unwichtig, ob es für den Endanwender Sinn ergibt oder nicht. Die Mehrzahl der aufgelisteten DBs arbeiten identisch zu Firebird. Von daher würde ich sagen: technisch korrekt, wenig Nutzen für den Endanwender.

Natürlich ist das für den Programmierer ärgerlich. Ich bin mir auch sicher, dass sich wieder einige Leute ärgern würden, wenn sich das Verhalten nun ändert. ;)

Vielmehr würde ich mir wünschen, dass dies besser in der Doku kommuniziert würde.

Gruß
Martin
Martin Köditz
it & synergy GmbH
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Das wa schon immer unterschiedlich zwischen Programm und Datenbankwelt.
In der DB ist das festgelegt, dass bei Vergleichen (außer like) eben Trainling Blanks nicht berücksichtigt werden sollen.
Das resultiert aus der Geschichte in den 60ern, als es noch Nicht-SQL-DB's gab (VSAM, ISAM) und nur die IBM-Programmiersprachen COBOL und RPG vorherrschten.
Auch bei diesen Sprachen ist es bei Zeichenkettenvergleichen eben normal, dass Trainling Blanks keine Rolle spielen. Somit war die Gleichheit zwischen DB und Programm gewährleistet.
SQL-DB's tauchten dann ende der 60er/70er auf und die mussten eben COBOL/RPG kompatibel sein und das wurde im SQL-Standard festgelegt.
Mit Einzug der heute bekannten Sprachen, zuerst C und C++, kamen die variablen Strings hinzu, so dass der Typ VARCHAR in den DB's Einzug hielt.
VARCHAR ist nun eben mit den Programmiersprachen mehr kompatibel, solange man die Daten beim Speichern trimmt.
De Typ CHAR ist für die ganzen neuen Sprachen eigentlich nicht mehr zu verwenden, da es bei den Vergleichen zwischen SQL und Programmen eben bzgl. der Trainling Blanks eben immer noch Unterschiede gibt.

Du darfst dich halt nicht darüber beschweren, dass du CHAR-Variablen nimmst statt die bessere Alternative für die Programmiersprache VARCHAR zu nehmen. Ürigens passt die Prüfung beim Schreiben von CHAR bzgl. der Länge auf jeden Fall.

Für unsere APP-Datenbanken sind CHAR-Variablen verboten;-).
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Martin,

keine Frage, kann man so sehen.
Von daher würde ich sagen: technisch korrekt, wenig Nutzen für den Endanwender.
Mich würde brennend interessieren welchen "realen" Nutzen / Sinn diese Technik denn für alle Nicht-End-Anwender in der Praxis hat, ausser einem SQL-Standard zu entsprechen, dessen Macher scheinbar
ihre Arbeit nicht unter der Betrachtung von Sinn bzw. Nutzen ihres Standards verrichten.

Hast Du mal ein Beispiel, wo diese Technik einen Sinn bzw. Nutzen stiftet ?

Ich wäre recht zuversichtlich, dass viele Anwender es echt gut finden würden, wenn man das jetzt ändert und es nur Wenige bis Niemanden geben würde, die sich ärgern, weil sie es ja nicht merken würden, da sie ja trimmen müssen, um korrekt "abfragen" zu können.

Und wie gesagt, könnte man das Verhalten über die Firebird.conf konfigurierbar machen und
somit wäre beiden Lagern geholfen.

Gemäß Deinem Link, wurde die Diskussion zuletzt in 2013 geführt. In 40 Tagen haben wir 2023.

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

Hallo bfuerchau,

ja, wenn ich die für mich sinn- / nutzen-freie Diskussion der FB-Macher betrachte und deren
heiliger SQL-Standard laut Martin wohl in Beton gegossen scheint, ist die einzig sinn-volle Konsequenz
Wohl CHAR-Felder aus seiner DB zu verbannen.

Insofern war es echt gut dieser Problematik mit Eurer Hilfe mal auf den Grund gegangen zu sein
und als Folge daraus in meinem Konvertierungs-Script automatisch dafür zu sorgen, dass alle CHAR-Felder nach VARCHAR konvertiert werden.

Ob ich beim Speichern auch Trimmen werde, weiss ich noch nicht, da ich einige Daten habe,
z.B. auf jeden Fall DTAUS und evtl. SEPA, die gemäß Standard zwingend zumindest
am Ende Blanks haben.

Da muss ich mal das eine oder andere erst testen.

Danke und Gruss
Antworten