Monthly Archives: 11月 2010

MySQL5.1のboolean型を検証

このエントリーをはてなブックマークに追加
はてなブックマーク - MySQL5.1のboolean型を検証
Share on Facebook

MySQLにてBOOLEAN型のフィールドを作りたいと思ったのですが、Googleで調べると「MySQLのBOOLEAN型は実質tinyint(1)と同じ」といったことが書いてありました。 また、「MySQLで厳密なBOOLEAN型を表現したければ、ENUM(‘TRUE’, ‘FALSE’) を使った方がいい」とも書いてありました。

個人的にはSQL文に「WHERE flag IS TRUE」と書いてSELECTできれば文句ありません。そういった挙動ができるのか確認しました。

実験した環境のMySQLバージョンは5.1.41です。

データはどのように格納されるのか?

以下のように、連番とブール値を対に持つテーブルを作りました。

CREATE TABLE boolean_test (
    `id` INT NOT NULL AUTO_INCREMENT,
    `flag` BOOLEAN,
    PRIMARY KEY (`id`)
);

テストデータは下記の通りです。ブーリアン(TRUE/FALSE)のほか、整数値やNULLでも実験します。

INSERT INTO boolean_test VALUES (1, TRUE);
INSERT INTO boolean_test VALUES (2, FALSE);
INSERT INTO boolean_test VALUES (3, 1);
INSERT INTO boolean_test VALUES (4, 0);
INSERT INTO boolean_test VALUES (5, NULL);
INSERT INTO boolean_test VALUES (6, 127);
INSERT INTO boolean_test VALUES (7, 128);
INSERT INTO boolean_test VALUES (8, 2147483647); -- 4 byteの最大値
INSERT INTO boolean_test VALUES (9, 2147483648); -- 4 byteの最大値+1

作成結果は下のようになりました。

mysql> SELECT * FROM boolean_test;
+----+------+
| id | flag |
+----+------+
|  1 |    1 |
|  2 |    0 |
|  3 |    1 |
|  4 |    0 |
|  5 | NULL |
|  6 |  127 |
|  7 |  127 |
|  8 |  127 |
|  9 |  127 |
+----+------+
9 rows in set (0.00 sec)

この時点で、TRUEは1として、FALSEは0として格納されています。 また、どんなに大きな値を入れても127で止まっています。 確かにMySQL内部でtinyint(1)として管理しているのがわかります。

実際、テーブルをDESCRIBEしてると、型はtinyint(1)と表示されます。

mysql> DESCRIBE boolean_test;
+-------+------------+------+-----+---------+----------------+
| Field | Type       | Null | Key | Default | Extra          |
+-------+------------+------+-----+---------+----------------+
| id    | int(11)    | NO   | PRI | NULL    | auto_increment |
| flag  | tinyint(1) | YES  |     | NULL    |                |
+-------+------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

BOOLEAN型としてSELECTできるか?

条件を「=」で指定してみる

SELECT * FROM boolean_test WHERE flag = TRUE;  -- id:1, 3 がヒット
SELECT * FROM boolean_test WHERE flag = FALSE; -- id:2, 4 がヒット
SELECT * FROM boolean_test WHERE flag = 1;     -- id:1, 3 がヒット
SELECT * FROM boolean_test WHERE flag = 0;     -- id:2, 4 がヒット

「TRUE = 1」「FALSE = 0」と扱っているようです。その証拠に、値が127のレコードは「flag = TRUE」でもヒットしません。

「flag = TRUE」という条件は、「flag = 1」と同値。

条件を「IS」で指定してみる

SELECT * FROM boolean_test WHERE flag IS TRUE;  -- id:1, 3, 6, 7, 8, 9 がヒット
SELECT * FROM boolean_test WHERE flag IS FALSE; -- id:2, 4 がヒット

0より大きいものはTRUEとして判別される模様。

「flag = TRUE」と「flag IS TRUE」は全く別の結果を返す。

UNKNOWNはどういう扱いになる?

SQL-99の定義によると、「BOOLEAN型はTRUE/FALSE/UNKNOWNの3値で表現される」ようです。 MySQLではどういう風に呼び出せるのか確かめてみました。

SELECT * FROM boolean_test WHERE flag IS UNKNOWN;  -- id: 5 がヒット
SELECT * FROM boolean_test WHERE flag = UNKNOWN;   -- NG: #1054 - Unknown column 'UNKNOWN' in 'where clause'
SELECT * FROM boolean_test WHERE flag = 'UNKNOWN'; -- ???: id:2, 4 がヒット

NULLのフィールドがUNKNOWNとして表現されているようです。 また、「= UNKNOWN」と指定するとクエリが失敗しました。「IS UNKNOWN」が正しいようです。

よくわからないのが3つめの例。id:2, 4はFALSEのレコードなので、全くヒットしてほしくないのですが。 このあたりはMySQLの仕様をよく確認しないとわかりませんが、まず入力しないだろうということで、この際無視します。

考察

やはりMySQLのBOOLEAN型はtinyint(1)と同じようです。 Boolean型と思ってクエリを投げると意図しない結果が返ってきてしまう危険性があるので、注意が必要です。

また、ENUM型は検証していませんが、SQL標準(SQL99)ではサポートされていないため、プログラムの互換性を考えると積極的に使う気が起きません。

MySQLでBoolean型を表したければ、少し気持ち悪いですがtinyint(1)で表現した方が良いようです。

参考

クラス定数を動的に指定する

このエントリーをはてなブックマークに追加
はてなブックマーク - クラス定数を動的に指定する
Share on Facebook

