Sortierung von mysql ist anders als die Sortierung von bash

Um schnell prüfen zu können, ob ein Element in einer Datenbank-Tabelle enthalten ist, ohne dafür jedesmal die Datenbank abzufragen, wollte ich diese eine Tabelle, deren Inhalt ich prüfen wollte, in einem bash-Array ablegen.

Die Datenbank-Tabelle hat folgenden Inhalt (neben der ID):

AR
ÄR
ZZ
XÉ
XÉ F
XE, J
XÉ; P
XÉ; S

Übernehmen kann man den Inhalt der mysql-Tabelle in einem bash-Script eigentlich ganz leicht:

while read line
do
  tablearray+=("${line}")
done <<(${mysqlcmd} -Bse "select name from table order by name")

Um nun auch schnell prüfen zu können, ob ein Element bereits in dem Array enthalten ist, wollte ich ungern alle Elemente durchlaufen. Stattdessen lasse ich bereits mysql die Daten sortieren. Im bash-Script selbst „suche“ ich dann über einen Binären-Suchbaum-Ansatz innerhalb des Bash-Arrays:

function contains()
{
  declare -a haystack=("${!2}")
  local needle=${1}
  local low=0
  local lowold=0
  local high=${#haystack[@]}
  local highold=${#haystack[@]}
  let "pos = high / 2"
  while true
  do
    if [[ "${haystack[${pos}]}" == "${needle}" ]]; then
      # Found
      return 0
    fi
    if [ "${haystack[${pos}]}" \> "${needle}" ]; then
      let "high = pos"
      let "pos = low + ( high - low ) / 2"
    else
      let "low = pos"
      let "pos = low + ( high - low ) / 2"
    fi
    if [ ${highold} -eq ${high} ]; then
      if [ ${lowold} -eq ${low} ]; then
        # Not found
        return 1
      fi
    fi
    let "lowold = low"
    let "highold = high"
  done
}

Aufrufen kann man diese Funktion bequem innerhalb des Scripts mit:

  if contains "SearchString" tablearray[@] ; then

Nebeneffekt hierbei ist allerdings, dass lokal eine Kopie des Arrays angelegt wird. Wer das nicht möchte, muss den Teil declare -a haystack=("${!2}") auslassen und statt $haystack immer auf den Original-Array verweisen. Das führt auch leider dazu, dass man für jedes Array eine eigene Funktion braucht…

Bei einigen Suchen konnte das Element nicht gefunden werden (obwohl es im Array enthalten war) und – noch schlimmer – das contains() lief in einer Endlos-Schleife weiter. Nach kurzer Analyse war klar, dass die Sortierung von bash eine andere ist als das, was mysql verwendet.

Ich hatte schon vermutet, dass es wahrscheinlich an den CHARSET oder COLLATE bzw. COLLATION von mysql liegt, und habe mir daher ein bash-Script geschrieben, welches prüft, welche Kombinationen genauso sortieren wie bash. Wer Interesse hat, kann sich das Script gerne herunterladen: mysql_collate.sh

Das Skript überprüft dabei zwei Dinge gleichzeitig:

  1. Sind die Sonderzeichen korrekt in die Datenbank eingespeichert worden und können auch korrekt wieder ausgelesen werden?
  2. Ist die Sortierung von bash mit der mysql-„ORDER BY“ Klausel kompatibel?

Auf meinem System:

$ mysql --version
mysql  Ver 14.14 Distrib 5.5.32, for debian-linux-gnu (x86_64) using readline 6.2

Erhalte ich folgende gültige Kombinationen:

dec8 / dec8_bin
cp850 / cp850_bin
hp8 / hp8_english_ci
hp8 / hp8_bin
latin1 / latin1_bin
latin2 / latin2_bin
ujis / ujis_japanese_ci
ujis / ujis_bin
cp1250 / cp1250_bin
latin5 / latin5_bin
utf8 / utf8_bin
utf8 / utf8_icelandic_ci
ucs2 / ucs2_bin
ucs2 / ucs2_icelandic_ci
keybcs2 / keybcs2_bin
macce / macce_bin
macroman / macroman_bin
cp852 / cp852_bin
latin7 / latin7_bin
utf8mb4 / utf8mb4_icelandic_ci
utf16 / utf16_icelandic_ci
cp1257 / cp1257_bin
utf32 / utf32_bin
utf32 / utf32_icelandic_ci
eucjpms / eucjpms_japanese_ci
eucjpms / eucjpms_bin

Ein wenig unangenehm ist mir das schon, aber ich arbeite nun mit einem binären Ansatz und habe für meine Zwecke latin1 / latin1_bin ausgewählt.

Schreibe einen Kommentar