Webアプリケーションでのビットフラグ

どうもお久しぶりです。

データ容量やメモリ容量を抑える為に、ビットフラグで情報を管理するのは、常套手段ですよね。

PHPではenumがないので(OrbitEnumは非推奨ですしね)、defineかハッシュを使います。
すなわち、

<?php
  define("STATUS_01", 0x00000001); ///< ステータス1フラグ
  define("STATUS_02", 0x00000002); ///< ステータス2フラグ
  define("STATUS_03", 0x00000004); ///< ステータス3フラグ
  define("STATUS_04", 0x00000008); ///< ステータス4フラグ

  $status = 0;

  // ステータス1フラグを設定
  $status |= STATUS_01;

  // ステータス2フラグを解除
  $status &= ~STATUS_02;

  // ステータス3フラグが立っていたら
  if (0 != (STATUS_03 & $status))
  {
     echo("立った!(゚∀゚)\n");
  }

か、

<?php
  /// ステータスビット列挙
  $enSTATUS = array(
     "STATUS_01" => 0x00000001  ///< ステータス1フラグ
    ,"STATUS_02" => 0x00000002  ///< ステータス2フラグ
    ,"STATUS_03" => 0x00000004  ///< ステータス3フラグ
    ,"STATUS_04" => 0x00000008  ///< ステータス4フラグ
  );

  $status = 0;

  // ステータス1フラグを設定
  $status |= $enSTATUS['STATUS_01'];

  // ステータス2フラグを解除
  $status &= ~$enSTATUS['STATUS_02'];

  // ステータス3フラグが立っていたら
  if (0 != ($enSTATUS['STATUS_03'] & $status))
  {
     echo("立った!(゚∀゚)\n");
  }

と言った具合に。
このビットフラグのステータスをDBに保存する場合、いちいちビットで分解して保存するのはコストがかかるので、そのまま保存します。
その後、ステータスの状態に関わらず、ビットフラグを設定したり解除したいケースは、ままあります。
削除フラグを立てる、だとか。

そうした場合、SELECTしてビットフラグを設定してUPDATEするのはコストがかかります。
そこで、SQLでビット演算をしてしまえば、コストは抑えられます。

具体的には、

mysql> -- フラグを設定
mysql> UPDATE user SET status = status |  0x00000001 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> -- フラグを解除
mysql> UPDATE user SET status = status & ~0x00000002 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> -- フラグを反転
mysql> UPDATE user SET status = status ^  0x00000004 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

このように行います。
うちのMySQL環境ではビット演算は64bitで計算されているようです。

mysql> SELECT ~0;
+----------------------+
| ~0                   |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)

ちなみに、CONV関数やBIN関数、HEX関数を用いることで、基数を変更して出力することができてデバッグも便利です。

mysql> -- 8進数で出力
mysql> SELECT CONV(~0, 10, 8);
+------------------------+
| CONV(~0, 10, 8)        |
+------------------------+
| 1777777777777777777777 |
+------------------------+
1 row in set (0.00 sec)

mysql> -- 2進数で出力
mysql> SELECT BIN(~0);
+------------------------------------------------------------------+
| BIN(~0)                                                          |
+------------------------------------------------------------------+
| 1111111111111111111111111111111111111111111111111111111111111111 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> -- 16進数で出力
mysql> SELECT HEX(~0);
+------------------+
| HEX(~0)          |
+------------------+
| FFFFFFFFFFFFFFFF |
+------------------+
1 row in set (0.00 sec)

追記

抽出のことを忘れていました。
ビットフラグを用いて、抽出も行えます。

mysql> -- フラグが降りているユーザを抽出
mysql> SELECT * FROM user WHERE 0 = status & 0x00000004;
+----+--------+--------+
| id | name   | status |
+----+--------+--------+
|  1 | test   |      0 |
|  2 | tanaka |      8 |
+----+--------+--------+
2 rows in set (0.01 sec)

mysql> -- フラグが立っているユーザを抽出
mysql> SELECT * FROM user WHERE 0 <> status & 0x00000004;
+----+---------+--------+
| id | name    | status |
+----+---------+--------+
|  3 | teraken |     14 |
+----+---------+--------+
1 row in set (0.00 sec)

当たり前ですが、複数のビットフラグを|結合しても、抽出できます。