可変変数を使えば、動的に変数名や関数名を指定できます。 簡単な例だと、下のようなことができます。

$a = 'hello';
${$a} = ' world!';  // $hello = ' world!'; と同義になる
echo $a . $hello;   // 'hello world!' と出力される

で、これを使ってconstで指定したクラス定数を動的に読み込みたかったのですが、なかなかうまく行きませんでした。

理由は、そもそもクラス定数であって変数ではないからでしょうか。

とりあえず、constant(定数名)関数を使うことでうまくクラス定数を指定できました。

使用例

なんでこんな記事を書いたかというと、開発環境と本番環境のDBを簡単に接続しわけるクラスを作りたかったからです。 以下のクラスをrequireして、「MyConnections::get_connection(“service”)」と呼べば本番環境に接続したPDOオブジェクトを取得できます。

<?php 
/**
 * DBに接続するクラス
 */

class MyConnections
{
    /** @var 本番環境の設定 */
    const SERVICE_DSN      = "mysql:host=servicedb; port=3306; dbname=uso_db";
    const SERVICE_USERNAME = "uso";
    const SERVICE_PASSWORD = "800";
    
    /** @var ローカル開発環境の設定 */
    const LOCAL_DSN      = "mysql:host=localhost; port=3306; dbname=uso_db;";
    const LOCAL_USERNAME = "root";
    const LOCAL_PASSWORD = "";
    
    /**
     * DBに接続し、PDOオブジェクトを返す
     *
     * @param  string  $target  接続先 (service|local)
     * @return PDO
     */
    public static function get_connections($target)
    {
        $env = strtoupper($target);
        
        $dsn      = constant("self::" . "{$env}_DSN");
        $username = constant("self::" . "{$env}_USERNAME");
        $password = constant("self::" . "{$env}_PASSWORD");
        
        try {
            return new pdo($dsn, $username, $password);
        } catch (PDOException $e) {
            throw $e;
        }
    }
}
?>

参考

2個以上のフィールドを結合してdisplayFieldに表示するには

このエントリーをはてなブックマークに追加
はてなブックマーク - 2個以上のフィールドを結合してdisplayFieldに表示するには
Share on Facebook

displayFieldに2個以上のフィールドを結合して出したいことがあります。例えば、「姓」「名」フィールドを繋げるケース。

そんなときは、モデル内で$virtualFieldsを指定してやれば良いです。

使い方

例えば、$displayFieldに「姓(family_name)」と「名(given_name)」を繋げて表示したい場合は、下のようにします。

<?php
class User extends AppModel {
    var $name = 'User';
    var $virtualFields = array('full_name' => 'CONCAT(User.family_name , " ", User.given_name)');
    var $displayField = 'full_name';
    var $order = 'User.id ASC';
?>

注意

  • $virtualFields は CakePHP 1.3 から導入されたらしいので、それ以前のバージョンでは使えません。
  • 紛らわしいですが、「virtualFields」(複数形)です。「displayField」(単数形)と混同しないように。
  • 上記はMySQLでの例です(CONCAT関数で文字列連結している)。PostgreSQLなどでは違うコマンドになるので、各RDBMSのマニュアルを参照してください。

参考

CakePHPでいわれのない「Missing database table」エラーを直すには

このエントリーをはてなブックマークに追加
はてなブックマーク - CakePHPでいわれのない「Missing database table」エラーを直すには
Share on Facebook

モデルの名前を修正して、再度Bakeしようとすると以下のようなエラーが。

Error: Missing database table 'people' for model 'Person'

「people」テーブルはさっきDBから消したので、存在していません。いったい何を見てエラーを出しているのか。

Googleでエラーメッセージを検索すると、「/app/tmp/cache/配下のキャッシュを消せば直る」という記事が。さっそく消して見るも、やはり同じエラーが出ました。

思い切って /app/models配下にあるモデルをすべて消したところ、無事bakeができるようになりました。

作成済みのモデルも見てリレーションを判別していたのでしょうか?何という想定外な仕様。

XAMPPのMySQLで正しくUTF-8を使えるようにする

このエントリーをはてなブックマークに追加
はてなブックマーク - XAMPPのMySQLで正しくUTF-8を使えるようにする
Share on Facebook

XAMPP 1.7.3 に入っているMySQLは、デフォルト設定だと文字コード指定が適当なので、普通に使っていると文字化けすることがあります。

コマンドプロンプトから下記を実行すると現在の文字コード設定が確認できます。

[c:\]$ c:\xampp\mysql\bin\mysql -uroot

mysql> SHOW variables LIKE '%char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | latin1                         |
| character_set_connection | latin1                         |
| character_set_database   | latin1                         |
| character_set_filesystem | binary                         |
| character_set_results    | latin1                         |
| character_set_server     | latin1                         |
| character_set_system     | utf8                           |
| character_sets_dir       | C:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
8 rows in set (0.00 sec)

これをどうにかするため、c:\xampp\mysql\bin\my.ini を下記のように変更します。

# [client]ブロックに下記の行を追加する
default-character-set=utf8

# [mysqld]ブロックに下記の行を追加する
default-character-set=utf8
skip-character-set-client-handshake

これでMySQLを再起動すると、文字コード設定がUTF-8になります。

[c:\]$ c:\xampp\mysql\bin\mysql -uroot

mysql> SHOW variables LIKE '%char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8                           |
| character_set_connection | utf8                           |
| character_set_database   | utf8                           |
| character_set_filesystem | binary                         |
| character_set_results    | utf8                           |
| character_set_server     | utf8                           |
| character_set_system     | utf8                           |
| character_sets_dir       | C:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
8 rows in set (0.00 sec)

参考