template

Index :: PHP/MySQL :: SQL: Umgang mit Flags (Bool'schen Werten)

Wer schon einmal alte Datenbestände (etwa Text- oder dbase-Dateien) konvertieren bzw. für MySQL aufbereiten musste, hat vermutlich festgestellt, dass Bool'sche Werte früher oft in einem Textfeld untergebracht wurden, wobei z.B. für wahr das Zeichen "j", "1" oder "x" stand, während man für falsch das Zeichen "n", "0" oder Leerstring verwendete.
Dies war so üblich, weil dem DB-Designer als kleineste Einheit entweder nur ein Textzeichen zur Verfügung stand, oder weil er Wert darauf legte, die Daten in ihrer originären Form (oft pures ASCII) lesbar zu halten.
In modernen Datenbanken ist die interne Darstellung gekapselt, und keiner würde auf die Idee kommen, mit einem HexEditor Daten aus den (mittlerweile durchweg binären) Tabellendateien herauslesen zu wollen.

Da MySQL auch binäre Operatoren unterstützt, können wir auf die einzelnen Bits eines numerischen Feldes - beispielweise vom Typ tinyint - zugreifen. Abgesehen vom optimal genutzten Speicherplatz ist auch die Performance des Ausmaskierens höher als der Vergleich mit einem Textfeld.

Alle Datensätze holen, die im Feld Flags das 3. Bit gesetzt haben:
$query = "SELECT * FROM tabelle WHERE (Flags&4)";
Alle Datensätze holen, die im Feld Flags das 1. ODER 4. Bit gesetzt haben:
$query = "SELECT * FROM tabelle WHERE (Flags&9)";
Alle Datensätze holen, die im Feld Flags das 1. UND 4. Bit gesetzt haben:
$query = "SELECT * FROM tabelle WHERE (Flags&9)=9";
Alle Datensätze holen, die im Feld Flags WEDER das 1. NOCH das 2. Bit gesetzt haben:
$query = "SELECT * FROM tabelle WHERE (!(Flags&3))";
Schließlich noch ein Beispiel, wie man solche Flags liest und schreibt (Ein Datensatz in Tabelle pizza besteht aus genau einem tinyint-Feld namens Belag, dessen 8 Bits verschiedene Pizzabeläge darstellen. Der Kunde darf sich im Formular eine Kombination zusammenstellen):
Php Einleitung
if (strlen($_POST['ok'])) { // Formular verarbeiten $flags = intval($_POST['kaese']) | (intval($_POST['tomat'])<<1) | (intval($_POST['salam'])<<2) | (intval($_POST['pilze'])<<3) | (intval($_POST['schin'])<<4) | (intval($_POST['olive'])<<5) | (intval($_POST['zwieb'])<<6) | (intval($_POST['knobi'])<<7); $query = "INSERT INTO pizza SET Belag=$flags"; mysql_query($query); } else { // Voreinstellung: Standard-Pizza holen $query = "SELECT Belag FROM pizza WHERE PizzaID=1"; $row = mysql_fetch_row(mysql_query($query)); $flags = $row[0]; }
Weiter unten, im BODY, das Formular:
<form action='<?php echo basename($_SERVER['PHP_SELF']); ?>' method='post'> <input type='checkbox' name='kaese' value='1'<?php if ($flags&1) echo " checked"; ?>>K&auml;se<br> <input type='checkbox' name='tomat' value='1'<?php if ($flags&2) echo " checked"; ?>>Tomaten<br> <input type='checkbox' name='salam' value='1'<?php if ($flags&4) echo " checked"; ?>>Salami<br> <input type='checkbox' name='pilze' value='1'<?php if ($flags&8) echo " checked"; ?>>Pilze<br> <input type='checkbox' name='schin' value='1'<?php if ($flags&16) echo " checked"; ?>>Schinken<br> <input type='checkbox' name='olive' value='1'<?php if ($flags&32) echo " checked"; ?>>Oliven<br> <input type='checkbox' name='zwieb' value='1'<?php if ($flags&64) echo " checked"; ?>>Zwiebeln<br> <input type='checkbox' name='knobi' value='1'<?php if ($flags&128) echo " checked"; ?>>Knoblauch<br> <input type='submit' name='ok' value='Ok'> </form>
Alternativ können die jeweiligen Bitwerte auch gleich in den checkbox-Values stehen; die POST-Werte sind dann direkt verwendbar:
Php Einleitung
if (strlen($_POST['ok'])) { // Formular verarbeiten $flags = intval($_POST['kaese']) | intval($_POST['tomat']) | intval($_POST['salam']) | intval($_POST['pilze']) | intval($_POST['schin']) | intval($_POST['olive']) | intval($_POST['zwieb']) | intval($_POST['knobi']); $query = "INSERT INTO pizza SET Belag=$flags"; mysql_query($query); } else { // Voreinstellung: Standard-Pizza holen $query = "SELECT Belag FROM pizza WHERE PizzaID=1"; $row = mysql_fetch_row(mysql_query($query)); $flags = $row[0]; }
Weiter unten, im BODY, das Formular:
<form action='<?php echo basename($_SERVER['PHP_SELF']); ?>' method='post'> <input type='checkbox' name='kaese' value='1'<?php if ($flags&1) echo " checked"; ?>>K&auml;se<br> <input type='checkbox' name='tomat' value='2'<?php if ($flags&2) echo " checked"; ?>>Tomaten<br> <input type='checkbox' name='salam' value='4'<?php if ($flags&4) echo " checked"; ?>>Salami<br> <input type='checkbox' name='pilze' value='8'<?php if ($flags&8) echo " checked"; ?>>Pilze<br> <input type='checkbox' name='schin' value='16'<?php if ($flags&16) echo " checked"; ?>>Schinken<br> <input type='checkbox' name='olive' value='32'<?php if ($flags&32) echo " checked"; ?>>Oliven<br> <input type='checkbox' name='zwieb' value='64'<?php if ($flags&64) echo " checked"; ?>>Zwiebeln<br> <input type='checkbox' name='knobi' value='128'<?php if ($flags&128) echo " checked"; ?>>Knoblauch<br> <input type='submit' name='ok' value='Ok'> </form>
Werden im Datensatz mehr als 8 Flags benötigt, verwendet man Felder vom Typ smallint (16 Bit), mediumint (24 Bit) oder int (32 Bit). Je nach SQL-Version steht auch der Typ bigint (64 Bit) zur Verfügung. I.d.R. behilft man sich allerdings damit, mehrere int-Felder zu nutzen; so könnte man mithilfe von 4 int-Feldern bereits 128 Flags verwalten.

Hat man es mit einer noch größeren oder auch dynamischen Flag-Anzahl zu tun, d.h. man möchte in der Tabelle keine Unmenge von int-Feldern verwenden, dann käme auch die Verwendung eines Textfeldes in Betracht - vornehmlich vom varchar-Typ - wobei sich die ASCII-Werte der einzelnen Zeichen als Bit-Container zweckentfremden lassen.
Bei einem varchar-Feld der maximalen Länge 255 könnten somit 2040 Flags verwaltet werden. Das Setzen und Löschen von Bits wird seitens PHP dann folgendermaßen bewerkstelligt (das relevante Textfeld heißt hier flagstext):
//---- Wert aus Datensatz holen
$query = "SELECT flagstext FROM tabelle WHERE ... ";
$row = mysql_fetch_row(mysql_query($query));
$flags = $row[0]; // optional: String in Arbeitsvariable schreiben

if (ord($flags[46]) & 4) { ... } // 3. Bit im 47. Byte prüfen

$flags[99] = chr(ord($flags[99]) | 32); // 6. Bit im 100. Byte setzen

$flags[99] = chr(ord($flags[99]) & ~8); // 4. Bit im 100. Byte löschen

//---- Wert zurückschreiben
mysql_query("UPDATE tabelle SET flagstext='". addslashes($flags) ."' WHERE ... ");
Die Umwandlung von Zeichen zu Bytewert geschieht mittels ord(), die Umwandlung von Bytewert zu Zeichen mittels chr().
Beim Zusammensetzen des Query-String muss addslashes() verwendet werden, weil $flags einen binären Inhalt besitzt und diverse SQL-kritische Zeichen nur maskiert (mit '\' escaped) übergeben werden können.

Um auch seitens SQL in einer WHERE-Klausel einzelne Textbits zu prüfen, werden die SQL-Funktionen ORD(zeichenkette) und MID(zeichenkette, position, laenge) (bzw. SUBSTRING(zeichenkette, position, laenge)) verwendet.

Hier werden z.B. alle Datensätze selektiert, die in flagstext im 200. Zeichen das 7. Bit gesetzt haben:
$query = "SELECT * FROM tabelle WHERE (ORD(MID(flagstext,200,1))&64)";

Index :: PHP/MySQL


template