Bitmaskierung

Wenn man die binären Zustände vieler Felder ablegen möchte, oder entsprechende Felder einer Fremdsoftware auswerten möchte, bietet sich die Bitmaskierung an.

...

SQL Server bietet auch die Möglichkeit einzelne Felder vom Datentyp Bit zu deklarieren und verwaltet diese Felder platzsparend im Hintergrund für uns, aber hier erhält jedes Feld einen eigenen Namen, was nicht immer für die Programmierung von Vorteil ist. In diesem Artikel geht es also um die Verwaltung von Bits, wie sie z. B. in einem INTEGER-Wert repräsentiert werden.

Grundlagen

Zahlen werden in der EDV im Dualsystem als Folge von Bits definiert. Hier findet man noch einmal die Hintergründe dazu!
Für die Darstellung der binären Zustände (z. B. aktiv/nicht aktiv) in Bezug auf die 7 Wochentage werden also 7 Bit benötigt. Jedes einzelne Bit steht für einen Wochentag, wobei das am weitesten rechts stehende Bit hier als Bit 1 bezeichnet wird.

  • Bit 1 für Sonntag
  • Bit 2 für Montag
  • Bit 3 für Dienstag
  • ...
  • Bit 7 für Samstag

Die Bitfolge 100 0101 entspricht also der Aktivierung der Bits für Tag 1, Tag 3 und Tag 7 der Woche. Die Umrechnung ins Dezimalsystem ergibt den Wert:
1 + 4 + 64 = 69
der uns aber in Bezug auf die codierten Zustände nicht weiterbringt.

Möglichkeiten der Umwandlung

Hier bieten sich verschiedene Wege an:

  • Bit-Maskierung mit AND
  • Iterative Berechnung

Beide Varianten habe ihre Vor- und Nachteile, wie die folgenden Beispiele zeigen.

Bit-Maskierung mit AND

Die erste Variante bedient sich der booleschen Algebra und verwendet den Ansatz, dass der Test, ob ein Bit gesetzt ist über eine UND-Verknüpfung eines Wertes mit dem entsprechenden Bit erfolgen kann. Im Dezimalsystem geschrieben lautet der Test:
69 AND 4
im Dualsystem sieht es dann so aus:

    100 0101
AND 000 0100
------------
         100

was dem Dezimalwert 4 entspricht. Aber eigentlich wollten wir ja die Information erhalten, dass das dritte Bit gesetzt ist. Das muss nun explizit im SQL erfolgen, wobei wir die Abfrage vereinfachen. Wenn die Maskierung mit dem betreffenden Bit einen Wert ungleich 0 ergibt, dann war das Bit gesetzt. Die boolesche Algebra wird im T-SQL durch das Zeichen & anstelle des AND durchgeführt:

CASE WHEN s.DaysOfWeek & 1 <> 0 THEN 'Sunday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 2 <> 0 THEN 'Monday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 4 <> 0 THEN 'Tuesday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 8 <> 0 THEN 'Wednesday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 16 <> 0 THEN 'Thursday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 32 <> 0 THEN 'Friday, ' ELSE '' END +
CASE WHEN s.DaysOfWeek & 64 <> 0 THEN 'Saturday, ' ELSE '' END AS Days_of_Week_Desc

Das Ergebnis dieser Abfrage wäre dann bei dem Wert 69 für s.DaysOfWeek die Zeichenfolge:
Sunday, Tuesday, Saturday,

Iterative Berechnung

Beginnend von der höchsten zu erwartenden Potenz aus, prüfen wir, ob der Wert größer ist als der Vergleichswert. Ist dies der Fall, dann war also das Bit an der Position gesetzt, welches der nächsthöheren Potenz entspricht. Das gefundene Bit wird vom Wert abgezogen und der Vergleich geht weiter, bis wir bei der Potenz 0 ankommen.

Hiermit zerlegen wir den Wert 100 0101 in die Zahlen 2^6 + 2^2 + 2^0. Die Bitpositionen entsprechen der Potenz + 1 also die Werte 7, 3, 1.

Damit das Ergebnis schöner aussieht, reihen wir die gefundenen Bitpositionen vor das bisherige Ergebnis, so dass am Ende folgender Wert ausgegeben wird: 1, 3, 7.

Eine Umsetzung in sprechende Namen wie im ersten Beispiel ist hier erst mal nicht vorgesehen.

Iterative Berechnung mit Reihen

Falls wir größere Bereiche abbilden wollen, wie z. B. die Tage eines Monats 1..31, so wäre es durchaus wünschenswert, diese als Reihe dargestellt zu bekommen, falls diese Werte lückenlos besetzt sind. Die Darstellung als:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
ist wenig übersichtlich, insbesondere wenn einzelne Werte fehlen sollten, sehr fehleranfällig. Wünschenswert ist hier die Darstellung:
1-31

