fc2ブログ

奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

型なし(PHP、MySQL)と型あり(Java、Oracle)の違い

まず、結論を。
「型なしでも、型を強く意識して開発する事」です。

次に、型なしと型ありの定義ですが、
ここでは、数値(int型等)と文字列(String型等)の比較が「出来るかどうか」とします。
型なしは「出来て」、型ありは「出来ない」です。
また、結構恐い内容も出てきますが、
別に脅すつもりは更々ありません。
単に事実を知って頂きたいと思っているだけです。

1.条件分岐において


以下は、各種バージョン情報です。

OS

Fedora12

PHP

5.3.0

Java

1.6.0.10

・まず、PHPで書いた場合。

-------------------------------------------------------

<?php

$b = 0;
$c = 'a1';

if($b == $c) {
  echo "bとcは同じです\n";
} else {
  echo "bとcは違います\n";
}

?>

結果:
[root@www ~]# php a.php
bとcは同じです
[root@www ~]#

-------------------------------------------------------

なぜ、こうなるのかは以下辺りのマニュアルをご参照下さい。

PHP: 比較演算子 - Manual
PHP: 文字列 - Manual

・一方、Javaで書いた場合。

-------------------------------------------------------

class a {

  public static void main(String args[]) {

    int b = 0;
    String c = "a1";

    if(b == c) {
      System.out.println("bとcは同じです");
    } else {
      System.out.println("bとcは違います");
    }
  }
}

