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.

  Bitmaskierung AND
  Bitmaskierung Plain Function
  Bitmaskierung Serien Function