Daher habe ich hier die folgende Vorgehensweise gewählt:

  • Speichere den ersten Wert (inklusive Bitposition) als möglichen Anfang einer Serie ab
  • Falls eine Lücke in den Bitpositionen auftritt und es vorher eine Serie gab (Differenz der Bitpositionen ist größer als 1), dann verbinde diese Werte mit einem Minus und hänge den aktuellen Wert mit einem Komma an. (z. B. 1-4, 6) Ansonsten verbinde diese Werte mit einem Komma und hänge den aktuellen Wert mit einem Komma an. (z. B. 1, 2, 4)
  • Falls es vor dem aktuellen Wert keine Serie gab, und der letzte gefundene Wert also der Anfang einer möglichen Serie war, dann hänge einfach den vorher gefundenen Wert mit einem Komma an und den aktuellen Wert ohne Trennzeichen. (z. B. 1, 5)
  • Nach dem Schleifenende noch einmal alle Abfragen für das letzte Ergebnis wiederholen

Code Beispiele

Ich habe diesem Artikel zwei Code-Beispiele angefügt, die für die beiden iterativen Varianten jeweils eine Funktion anlegen. Es empfiehlt sich eine Datenbank für administrative Tools und Skripte anzulegen, die unabhängig von irgendwelchen Anwendungen existieren kann.

Der dritte Code-Teil zeigt die Anwendung der Bit-Maskierung mit AND.

Was man dann mit diesen Funktionen z. B. bei der Auswertung der Reporting-Services erreichen kann, werde ich in einem meiner nächsten blog-Einträge beschreiben.

  • Holger Schmeling
    Kommentar von: Holger Schmeling
    04.05.12 @ 14:31:44

    Clever.
    Eine Warnung vermisse ich aber leider und ergänze sie deshalb.
    So verlockend das nämlich auf den ersten Blick erscheinen mag, handelt es sich doch letztlich um eine Verletzung der ersten Normalform, die ja besagt, dass jedes Attribut atomar sein soll.
    In der Regel ist es keine gute Idee, diese Richtlinie zu verletzen.
    Wenn man so etwas macht, dann ist die Verwendung von Funktionen zur Zerlegung der Information in die atomaren Bestandteile ein natürlicher Ansatz, der ebenfalls nicht ganz frei von Problemen ist. Bevor man sich versieht, stehen diese Funktionsaufrufe dann in Prädikaten. Der Optimierer hat dann keinerlei Möglichkeit, eine vernünftige Kardinalitätschätzung durchzuführen und der Ausführungsplan ist in der Regel suboptimal. Eine Index auf so einer Bit-codierten Spalte ist ebenfalls wenig sinnvoll.
    Die Verwendung von benutzerdefinierten Funktionen in der SELECT-Liste ist ein weiterer Performance-Killer.
    Also bitte mit Vorsicht verwenden.

  • Kommentar von: Christoph Muthmann
    04.05.12 @ 14:40:34

    Vielen Dank für den Hinweis. Ich war auf umgekehrtem Wege zu dem Thema gekommen, da ich die Abonnements vom SSRS auswerten wollte.
    Es gibt immer wieder Hersteller, die auf solchem Wege ihre Informationen codieren, bzw. verschleiern. Microsoft gehört bei den Reporting Services leider auch in diese Kategorie.

Einen Kommentar hinterlassen

Ihre E-Mail-Adresse wird nicht auf dieser Seite angezeigt.
SchlechtExzellent
(Zeilenumbrüche werden zu <br />)
(For my next comment on this site)
(Allow users to contact me through a message form -- Your email will not be revealed!)
Dies ist ein Captcha Bild. Es wird benutzt, um Massenzugriffe von Robotern zu verhindern.
Bitte gib die Zeichen des obigen Bildes ein. (Groß/Kleinschreibung ist wichtig)
Trackback-Adresse für diesen Eintrag
Dies ist ein Captcha Bild. Es wird benutzt, um Massenzugriffe von Robotern zu verhindern.
Bitte gib die Zeichen des obigen Bildes ein. (Groß/Kleinschreibung ist wichtig)
  • Christoph Muthmann
    Trackback von: Christoph Muthmann
    17.04.12 @ 12:17:39

    Alle Abonnements anzeigen
    So schön ja auch die Möglichkeit ist, einzelne Abonnements einzurichten, so schwierig ist es, den Überblick zu behalten, wenn z. B. Teile eines Reportservers umziehen sollen. …