結果:
[root@www ~]# javac a.java
a.java:8: 型 int と java.lang.String は比較できません。
    if(b == c) {
         ^
エラー 1 個
[root@www ~]#
-------------------------------------------------------

「javac」というのは、あくまでコンパイルするだけであって、
プログラムを実行するわけではありません。
実行するには、「java」と入力しますが、
javaを実行するには、javacでエラーが1件も出ない事が条件になります。
つまり、上記の「やっている事は同じコード」でも、

PHP→実行可能
Java→実行不可能

となるわけです。

ちなみに、ちょっと恐い話ですが、PHPにおいて以下の様なケースも。

-------------------------------------------------------

<?php

// WEB画面上で入力したパスワード
$input_password = '0010000';
// DBに保存されているパスワード
$db_password = '10e3';

if($input_password == $db_password) {
  echo "パスワードが同じです\n";
} else {
  echo "パスワードが違います\n";
}

?>

結果:
[root@www ~]# php b.php
パスワードが同じです
[root@www ~]#

-------------------------------------------------------

上記は、個人的に浮動小数点数が苦手なので、はっきり分かりませんが、

$input_password = (float) '0010000';
$db_password = (float) '10e3';
var_dump($input_password);
var_dump($db_password);

上記の結果が、

float(10000)
float(10000)

となるので、おそらくfloatに変換してから比較していると思われます。
更にちなみに、文字列型以外でも安心は出来なくて、

if(0 == false)

上記も「true」となります。

・解決方法

マニュアルにもヒントが書いてありますが、以外と簡単で、

「==(イコール2つ)」を、「===(イコール3つ)」にする

です。
個人的には、パッと見で型を判断出来るメリットがあると思い、
文字列型だけはstrcmp関数を使って比較しています。

2.WHERE句の比較において


以下は、各種バージョン情報です。

OS

Fedora12

MySQL

5.1.41

Oracle

11.1.0.6.0

・まず、MySQLの場合

-------------------------------------------------------

テーブル作成~データ確認

mysql> CREATE TABLE a (
    ->   user_id VARCHAR(100) NOT NULL primary key,
    ->   password VARCHAR(100) NOT NULL
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into a values('a', 'b');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from a;
+---------+----------+
| user_id | password |
+---------+----------+
| a       | b        |
+---------+----------+
1 row in set (0.00 sec)

mysql>

ここから、以下のSQL文を実行します。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from a where user_id = 0;
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from a;
Empty set (0.00 sec)

mysql>

-------------------------------------------------------

上記の通り、「0」と「'a'」が同じと見なされて、
データが削除されています。
なぜ、こうなるのかは現時点では判明していませんが、
PHPと同じく、0と'a'の比較時に、
'a'が0に変換されてから比較されていると予想します。
実際、DELETE文のWHERE句を0→1に変更したら、
データは削除されなかったので。

・一方、Oracleの場合

-------------------------------------------------------

テーブル作成~データ確認

SQL> CREATE TABLE a (
  user_id VARCHAR2(100) NOT NULL primary key,
  password VARCHAR2(100) NOT NULL
);  2    3    4  

Table created.

SQL> insert into a values('a', 'b');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from a;

USER_ID
--------------------------------------------
PASSWORD
--------------------------------------------
a
b

SQL>

ここから、以下のSQL文を実行します。

SQL> delete from a where user_id = 0;

delete from a where user_id = 0
                    *
ERROR at line 1:
ORA-01722: invalid number

SQL>

-------------------------------------------------------

上記の様に、「0」と「'a'」が別と見なされてエラーが発生しました。
ちなみに、DELETE文のWHERE句を0→'0'に変更した場合。
エラーは発生しなくなりますが、やはり'a'とは一致せずに正常に終了します。

そして、上記のMySQLの挙動が、以下の処理の様な時に問題となります。

-------------------------------------------------------


<?php

$user_id = 'test';
$result = null;

$conn = new mysqli('ホスト名', 'ユーザー名', 'パスワード', 'データベース名', ポート番号);

$conn->set_charset('utf8');

$conn->autocommit(false);

$stmt = $conn->prepare('DELETE FROM a WHERE user_id = ?');

$stmt->bind_param('i', $user_id);

$stmt->execute();

if(0 < $stmt->affected_rows) {
  $result = "削除成功\n";
  $conn->commit();
} else {
  $result = "削除失敗\n";
  $conn->rollback();
}

$stmt->close();

$conn->close();

echo $result;

?>

結果:
[root@www ~]# php bind_param_test.php
削除成功
[root@www ~]#

上記の実行時に発行されるSQLは以下の通りです。
MySQLのクエリログから抜粋しています。

Prepare DELETE FROM a WHERE user_id = ?
Execute DELETE FROM a WHERE user_id = 0


-------------------------------------------------------

やはり、データが削除されます。
ポイントは、mysqli_stmt::bind_param(マニュアル)の第1引数です。
'i'は数値型を期待しているのに、文字列型を渡している為、
文字列→数値への変換により予期せぬ値になってしまっています。

・解決方法

変数の型(上記では$user_id)が文字列型で、
DB内のカラムの型(上記ではuser_id)がVARCHAR(文字列型)なのですから、
mysqli_stmt::bind_paramの第1引数も's'とすればよいだけです。
第2引数以降を文字列型と判断し、シングルクォーテーションで囲んでくれます。

・PostgreSQLは?

PostgreSQL8.4.4において。
VARCHARで定義されているカラムの値(例:'a'など)と、
数値型の値(例:0など)を比較しようとすると、「エラーになります」。
なので、この部分についてはOracleと同じの様です。

3.まとめ


PHP試験が秋に開始、オライリー本が教科書 - @ITや、
LAMP経験者のニーズ高し。ポテンシャル採用も - @IT自分戦略研究所を見ての通り、
PHPとMySQLに対するニーズが非常に高まってきています。
よって、これからプログラミングを始める人達も、
PHPとMySQLからというケースも多いと思います。
とりあえず、そういった方を見かけたら、
可能な限り、「型の存在と重要性」を伝えてあげれば良いのではないでしょうか。

スポンサーサイト



テーマ:日記 - ジャンル:日記

  1. 2010/08/01(日) 10:10:42|
  2. プログラミング
  3. | トラックバック:0
  4. | コメント:0
<<自作フレームワーク:バージョン情報とフォルダとファイル構成 | ホーム | パフォーマンスが上がるfor文の書き方>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://kitoku1.blog129.fc2.com/tb.php/13-05688650
この記事にトラックバックする(FC2ブログユーザー)